From ad801f1db8047b6644f890c68c0a6ff9c81ee326 Mon Sep 17 00:00:00 2001 From: Erik Duisters Date: Sun, 5 Jan 2020 17:40:54 +0000 Subject: [PATCH] Make FindMyPhone plugin work on Android 10 --- AndroidManifest.xml | 11 +- build.gradle | 5 +- res/values/strings.xml | 3 +- .../Helpers/NotificationHelper.java | 3 + src/org/kde/kdeconnect/MyApplication.java | 41 +++++ .../FindMyPhoneActivity.java | 79 +++------- .../FindMyPhonePlugin/FindMyPhonePlugin.java | 145 +++++++++++++++++- .../FindMyPhoneReceiver.java | 35 +++++ 8 files changed, 258 insertions(+), 64 deletions(-) create mode 100644 src/org/kde/kdeconnect/MyApplication.java create mode 100644 src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhoneReceiver.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index d025d66d..ecbbdb7c 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -33,6 +34,7 @@ + + android:theme="@style/KdeConnectTheme" + android:name="org.kde.kdeconnect.MyApplication"> @@ -119,6 +122,12 @@ android:label="@string/findmyphone_title" android:launchMode="singleInstance" /> + + + + + + Find my tablet Find my TV Rings this device so you can find it - Found + Found it Open Close @@ -300,6 +300,7 @@ Persistent indicator Media control File transfer + High priority Stop the current player Copy URL to clipboard diff --git a/src/org/kde/kdeconnect/Helpers/NotificationHelper.java b/src/org/kde/kdeconnect/Helpers/NotificationHelper.java index 60d19e38..a2305afe 100644 --- a/src/org/kde/kdeconnect/Helpers/NotificationHelper.java +++ b/src/org/kde/kdeconnect/Helpers/NotificationHelper.java @@ -18,6 +18,7 @@ public class NotificationHelper { public final static String MEDIA_CONTROL = "media_control"; public final static String FILETRANSFER = "filetransfer"; public final static String RECEIVENOTIFICATION = "receive"; + public final static String HIGHPRIORITY = "highpriority"; } public static void notifyCompat(NotificationManager notificationManager, int notificationId, Notification notification) { @@ -80,6 +81,8 @@ public class NotificationHelper { NotificationManager.IMPORTANCE_DEFAULT) ); + NotificationChannel highPriority = new NotificationChannel(Channels.HIGHPRIORITY, context.getString(R.string.notification_channel_high_priority), NotificationManager.IMPORTANCE_HIGH); + manager.createNotificationChannel(highPriority); } diff --git a/src/org/kde/kdeconnect/MyApplication.java b/src/org/kde/kdeconnect/MyApplication.java new file mode 100644 index 00000000..caabcb66 --- /dev/null +++ b/src/org/kde/kdeconnect/MyApplication.java @@ -0,0 +1,41 @@ +package org.kde.kdeconnect; + +import android.app.Application; + +import androidx.annotation.NonNull; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.ProcessLifecycleOwner; + +public class MyApplication extends Application { + private static class LifecycleObserver implements DefaultLifecycleObserver { + private boolean inForeground = false; + + @Override + public void onStart(@NonNull LifecycleOwner owner) { + inForeground = true; + } + + @Override + public void onStop(@NonNull LifecycleOwner owner) { + inForeground = false; + } + + boolean isInForeground() { + return inForeground; + } + } + + private static final LifecycleObserver foregroundTracker = new LifecycleObserver(); + + @Override + public void onCreate() { + super.onCreate(); + + ProcessLifecycleOwner.get().getLifecycle().addObserver(foregroundTracker); + } + + public static boolean isInForeground() { + return foregroundTracker.isInForeground(); + } +} diff --git a/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhoneActivity.java b/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhoneActivity.java index 562b5530..48fdc65f 100644 --- a/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhoneActivity.java +++ b/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhoneActivity.java @@ -19,47 +19,37 @@ */ package org.kde.kdeconnect.Plugins.FindMyPhonePlugin; -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.media.AudioManager; -import android.media.MediaPlayer; -import android.net.Uri; import android.os.Bundle; -import android.preference.PreferenceManager; -import android.provider.Settings; import android.util.Log; import android.view.Window; import android.view.WindowManager; +import androidx.appcompat.app.AppCompatActivity; + +import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.UserInterface.ThemeUtil; import org.kde.kdeconnect_tp.R; -public class FindMyPhoneActivity extends Activity { +public class FindMyPhoneActivity extends AppCompatActivity { + static final String EXTRA_DEVICE_ID = "deviceId"; - private MediaPlayer mediaPlayer; - private int previousVolume; - private AudioManager audioManager; - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - - if (mediaPlayer != null) { - // If this activity was already open and we received the ring packet again, just finish it - finish(); - } - // otherwise the activity will become active again - } + private String deviceId; + private FindMyPhonePlugin plugin; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + ThemeUtil.setUserPreferredTheme(this); setContentView(R.layout.activity_find_my_phone); - audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + if (!getIntent().hasExtra(EXTRA_DEVICE_ID)) { + Log.e("FindMyPhoneActivity", "You must include the deviceId for which this activity is started as an intent EXTRA"); + finish(); + } + + deviceId = getIntent().getStringExtra(EXTRA_DEVICE_ID); + plugin = BackgroundService.getInstance().getDevice(deviceId).getPlugin(FindMyPhonePlugin.class); Window window = this.getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | @@ -72,42 +62,19 @@ public class FindMyPhoneActivity extends Activity { @Override protected void onStart() { super.onStart(); - - try { - // Make sure we are heard even when the phone is silent, restore original volume later - previousVolume = audioManager.getStreamVolume(AudioManager.STREAM_ALARM); - audioManager.setStreamVolume(AudioManager.STREAM_ALARM, audioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM), 0); - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - Uri ringtone; - String ringtoneString = prefs.getString(getString(R.string.findmyphone_preference_key_ringtone), ""); - if (ringtoneString.isEmpty()) { - ringtone = Settings.System.DEFAULT_RINGTONE_URI; - } else { - ringtone = Uri.parse(ringtoneString); - } - - mediaPlayer = new MediaPlayer(); - mediaPlayer.setDataSource(this, ringtone); - mediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); - mediaPlayer.setLooping(true); - mediaPlayer.prepare(); - mediaPlayer.start(); - - } catch (Exception e) { - Log.e("FindMyPhoneActivity", "Exception", e); - } - + /* + For whatever reason when Android launches this activity as a SystemAlertWindow it calls: + onCreate(), onStart(), onResume(), onStop(), onStart(), onResume(). + When using BackgroundService.RunWithPlugin we get into concurrency problems and sometimes no sound will be played + */ + plugin.startPlaying(); + plugin.hideNotification(); } @Override protected void onStop() { super.onStop(); - if (mediaPlayer != null) { - mediaPlayer.stop(); - } - audioManager.setStreamVolume(AudioManager.STREAM_ALARM, previousVolume, 0); + plugin.stopPlaying(); } - } diff --git a/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhonePlugin.java b/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhonePlugin.java index 2cc9ccda..c354e775 100644 --- a/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhonePlugin.java +++ b/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhonePlugin.java @@ -21,20 +21,46 @@ package org.kde.kdeconnect.Plugins.FindMyPhonePlugin; import android.app.Activity; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.net.Uri; +import android.os.Build; +import android.os.PowerManager; +import android.preference.PreferenceManager; +import android.provider.Settings; +import android.util.Log; + +import androidx.core.app.NotificationCompat; import org.kde.kdeconnect.Helpers.DeviceHelper; +import org.kde.kdeconnect.Helpers.NotificationHelper; +import org.kde.kdeconnect.MyApplication; import org.kde.kdeconnect.NetworkPacket; import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.Plugins.PluginFactory; import org.kde.kdeconnect.UserInterface.PluginSettingsFragment; import org.kde.kdeconnect_tp.R; +import java.io.IOException; + +import static android.content.Context.POWER_SERVICE; + @PluginFactory.LoadablePlugin public class FindMyPhonePlugin extends Plugin { - public final static String PACKET_TYPE_FINDMYPHONE_REQUEST = "kdeconnect.findmyphone.request"; + private NotificationManager notificationManager; + private int notificationId; + private AudioManager audioManager; + private MediaPlayer mediaPlayer; + private int previousVolume; + private PowerManager powerManager; + @Override public String getDisplayName() { switch (DeviceHelper.getDeviceType(context)) { @@ -55,13 +81,122 @@ public class FindMyPhonePlugin extends Plugin { } @Override - public boolean onPacketReceived(NetworkPacket np) { + public boolean onCreate() { + notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + notificationId = (int) System.currentTimeMillis(); + audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + powerManager = (PowerManager) context.getSystemService(POWER_SERVICE); + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + Uri ringtone; + String ringtoneString = prefs.getString(context.getString(R.string.findmyphone_preference_key_ringtone), ""); + if (ringtoneString.isEmpty()) { + ringtone = Settings.System.DEFAULT_RINGTONE_URI; + } else { + ringtone = Uri.parse(ringtoneString); + } + + try { + mediaPlayer = new MediaPlayer(); + mediaPlayer.setDataSource(context, ringtone); + //TODO: Deprecated use setAudioAttributes for API > 21 + mediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); + mediaPlayer.setLooping(true); + mediaPlayer.prepare(); + } catch (Exception e) { + Log.e("FindMyPhoneActivity", "Exception", e); + return false; + } - Intent intent = new Intent(context, FindMyPhoneActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); return true; + } + @Override + public void onDestroy() { + stopPlaying(); + audioManager = null; + mediaPlayer.release(); + mediaPlayer = null; + } + + @Override + public boolean onPacketReceived(NetworkPacket np) { + if (Build.VERSION.SDK_INT < 29 || MyApplication.isInForeground()) { + Intent intent = new Intent(context, FindMyPhoneActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(FindMyPhoneActivity.EXTRA_DEVICE_ID, device.getDeviceId()); + context.startActivity(intent); + } else { + if (powerManager.isInteractive()) { + startPlaying(); + showBroadcastNotification(); + } else { + showActivityNotification(); + } + } + + return true; + } + + private void showBroadcastNotification() { + Intent intent = new Intent(context, FindMyPhoneReceiver.class); + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + intent.setAction(FindMyPhoneReceiver.ACTION_FOUND_IT); + intent.putExtra(FindMyPhoneReceiver.EXTRA_DEVICE_ID, device.getDeviceId()); + + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + + createNotification(pendingIntent); + } + + private void showActivityNotification() { + Intent intent = new Intent(context, FindMyPhoneActivity.class); + intent.putExtra(FindMyPhoneActivity.EXTRA_DEVICE_ID, device.getDeviceId()); + + PendingIntent pi = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + createNotification(pi); + } + + private void createNotification(PendingIntent pendingIntent) { + NotificationCompat.Builder notification = new NotificationCompat.Builder(context, NotificationHelper.Channels.HIGHPRIORITY); + notification + .setSmallIcon(R.drawable.ic_notification) + .setOngoing(false) + .setFullScreenIntent(pendingIntent, true) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setAutoCancel(true) + .setContentTitle(context.getString(R.string.findmyphone_found)); + notification.setGroup("BackgroundService"); + + notificationManager.notify(notificationId, notification.build()); + } + + void startPlaying() { + if (!mediaPlayer.isPlaying()) { + // Make sure we are heard even when the phone is silent, restore original volume later + previousVolume = audioManager.getStreamVolume(AudioManager.STREAM_ALARM); + audioManager.setStreamVolume(AudioManager.STREAM_ALARM, audioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM), 0); + + mediaPlayer.start(); + } + } + + void hideNotification() { + notificationManager.cancel(notificationId); + } + + void stopPlaying() { + audioManager.setStreamVolume(AudioManager.STREAM_ALARM, previousVolume, 0); + mediaPlayer.stop(); + try { + mediaPlayer.prepare(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + boolean isPlaying() { + return mediaPlayer.isPlaying(); } @Override diff --git a/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhoneReceiver.java b/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhoneReceiver.java new file mode 100644 index 00000000..afea3961 --- /dev/null +++ b/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhoneReceiver.java @@ -0,0 +1,35 @@ +package org.kde.kdeconnect.Plugins.FindMyPhonePlugin; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import org.kde.kdeconnect.BackgroundService; + +public class FindMyPhoneReceiver extends BroadcastReceiver { + final static String ACTION_FOUND_IT = "org.kde.kdeconnect.Plugins.FindMyPhonePlugin.foundIt"; + final static String EXTRA_DEVICE_ID = "deviceId"; + + @Override + public void onReceive(Context context, Intent intent) { + switch (intent.getAction()) { + case ACTION_FOUND_IT: + foundIt(context, intent); + break; + default: + Log.d("ShareBroadcastReceiver", "Unhandled Action received: " + intent.getAction()); + } + } + + private void foundIt(Context context, Intent intent) { + if (!intent.hasExtra(EXTRA_DEVICE_ID)) { + Log.e("FindMyPhoneReceiver", "foundIt() - deviceId extra is not present, ignoring"); + return; + } + + String deviceId = intent.getStringExtra(EXTRA_DEVICE_ID); + + BackgroundService.RunWithPlugin(context, deviceId, FindMyPhonePlugin.class, FindMyPhonePlugin::stopPlaying); + } +}