diff --git a/src/org/kde/kdeconnect/BackgroundService.java b/src/org/kde/kdeconnect/BackgroundService.java index c280f7de..8e4e6e49 100644 --- a/src/org/kde/kdeconnect/BackgroundService.java +++ b/src/org/kde/kdeconnect/BackgroundService.java @@ -1,410 +1,434 @@ /* * Copyright 2014 Albert Vaca Cintora * * 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; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.os.Binder; import android.os.Build; import android.os.IBinder; import android.support.v4.app.NotificationCompat; import android.text.TextUtils; import android.util.Log; import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BaseLinkProvider; //import org.kde.kdeconnect.Backends.BluetoothBackend.BluetoothLinkProvider; import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider; import org.kde.kdeconnect.Helpers.NotificationHelper; import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper; import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; +import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.UserInterface.MainActivity; import org.kde.kdeconnect_tp.R; import java.util.ArrayList; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class BackgroundService extends Service { private static final int FOREGROUND_NOTIFICATION_ID = 1; private static BackgroundService instance; public interface DeviceListChangedCallback { void onDeviceListChanged(); } + public interface PluginCallback { + void run(T plugin); + } + private final ConcurrentHashMap deviceListChangedCallbacks = new ConcurrentHashMap<>(); private final ArrayList linkProviders = new ArrayList<>(); private final ConcurrentHashMap devices = new ConcurrentHashMap<>(); private final HashSet discoveryModeAcquisitions = new HashSet<>(); public static BackgroundService getInstance() { return instance; } private boolean acquireDiscoveryMode(Object key) { boolean wasEmpty = discoveryModeAcquisitions.isEmpty(); discoveryModeAcquisitions.add(key); if (wasEmpty) { onNetworkChange(); } //Log.e("acquireDiscoveryMode",key.getClass().getName() +" ["+discoveryModeAcquisitions.size()+"]"); return wasEmpty; } private void releaseDiscoveryMode(Object key) { boolean removed = discoveryModeAcquisitions.remove(key); //Log.e("releaseDiscoveryMode",key.getClass().getName() +" ["+discoveryModeAcquisitions.size()+"]"); if (removed && discoveryModeAcquisitions.isEmpty()) { cleanDevices(); } } public static void addGuiInUseCounter(Context activity) { addGuiInUseCounter(activity, false); } public static void addGuiInUseCounter(final Context activity, final boolean forceNetworkRefresh) { BackgroundService.RunCommand(activity, service -> { boolean refreshed = service.acquireDiscoveryMode(activity); if (!refreshed && forceNetworkRefresh) { service.onNetworkChange(); } }); } public static void removeGuiInUseCounter(final Context activity) { BackgroundService.RunCommand(activity, service -> { //If no user interface is open, close the connections open to other devices service.releaseDiscoveryMode(activity); }); } private final Device.PairingCallback devicePairingCallback = new Device.PairingCallback() { @Override public void incomingRequest() { onDeviceListChanged(); } @Override public void pairingSuccessful() { onDeviceListChanged(); } @Override public void pairingFailed(String error) { onDeviceListChanged(); } @Override public void unpaired() { onDeviceListChanged(); } }; public void onDeviceListChanged() { for (DeviceListChangedCallback callback : deviceListChangedCallbacks.values()) { callback.onDeviceListChanged(); } if (NotificationHelper.isPersistentNotificationEnabled(this)) { //Update the foreground notification with the currently connected device list NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); nm.notify(FOREGROUND_NOTIFICATION_ID, createForegroundNotification()); } } private void loadRememberedDevicesFromSettings() { //Log.e("BackgroundService", "Loading remembered trusted devices"); SharedPreferences preferences = getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); Set trustedDevices = preferences.getAll().keySet(); for (String deviceId : trustedDevices) { //Log.e("BackgroundService", "Loading device "+deviceId); if (preferences.getBoolean(deviceId, false)) { Device device = new Device(this, deviceId); devices.put(deviceId, device); device.addPairingCallback(devicePairingCallback); } } } private void registerLinkProviders() { linkProviders.add(new LanLinkProvider(this)); // linkProviders.add(new LoopbackLinkProvider(this)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { // linkProviders.add(new BluetoothLinkProvider(this)); } } public ArrayList getLinkProviders() { return linkProviders; } public Device getDevice(String id) { return devices.get(id); } private void cleanDevices() { new Thread(() -> { for (Device d : devices.values()) { if (!d.isPaired() && !d.isPairRequested() && !d.isPairRequestedByPeer() && !d.deviceShouldBeKeptAlive()) { d.disconnect(); } } }).start(); } private final BaseLinkProvider.ConnectionReceiver deviceListener = new BaseLinkProvider.ConnectionReceiver() { @Override public void onConnectionReceived(final NetworkPacket identityPacket, final BaseLink link) { String deviceId = identityPacket.getString("deviceId"); Device device = devices.get(deviceId); if (device != null) { Log.i("KDE/BackgroundService", "addLink, known device: " + deviceId); device.addLink(identityPacket, link); } else { Log.i("KDE/BackgroundService", "addLink,unknown device: " + deviceId); device = new Device(BackgroundService.this, identityPacket, link); if (device.isPaired() || device.isPairRequested() || device.isPairRequestedByPeer() || link.linkShouldBeKeptAlive() || !discoveryModeAcquisitions.isEmpty()) { devices.put(deviceId, device); device.addPairingCallback(devicePairingCallback); } else { device.disconnect(); } } onDeviceListChanged(); } @Override public void onConnectionLost(BaseLink link) { Device d = devices.get(link.getDeviceId()); Log.i("KDE/onConnectionLost", "removeLink, deviceId: " + link.getDeviceId()); if (d != null) { d.removeLink(link); if (!d.isReachable() && !d.isPaired()) { //Log.e("onConnectionLost","Removing connection device because it was not paired"); devices.remove(link.getDeviceId()); d.removePairingCallback(devicePairingCallback); } } else { //Log.d("KDE/onConnectionLost","Removing connection to unknown device"); } onDeviceListChanged(); } }; public ConcurrentHashMap getDevices() { return devices; } public void onNetworkChange() { for (BaseLinkProvider a : linkProviders) { a.onNetworkChange(); } } public void addConnectionListener(BaseLinkProvider.ConnectionReceiver cr) { for (BaseLinkProvider a : linkProviders) { a.addConnectionReceiver(cr); } } public void removeConnectionListener(BaseLinkProvider.ConnectionReceiver cr) { for (BaseLinkProvider a : linkProviders) { a.removeConnectionReceiver(cr); } } public void addDeviceListChangedCallback(String key, DeviceListChangedCallback callback) { deviceListChangedCallbacks.put(key, callback); } public void removeDeviceListChangedCallback(String key) { deviceListChangedCallbacks.remove(key); } //This will called only once, even if we launch the service intent several times @Override public void onCreate() { super.onCreate(); instance = this; // Register screen on listener IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); registerReceiver(new KdeConnectBroadcastReceiver(), filter); Log.i("KDE/BackgroundService", "Service not started yet, initializing..."); initializeSecurityParameters(); NotificationHelper.initializeChannels(this); loadRememberedDevicesFromSettings(); registerLinkProviders(); //Link Providers need to be already registered addConnectionListener(deviceListener); for (BaseLinkProvider a : linkProviders) { a.onStart(); } } public void changePersistentNotificationVisibility(boolean visible) { NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); if (visible) { nm.notify(FOREGROUND_NOTIFICATION_ID, createForegroundNotification()); } else { stopForeground(true); Start(this); } } private Notification createForegroundNotification() { //Why is this needed: https://developer.android.com/guide/components/services#Foreground Intent intent = new Intent(this, MainActivity.class); PendingIntent pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.Builder notification = new NotificationCompat.Builder(this, NotificationHelper.Channels.PERSISTENT); notification .setSmallIcon(R.drawable.ic_notification) .setOngoing(true) .setContentIntent(pi) .setPriority(NotificationCompat.PRIORITY_MIN) //MIN so it's not shown in the status bar before Oreo, on Oreo it will be bumped to LOW .setShowWhen(false) .setAutoCancel(false); notification.setGroup("BackgroundService"); ArrayList connectedDevices = new ArrayList<>(); for (Device device : getDevices().values()) { if (device.isReachable() && device.isPaired()) { connectedDevices.add(device.getName()); } } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { //Pre-oreo, the notification will have an empty title line without this notification.setContentTitle("KDE Connect"); } if (connectedDevices.isEmpty()) { notification.setContentText(getString(R.string.foreground_notification_no_devices)); } else { notification.setContentText(getString(R.string.foreground_notification_devices, TextUtils.join(", ", connectedDevices))); } return notification.build(); } private void initializeSecurityParameters() { RsaHelper.initialiseRsaKeys(this); SslHelper.initialiseCertificate(this); } @Override public void onDestroy() { stopForeground(true); for (BaseLinkProvider a : linkProviders) { a.onStop(); } super.onDestroy(); } @Override public IBinder onBind(Intent intent) { return new Binder(); } //To use the service from the gui public interface InstanceCallback { void onServiceStart(BackgroundService service); } private final static ArrayList callbacks = new ArrayList<>(); private final static Lock mutex = new ReentrantLock(true); @Override public int onStartCommand(Intent intent, int flags, int startId) { //This will be called for each intent launch, even if the service is already started and it is reused mutex.lock(); try { for (InstanceCallback c : callbacks) { c.onServiceStart(this); } callbacks.clear(); } finally { mutex.unlock(); } if (NotificationHelper.isPersistentNotificationEnabled(this)) { startForeground(FOREGROUND_NOTIFICATION_ID, createForegroundNotification()); } return Service.START_STICKY; } private static void Start(Context c) { RunCommand(c, null); } public static void RunCommand(final Context c, final InstanceCallback callback) { new Thread(() -> { if (callback != null) { mutex.lock(); try { callbacks.add(callback); } finally { mutex.unlock(); } } Intent serviceIntent = new Intent(c, BackgroundService.class); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { c.startForegroundService(serviceIntent); } else { c.startService(serviceIntent); } }).start(); } + public static void runWithPlugin(final Context c, final String deviceId, final Class pluginClass, final PluginCallback cb) { + RunCommand(c, service -> { + Device device = service.getDevice(deviceId); + + if (device == null) { + Log.e("BackgroundService", "Device " + deviceId + " not found"); + return; + } + + final T plugin = device.getPlugin(pluginClass); + + if (plugin == null) { + Log.e("BackgroundService", "Device " + device.getName() + " does not have plugin " + pluginClass.getName()); + return; + } + cb.run(plugin); + }); + } + } diff --git a/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadActivity.java b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadActivity.java index f1331de6..8f143837 100644 --- a/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadActivity.java +++ b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadActivity.java @@ -1,426 +1,381 @@ /* * Copyright 2014 Ahmed I. Khalil * * 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 . -*/ + * along with this program. If not, see . + */ package org.kde.kdeconnect.Plugins.MousePadPlugin; import android.content.Context; import android.content.SharedPreferences; import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.v7.app.AppCompatActivity; import android.view.GestureDetector; import android.view.HapticFeedbackConstants; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.inputmethod.InputMethodManager; import org.kde.kdeconnect.BackgroundService; -import org.kde.kdeconnect.Device; import org.kde.kdeconnect.UserInterface.ThemeUtil; import org.kde.kdeconnect_tp.R; public class MousePadActivity extends AppCompatActivity implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener, MousePadGestureDetector.OnGestureListener { private String deviceId; private final static float MinDistanceToSendScroll = 2.5f; // touch gesture scroll private final static float MinDistanceToSendGenericScroll = 0.1f; // real mouse scroll wheel event private final static float StandardDpi = 240.0f; // = hdpi private float mPrevX; private float mPrevY; private float mCurrentX; private float mCurrentY; private float mCurrentSensitivity; private float displayDpiMultiplier; private int scrollDirection = 1; private boolean isScrolling = false; private float accumulatedDistanceY = 0; private GestureDetector mDetector; private MousePadGestureDetector mMousePadGestureDetector; private PointerAccelerationProfile mPointerAccelerationProfile; private PointerAccelerationProfile.MouseDelta mouseDelta; // to be reused on every touch move event private KeyListenerView keyListenerView; enum ClickType { RIGHT, MIDDLE, NONE; static ClickType fromString(String s) { switch (s) { case "right": return RIGHT; case "middle": return MIDDLE; default: return NONE; } } } private ClickType doubleTapAction, tripleTapAction; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ThemeUtil.setUserPreferredTheme(this); setContentView(R.layout.activity_mousepad); deviceId = getIntent().getStringExtra("deviceId"); getWindow().getDecorView().setHapticFeedbackEnabled(true); mDetector = new GestureDetector(this, this); mMousePadGestureDetector = new MousePadGestureDetector(this); mDetector.setOnDoubleTapListener(this); keyListenerView = findViewById(R.id.keyListener); keyListenerView.setDeviceId(deviceId); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); if (prefs.getBoolean(getString(R.string.mousepad_scroll_direction), false)) { scrollDirection = -1; } else { scrollDirection = 1; } String doubleTapSetting = prefs.getString(getString(R.string.mousepad_double_tap_key), getString(R.string.mousepad_default_double)); String tripleTapSetting = prefs.getString(getString(R.string.mousepad_triple_tap_key), getString(R.string.mousepad_default_triple)); String sensitivitySetting = prefs.getString(getString(R.string.mousepad_sensitivity_key), getString(R.string.mousepad_default_sensitivity)); - String accelerationProfileName=prefs.getString(getString(R.string.mousepad_acceleration_profile_key), + String accelerationProfileName = prefs.getString(getString(R.string.mousepad_acceleration_profile_key), getString(R.string.mousepad_default_acceleration_profile)); mPointerAccelerationProfile = PointerAccelerationProfileFactory.getProfileWithName(accelerationProfileName); doubleTapAction = ClickType.fromString(doubleTapSetting); tripleTapAction = ClickType.fromString(tripleTapSetting); //Technically xdpi and ydpi should be handled separately, //but since ydpi is usually almost equal to xdpi, only xdpi is used for the multiplier. displayDpiMultiplier = StandardDpi / getResources().getDisplayMetrics().xdpi; switch (sensitivitySetting) { case "slowest": mCurrentSensitivity = 0.2f; break; case "aboveSlowest": mCurrentSensitivity = 0.5f; break; case "default": mCurrentSensitivity = 1.0f; break; case "aboveDefault": mCurrentSensitivity = 1.5f; break; case "fastest": mCurrentSensitivity = 2.0f; break; default: mCurrentSensitivity = 1.0f; return; } final View decorView = getWindow().getDecorView(); decorView.setOnSystemUiVisibilityChangeListener(visibility -> { if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { int fullscreenType = 0; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { fullscreenType |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { fullscreenType |= View.SYSTEM_UI_FLAG_FULLSCREEN; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { fullscreenType |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; } getWindow().getDecorView().setSystemUiVisibility(fullscreenType); } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu_mousepad, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_right_click: sendRightClick(); return true; case R.id.menu_middle_click: sendMiddleClick(); return true; case R.id.menu_show_keyboard: showKeyboard(); return true; default: return super.onOptionsItemSelected(item); } } @Override public boolean onTouchEvent(MotionEvent event) { if (mMousePadGestureDetector.onTouchEvent(event)) { return true; } if (mDetector.onTouchEvent(event)) { return true; } int actionType = event.getAction(); if (isScrolling) { if (actionType == MotionEvent.ACTION_UP) { isScrolling = false; } else { return false; } } switch (actionType) { case MotionEvent.ACTION_DOWN: mPrevX = event.getX(); mPrevY = event.getY(); break; case MotionEvent.ACTION_MOVE: mCurrentX = event.getX(); mCurrentY = event.getY(); - BackgroundService.RunCommand(this, service -> { - Device device = service.getDevice(deviceId); - MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class); - if (mousePadPlugin == null) return; + BackgroundService.runWithPlugin(this, deviceId, MousePadPlugin.class, plugin -> { float deltaX = (mCurrentX - mPrevX) * displayDpiMultiplier * mCurrentSensitivity; float deltaY = (mCurrentY - mPrevY) * displayDpiMultiplier * mCurrentSensitivity; // Run the mouse delta through the pointer acceleration profile mPointerAccelerationProfile.touchMoved(deltaX, deltaY, event.getEventTime()); mouseDelta = mPointerAccelerationProfile.commitAcceleratedMouseDelta(mouseDelta); - mousePadPlugin.sendMouseDelta(mouseDelta.x,mouseDelta.y); + plugin.sendMouseDelta(mouseDelta.x, mouseDelta.y); mPrevX = mCurrentX; mPrevY = mCurrentY; }); break; } return true; } @Override public boolean onDown(MotionEvent e) { return false; } @Override public void onShowPress(MotionEvent e) { //From GestureDetector, left empty } @Override public boolean onSingleTapUp(MotionEvent e) { return false; } @Override public boolean onGenericMotionEvent(MotionEvent e) { if (e.getAction() == MotionEvent.ACTION_SCROLL) { final float distanceY = e.getAxisValue(MotionEvent.AXIS_VSCROLL); accumulatedDistanceY += distanceY; if (accumulatedDistanceY > MinDistanceToSendGenericScroll || accumulatedDistanceY < -MinDistanceToSendGenericScroll) { sendScroll(accumulatedDistanceY); accumulatedDistanceY = 0; } } return super.onGenericMotionEvent(e); } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, final float distanceX, final float distanceY) { // If only one thumb is used then cancel the scroll gesture if (e2.getPointerCount() <= 1) { return false; } isScrolling = true; accumulatedDistanceY += distanceY; if (accumulatedDistanceY > MinDistanceToSendScroll || accumulatedDistanceY < -MinDistanceToSendScroll) { sendScroll(scrollDirection * accumulatedDistanceY); accumulatedDistanceY = 0; } return true; } @Override public void onLongPress(MotionEvent e) { - getWindow().getDecorView().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); - - BackgroundService.RunCommand(this, service -> { - Device device = service.getDevice(deviceId); - MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class); - if (mousePadPlugin == null) return; - mousePadPlugin.sendSingleHold(); - }); + BackgroundService.runWithPlugin(this, deviceId, MousePadPlugin.class, MousePadPlugin::sendSingleHold); } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return false; } @Override public boolean onSingleTapConfirmed(MotionEvent e) { - BackgroundService.RunCommand(this, service -> { - Device device = service.getDevice(deviceId); - MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class); - if (mousePadPlugin == null) return; - mousePadPlugin.sendSingleClick(); - }); + BackgroundService.runWithPlugin(this, deviceId, MousePadPlugin.class, MousePadPlugin::sendSingleClick); return true; } @Override public boolean onDoubleTap(MotionEvent e) { - BackgroundService.RunCommand(this, service -> { - Device device = service.getDevice(deviceId); - MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class); - if (mousePadPlugin == null) return; - mousePadPlugin.sendDoubleClick(); - }); + BackgroundService.runWithPlugin(this, deviceId, MousePadPlugin.class, MousePadPlugin::sendDoubleClick); return true; } @Override public boolean onDoubleTapEvent(MotionEvent e) { return false; } @Override public boolean onTripleFingerTap(MotionEvent ev) { switch (tripleTapAction) { case RIGHT: sendRightClick(); break; case MIDDLE: sendMiddleClick(); break; default: } return true; } @Override public boolean onDoubleFingerTap(MotionEvent ev) { switch (doubleTapAction) { case RIGHT: sendRightClick(); break; case MIDDLE: sendMiddleClick(); break; default: } return true; } private void sendMiddleClick() { - BackgroundService.RunCommand(this, service -> { - Device device = service.getDevice(deviceId); - MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class); - if (mousePadPlugin == null) return; - mousePadPlugin.sendMiddleClick(); - }); + BackgroundService.runWithPlugin(this, deviceId, MousePadPlugin.class, MousePadPlugin::sendMiddleClick); } private void sendRightClick() { - BackgroundService.RunCommand(this, service -> { - Device device = service.getDevice(deviceId); - MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class); - if (mousePadPlugin == null) return; - mousePadPlugin.sendRightClick(); - }); - } - - private void sendSingleHold() { - BackgroundService.RunCommand(this, service -> { - Device device = service.getDevice(deviceId); - MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class); - if (mousePadPlugin == null) return; - mousePadPlugin.sendSingleHold(); - }); + BackgroundService.runWithPlugin(this, deviceId, MousePadPlugin.class, MousePadPlugin::sendRightClick); } private void sendScroll(final float y) { - BackgroundService.RunCommand(this, service -> { - Device device = service.getDevice(deviceId); - MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class); - if (mousePadPlugin == null) return; - mousePadPlugin.sendScroll(0, y); - }); + BackgroundService.runWithPlugin(this, deviceId, MousePadPlugin.class, plugin -> plugin.sendScroll(0, y)); } private void showKeyboard() { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.toggleSoftInputFromWindow(keyListenerView.getWindowToken(), 0, 0); } @Override protected void onStart() { super.onStart(); BackgroundService.addGuiInUseCounter(this); } @Override protected void onStop() { super.onStop(); BackgroundService.removeGuiInUseCounter(this); } } diff --git a/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterActivity.java b/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterActivity.java index ea9b6f7a..e9f82999 100644 --- a/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterActivity.java +++ b/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterActivity.java @@ -1,156 +1,142 @@ /* * Copyright 2014 Ahmed I. Khalil * * 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.PresenterPlugin; import android.content.Context; import android.os.Build; import android.os.Bundle; import android.os.PowerManager; import android.support.v4.media.VolumeProviderCompat; import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.PlaybackStateCompat; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Device; import org.kde.kdeconnect.UserInterface.ThemeUtil; import org.kde.kdeconnect_tp.R; public class PresenterActivity extends AppCompatActivity { private MediaSessionCompat mMediaSession; private PresenterPlugin plugin; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ThemeUtil.setUserPreferredTheme(this); setContentView(R.layout.activity_presenter); final String deviceId = getIntent().getStringExtra("deviceId"); - BackgroundService.RunCommand(this, service -> { - Device device = service.getDevice(deviceId); - - plugin = device.getPlugin(PresenterPlugin.class); - if (plugin == null) { - Log.e("PresenterActivity", "device has no presenter plugin!"); - return; - } - - runOnUiThread(() -> { - - findViewById(R.id.next_button).setOnClickListener(v -> plugin.sendNext()); - - findViewById(R.id.previous_button).setOnClickListener(v -> plugin.sendPrevious()); - - - }); - }); - + BackgroundService.runWithPlugin(this, deviceId, PresenterPlugin.class, plugin -> runOnUiThread(() -> { + this.plugin = plugin; + findViewById(R.id.next_button).setOnClickListener(v -> plugin.sendNext()); + findViewById(R.id.previous_button).setOnClickListener(v -> plugin.sendPrevious()); + })); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu_presenter, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()) { case R.id.fullscreen: plugin.sendFullscreen(); return true; case R.id.exit_presentation: plugin.sendEsc(); return true; default: return super.onContextItemSelected(item); } } @Override protected void onStart() { super.onStart(); BackgroundService.addGuiInUseCounter(this); if (mMediaSession != null) { mMediaSession.setActive(true); return; } createMediaSession(); //Mediasession will keep } @Override protected void onStop() { super.onStop(); BackgroundService.removeGuiInUseCounter(this); if (mMediaSession != null) { PowerManager pm = (PowerManager) this.getSystemService(Context.POWER_SERVICE); boolean screenOn; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { screenOn = pm.isInteractive(); } else { screenOn = pm.isScreenOn(); } if (screenOn) { mMediaSession.release(); } // else we are in the lockscreen, keep the mediasession } } private void createMediaSession() { mMediaSession = new MediaSessionCompat(this, "kdeconnect"); mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); mMediaSession.setPlaybackState(new PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_PLAYING, 0, 0) .build()); mMediaSession.setPlaybackToRemote(getVolumeProvider()); mMediaSession.setActive(true); } private VolumeProviderCompat getVolumeProvider() { final int VOLUME_UP = 1; final int VOLUME_DOWN = -1; return new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, 1, 0) { @Override public void onAdjustVolume(int direction) { if (direction == VOLUME_UP) { plugin.sendNext(); } else if (direction == VOLUME_DOWN) { plugin.sendPrevious(); } } }; } } diff --git a/src/org/kde/kdeconnect/Plugins/SystemvolumePlugin/SystemvolumeFragment.java b/src/org/kde/kdeconnect/Plugins/SystemvolumePlugin/SystemvolumeFragment.java index 8f239bef..75a6b61f 100644 --- a/src/org/kde/kdeconnect/Plugins/SystemvolumePlugin/SystemvolumeFragment.java +++ b/src/org/kde/kdeconnect/Plugins/SystemvolumePlugin/SystemvolumeFragment.java @@ -1,168 +1,154 @@ /* * Copyright 2018 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.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.ListFragment; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; 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 ListFragment implements Sink.UpdateListener, SystemvolumePlugin.SinkListener { private SystemvolumePlugin plugin; private Activity activity; private SinkAdapter adapter; private Context context; private boolean tracking; @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); getListView().setDivider(null); setListAdapter(new SinkAdapter(getContext(), new Sink[0])); } @Override public void updateSink(final Sink sink) { // Don't set progress while the slider is moved if (!tracking) { activity.runOnUiThread(() -> adapter.notifyDataSetChanged()); } } public void connectToPlugin(final String deviceId) { - - BackgroundService.RunCommand(activity, service -> { - Device device = service.getDevice(deviceId); - - if (device == null) - return; - - plugin = device.getPlugin(SystemvolumePlugin.class); - - if (plugin == null) { - Log.e("SystemvolumeFragment", "device has no systemvolume plugin!"); - return; - } - + BackgroundService.runWithPlugin(activity, deviceId, SystemvolumePlugin.class, plugin -> { + this.plugin = plugin; plugin.addSinkListener(SystemvolumeFragment.this); plugin.requestSinkList(); - Log.d("Systemvolume", "requestSinklist"); }); - } @Override public void onAttach(Context context) { super.onAttach(context); activity = getActivity(); this.context = context; } @Override public void sinksChanged() { for (Sink sink : plugin.getSinks()) { sink.addListener(SystemvolumeFragment.this); } activity.runOnUiThread(() -> { adapter = new SinkAdapter(context, plugin.getSinks().toArray(new Sink[0])); setListAdapter(adapter); }); } private class SinkAdapter extends ArrayAdapter { private SinkAdapter(@NonNull Context context, @NonNull Sink[] objects) { super(context, R.layout.list_item_systemvolume, objects); } @NonNull @Override public View getView(final int position, @Nullable View convertView, @NonNull ViewGroup parent) { View view = getLayoutInflater().inflate(R.layout.list_item_systemvolume, parent, false); UIListener listener = new UIListener(getItem(position)); ((TextView) view.findViewById(R.id.systemvolume_label)).setText(getItem(position).getDescription()); final SeekBar seekBar = view.findViewById(R.id.systemvolume_seek); seekBar.setMax(getItem(position).getMaxVolume()); seekBar.setProgress(getItem(position).getVolume()); seekBar.setOnSeekBarChangeListener(listener); ImageButton button = view.findViewById(R.id.systemvolume_mute); int iconRes = getItem(position).isMute() ? R.drawable.ic_volume_mute_black : R.drawable.ic_volume_black; button.setImageResource(iconRes); button.setOnClickListener(listener); return view; } } private class UIListener implements SeekBar.OnSeekBarChangeListener, ImageButton.OnClickListener { private final Sink sink; private UIListener(Sink sink) { this.sink = sink; } @Override public void onProgressChanged(final SeekBar seekBar, int i, boolean b) { BackgroundService.RunCommand(activity, service -> plugin.sendVolume(sink.getName(), seekBar.getProgress())); } @Override public void onStartTrackingTouch(SeekBar seekBar) { tracking = true; } @Override public void onStopTrackingTouch(final SeekBar seekBar) { tracking = false; BackgroundService.RunCommand(activity, service -> plugin.sendVolume(sink.getName(), seekBar.getProgress())); } @Override public void onClick(View view) { plugin.sendMute(sink.getName(), !sink.isMute()); } } }