diff --git a/res/drawable/ic_volume_mute_black.xml b/res/drawable/ic_volume_mute_black.xml new file mode 100644 --- /dev/null +++ b/res/drawable/ic_volume_mute_black.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/res/layout/fragment_systemvolume.xml b/res/layout/fragment_systemvolume.xml new file mode 100644 --- /dev/null +++ b/res/layout/fragment_systemvolume.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/mpris_control.xml b/res/layout/mpris_control.xml --- a/res/layout/mpris_control.xml +++ b/res/layout/mpris_control.xml @@ -166,19 +166,10 @@ - diff --git a/res/values/strings.xml b/res/values/strings.xml --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -217,4 +217,9 @@ To see phone calls and SMS from the desktop you need to give permission to phone calls and SMS To see a contact name instead of a phone number you need to give access to the phone\'s contacts + System volume: + System volume + Control the system volume of the remote device + Mute + diff --git a/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisActivity.java b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisActivity.java --- a/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisActivity.java +++ b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisActivity.java @@ -26,6 +26,8 @@ import android.os.Message; import android.preference.PreferenceManager; import android.support.annotation.NonNull; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.KeyEvent; @@ -42,6 +44,7 @@ import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Device; import org.kde.kdeconnect.NetworkPackage; +import org.kde.kdeconnect.Plugins.SystemvolumePlugin.SystemvolumeFragment; import org.kde.kdeconnect_tp.R; import java.util.List; @@ -81,8 +84,11 @@ Log.e("MprisActivity", "device has no mpris plugin!"); return; } + targetPlayer = mpris.getPlayerStatus(targetPlayerName); + addSytemvolumeFragment(); + mpris.setPlayerStatusUpdatedHandler("activity", new Handler() { @Override public void handleMessage(Message msg) { @@ -160,6 +166,17 @@ } + private void addSytemvolumeFragment() { + + FragmentManager fragmentManager = getSupportFragmentManager(); +// FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); +// SystemvolumeFragment fragment = new SystemvolumeFragment(); +// fragmentTransaction.add(R.id.mpris_control_view, fragment); +// fragmentTransaction.commitNow(); + ((SystemvolumeFragment) fragmentManager.findFragmentById(R.id.systemvolume_fragment)).connectToPlugin(deviceId); + + } + private final BaseLinkProvider.ConnectionReceiver connectionReceiver = new BaseLinkProvider.ConnectionReceiver() { @Override public void onConnectionReceived(NetworkPackage identityPackage, BaseLink link) { @@ -442,4 +459,5 @@ BackgroundService.removeGuiInUseCounter(this); } + } diff --git a/src/org/kde/kdeconnect/Plugins/PluginFactory.java b/src/org/kde/kdeconnect/Plugins/PluginFactory.java --- a/src/org/kde/kdeconnect/Plugins/PluginFactory.java +++ b/src/org/kde/kdeconnect/Plugins/PluginFactory.java @@ -37,6 +37,7 @@ import org.kde.kdeconnect.Plugins.RunCommandPlugin.RunCommandPlugin; import org.kde.kdeconnect.Plugins.SftpPlugin.SftpPlugin; import org.kde.kdeconnect.Plugins.SharePlugin.SharePlugin; +import org.kde.kdeconnect.Plugins.SystemvolumePlugin.SystemvolumePlugin; import org.kde.kdeconnect.Plugins.TelepathyPlugin.TelepathyPlugin; import org.kde.kdeconnect.Plugins.TelephonyPlugin.TelephonyPlugin; @@ -126,6 +127,7 @@ PluginFactory.registerPlugin(FindMyPhonePlugin.class); PluginFactory.registerPlugin(RunCommandPlugin.class); PluginFactory.registerPlugin(RemoteKeyboardPlugin.class); + PluginFactory.registerPlugin(SystemvolumePlugin.class); } public static PluginInfo getPluginInfo(Context context, String pluginKey) { diff --git a/src/org/kde/kdeconnect/Plugins/SystemvolumePlugin/Sink.java b/src/org/kde/kdeconnect/Plugins/SystemvolumePlugin/Sink.java new file mode 100644 --- /dev/null +++ b/src/org/kde/kdeconnect/Plugins/SystemvolumePlugin/Sink.java @@ -0,0 +1,83 @@ +/* + * Copyright 2017 Nicolas Fella + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package org.kde.kdeconnect.Plugins.SystemvolumePlugin; + +import org.kde.kdeconnect.NetworkPackage; + +import java.util.ArrayList; +import java.util.List; + +class Sink { + + public interface UpdateListener { + void updateSink(Sink sink); + } + + private int volume; + private String description; + private String name; + private boolean mute; + + private List listeners; + + public Sink(String name) { + listeners = new ArrayList<>(); + this.name = name; + } + + public void update(NetworkPackage np) { + volume = np.getInt("volume"); + description = np.getString("description"); + mute = np.getBoolean("mute"); + + for (UpdateListener l: listeners) { + l.updateSink(this); + } + } + + public int getVolume() { + return volume; + } + + public String getDescription() { + return description; + } + + public String getName() { + return name; + } + + public boolean isMute() { + return mute; + } + + public void addListener(UpdateListener l) { + + if (!listeners.contains(l)) { + listeners.add(l); + } + } + + public void removeListener(UpdateListener l) { + listeners.remove(l); + } + +} diff --git a/src/org/kde/kdeconnect/Plugins/SystemvolumePlugin/SystemvolumeFragment.java b/src/org/kde/kdeconnect/Plugins/SystemvolumePlugin/SystemvolumeFragment.java new file mode 100644 --- /dev/null +++ b/src/org/kde/kdeconnect/Plugins/SystemvolumePlugin/SystemvolumeFragment.java @@ -0,0 +1,169 @@ +/* + * Copyright 2017 Nicolas Fella + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package org.kde.kdeconnect.Plugins.SystemvolumePlugin; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.os.Handler; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.SeekBar; +import android.widget.TextView; + +import org.kde.kdeconnect.BackgroundService; +import org.kde.kdeconnect.Device; +import org.kde.kdeconnect_tp.R; + +public class SystemvolumeFragment extends Fragment implements Sink.UpdateListener, View.OnClickListener, Runnable, SeekBar.OnSeekBarChangeListener { + + private final Handler updateHandler = new Handler(); + + private SeekBar seekBar; + private ImageButton button; + private TextView description; + + private boolean mute = false; + private SystemvolumePlugin plugin; + + private Activity activity; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + + View view = inflater.inflate(R.layout.fragment_systemvolume, container, false); + + seekBar = (SeekBar) view.findViewById(R.id.systemvolume_seek); + seekBar.setMax(65535); + seekBar.setOnSeekBarChangeListener(this); + + button = (ImageButton) view.findViewById(R.id.systemvolume_mute); + button.setOnClickListener(this); + + description = (TextView) view.findViewById(R.id.systemvolume_label); + + return view; + } + + @Override + public void updateSink(final Sink sink) { + + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + seekBar.setProgress(sink.getVolume()); + description.setText(sink.getDescription()); + + int iconRes = sink.isMute() ? R.drawable.ic_volume_mute_black : R.drawable.ic_volume_black; + button.setImageResource(iconRes); + } + }); + } + + @Override + public void onStop() { + super.onStop(); + updateHandler.removeCallbacks(this); + } + + @Override + public void onClick(View view) { + + mute = !mute; + plugin.setMute(mute); + + } + + @Override + public void run() { + BackgroundService.RunCommand(activity, new BackgroundService.InstanceCallback() { + @Override + public void onServiceStart(BackgroundService service) { + + plugin.askForSystemVolume(); + + for (Sink sink : plugin.getSinks()) { + sink.addListener(SystemvolumeFragment.this); + } + + updateHandler.removeCallbacks(SystemvolumeFragment.this); + updateHandler.postDelayed(SystemvolumeFragment.this, 500); + } + }); + } + + @Override + public void onProgressChanged(final SeekBar seekBar, int i, boolean b) { + BackgroundService.RunCommand(activity, new BackgroundService.InstanceCallback() { + @Override + public void onServiceStart(BackgroundService service) { + plugin.sendSystemVolumeSet(seekBar.getProgress()); + } + }); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + updateHandler.removeCallbacks(this); + } + + @Override + public void onStopTrackingTouch(final SeekBar seekBar) { + BackgroundService.RunCommand(activity, new BackgroundService.InstanceCallback() { + @Override + public void onServiceStart(BackgroundService service) { + plugin.sendSystemVolumeSet(seekBar.getProgress()); + updateHandler.postDelayed(SystemvolumeFragment.this, 1); + } + }); + } + + public void connectToPlugin(final String deviceId) { + + BackgroundService.RunCommand(activity, new BackgroundService.InstanceCallback() { + @Override + public void onServiceStart(BackgroundService service) { + Device device = service.getDevice(deviceId); + plugin = device.getPlugin(SystemvolumePlugin.class); + + if (plugin == null) { + Log.e("SystemvolumeFragment", "device has no systemvolume plugin!"); + return; + } + + updateHandler.postDelayed(SystemvolumeFragment.this, 0); + } + }); + + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + activity = getActivity(); + } +} diff --git a/src/org/kde/kdeconnect/Plugins/SystemvolumePlugin/SystemvolumePlugin.java b/src/org/kde/kdeconnect/Plugins/SystemvolumePlugin/SystemvolumePlugin.java new file mode 100644 --- /dev/null +++ b/src/org/kde/kdeconnect/Plugins/SystemvolumePlugin/SystemvolumePlugin.java @@ -0,0 +1,113 @@ +/* + * Copyright 2017 Nicolas Fella + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package org.kde.kdeconnect.Plugins.SystemvolumePlugin; + +import android.util.Log; + +import org.kde.kdeconnect.NetworkPackage; +import org.kde.kdeconnect.Plugins.Plugin; +import org.kde.kdeconnect_tp.R; + +import java.util.Collection; +import java.util.HashMap; + + +public class SystemvolumePlugin extends Plugin { + + private final static String PACKAGE_TYPE_SYSTEMVOLUME = "kdeconnect.systemvolume"; + + private HashMap sinks; + + public SystemvolumePlugin() { + sinks = new HashMap<>(); + } + + @Override + public String getDisplayName() { + return context.getResources().getString(R.string.pref_plugin_systemvolume); + } + + @Override + public String getDescription() { + return context.getResources().getString(R.string.pref_plugin_systemvolume_desc); + } + + @Override + public boolean onPackageReceived(NetworkPackage np) { + + String name = np.getString("name"); + + if (!sinks.containsKey(name)) { + sinks.put(name, new Sink(name)); + } + + sinks.get(np.getString("name")).update(np); + + return true; + } + + public void sendSystemVolumeSet(int volume) { + + NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_SYSTEMVOLUME); + np.set("volume", volume); + np.set("request", false); + device.sendPackage(np); + } + + public void askForSystemVolume() { + NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_SYSTEMVOLUME); + np.set("request", true); + device.sendPackage(np); + Log.d("Systemvolume", "sent ask"); + } + + public void setMute(boolean mute) { + NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_SYSTEMVOLUME); + np.set("mute", mute); + np.set("request", false); + device.sendPackage(np); + } + + @Override + public boolean hasMainActivity() { + return false; + } + + @Override + public boolean displayInContextMenu() { + return false; + } + + @Override + public String[] getSupportedPackageTypes() { + return new String[]{PACKAGE_TYPE_SYSTEMVOLUME}; + } + + @Override + public String[] getOutgoingPackageTypes() { + return new String[]{PACKAGE_TYPE_SYSTEMVOLUME}; + } + + public Collection getSinks() { + return sinks.values(); + } + +}