File indexing completed on 2024-12-22 04:41:39

0001 /*
0002  * SPDX-FileCopyrightText: 2015 David Edmundson <david@davidedmundson.co.uk>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 package org.kde.kdeconnect.Plugins.FindMyPhonePlugin;
0008 
0009 import android.Manifest;
0010 import android.app.Activity;
0011 import android.app.NotificationManager;
0012 import android.app.PendingIntent;
0013 import android.content.Intent;
0014 import android.content.SharedPreferences;
0015 import android.media.AudioAttributes;
0016 import android.media.AudioManager;
0017 import android.media.MediaPlayer;
0018 import android.net.Uri;
0019 import android.os.Build;
0020 import android.os.PowerManager;
0021 import android.preference.PreferenceManager;
0022 import android.provider.Settings;
0023 import android.util.Log;
0024 
0025 import androidx.annotation.NonNull;
0026 import androidx.core.app.NotificationCompat;
0027 import androidx.core.content.ContextCompat;
0028 
0029 import org.apache.commons.lang3.ArrayUtils;
0030 import org.kde.kdeconnect.Helpers.DeviceHelper;
0031 import org.kde.kdeconnect.Helpers.LifecycleHelper;
0032 import org.kde.kdeconnect.Helpers.NotificationHelper;
0033 import org.kde.kdeconnect.NetworkPacket;
0034 import org.kde.kdeconnect.Plugins.Plugin;
0035 import org.kde.kdeconnect.Plugins.PluginFactory;
0036 import org.kde.kdeconnect.UserInterface.PluginSettingsFragment;
0037 import org.kde.kdeconnect_tp.R;
0038 
0039 import java.io.IOException;
0040 
0041 @PluginFactory.LoadablePlugin
0042 public class FindMyPhonePlugin extends Plugin {
0043     public final static String PACKET_TYPE_FINDMYPHONE_REQUEST = "kdeconnect.findmyphone.request";
0044 
0045     private NotificationManager notificationManager;
0046     private int notificationId;
0047     private AudioManager audioManager;
0048     private MediaPlayer mediaPlayer;
0049     private int previousVolume = -1;
0050     private PowerManager powerManager;
0051 
0052     @Override
0053     public @NonNull String getDisplayName() {
0054         switch (DeviceHelper.getDeviceType(context)) {
0055             case TV:
0056                 return context.getString(R.string.findmyphone_title_tv);
0057             case TABLET:
0058                 return context.getString(R.string.findmyphone_title_tablet);
0059             case PHONE:
0060             default:
0061                 return context.getString(R.string.findmyphone_title);
0062         }
0063     }
0064 
0065     @Override
0066     public @NonNull String getDescription() {
0067         return context.getString(R.string.findmyphone_description);
0068     }
0069 
0070     @Override
0071     public boolean onCreate() {
0072         notificationManager = ContextCompat.getSystemService(context, NotificationManager.class);
0073         notificationId = (int) System.currentTimeMillis();
0074         audioManager = ContextCompat.getSystemService(context, AudioManager.class);
0075         powerManager = ContextCompat.getSystemService(context, PowerManager.class);
0076 
0077         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
0078         Uri ringtone;
0079         String ringtoneString = prefs.getString(context.getString(R.string.findmyphone_preference_key_ringtone), "");
0080         if (ringtoneString.isEmpty()) {
0081             ringtone = Settings.System.DEFAULT_RINGTONE_URI;
0082         } else {
0083             ringtone = Uri.parse(ringtoneString);
0084         }
0085 
0086         try {
0087             mediaPlayer = new MediaPlayer();
0088             mediaPlayer.setDataSource(context, ringtone);
0089             AudioAttributes audioAttributes = new AudioAttributes.Builder()
0090                 .setUsage(AudioAttributes.USAGE_ALARM)
0091                 .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
0092                 .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
0093                 .build();
0094             mediaPlayer.setWakeMode(context, PowerManager.SCREEN_DIM_WAKE_LOCK); // Prevent screen turning off, requires WAKE_LOCK permission
0095             mediaPlayer.setAudioAttributes(audioAttributes);
0096             mediaPlayer.setLooping(true);
0097             mediaPlayer.prepare();
0098         } catch (Exception e) {
0099             Log.e("FindMyPhoneActivity", "Exception", e);
0100             return false;
0101         }
0102 
0103         return true;
0104     }
0105 
0106     @Override
0107     public void onDestroy() {
0108         if (mediaPlayer.isPlaying()) {
0109             stopPlaying();
0110         }
0111         audioManager = null;
0112         mediaPlayer.release();
0113         mediaPlayer = null;
0114     }
0115 
0116     @Override
0117     public boolean onPacketReceived(@NonNull NetworkPacket np) {
0118         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || LifecycleHelper.isInForeground()) {
0119             Intent intent = new Intent(context, FindMyPhoneActivity.class);
0120             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
0121             intent.putExtra(FindMyPhoneActivity.EXTRA_DEVICE_ID, device.getDeviceId());
0122             context.startActivity(intent);
0123         } else {
0124             if (!checkOptionalPermissions()) {
0125                 return false;
0126             }
0127             if (powerManager.isInteractive()) {
0128                 startPlaying();
0129                 showBroadcastNotification();
0130             } else {
0131                 showActivityNotification();
0132             }
0133         }
0134         return true;
0135     }
0136 
0137     private void showBroadcastNotification() {
0138         Intent intent = new Intent(context, FindMyPhoneReceiver.class);
0139         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
0140         intent.setAction(FindMyPhoneReceiver.ACTION_FOUND_IT);
0141         intent.putExtra(FindMyPhoneReceiver.EXTRA_DEVICE_ID, device.getDeviceId());
0142 
0143         PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
0144 
0145         createNotification(pendingIntent);
0146     }
0147 
0148     private void showActivityNotification() {
0149         Intent intent = new Intent(context, FindMyPhoneActivity.class);
0150         intent.putExtra(FindMyPhoneActivity.EXTRA_DEVICE_ID, device.getDeviceId());
0151 
0152         PendingIntent pi = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
0153         createNotification(pi);
0154     }
0155 
0156     private void createNotification(PendingIntent pendingIntent) {
0157         NotificationCompat.Builder notification = new NotificationCompat.Builder(context, NotificationHelper.Channels.HIGHPRIORITY);
0158         notification
0159                 .setSmallIcon(R.drawable.ic_notification)
0160                 .setOngoing(false)
0161                 .setFullScreenIntent(pendingIntent, true)
0162                 .setPriority(NotificationCompat.PRIORITY_HIGH)
0163                 .setAutoCancel(true)
0164                 .setOngoing(true)
0165                 .setContentTitle(context.getString(R.string.findmyphone_found));
0166         notification.setGroup("BackgroundService");
0167 
0168         notificationManager.notify(notificationId, notification.build());
0169     }
0170 
0171     void startPlaying() {
0172         if (mediaPlayer != null && !mediaPlayer.isPlaying()) {
0173             // Make sure we are heard even when the phone is silent, restore original volume later
0174             previousVolume = audioManager.getStreamVolume(AudioManager.STREAM_ALARM);
0175             audioManager.setStreamVolume(AudioManager.STREAM_ALARM, audioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM), 0);
0176 
0177             mediaPlayer.start();
0178         }
0179     }
0180 
0181     void hideNotification() {
0182         notificationManager.cancel(notificationId);
0183     }
0184 
0185     void stopPlaying() {
0186         if (audioManager == null) {
0187             // The Plugin was destroyed (probably the device disconnected)
0188             return;
0189         }
0190 
0191         if (previousVolume != -1) {
0192             audioManager.setStreamVolume(AudioManager.STREAM_ALARM, previousVolume, 0);
0193         }
0194         mediaPlayer.stop();
0195         try {
0196             mediaPlayer.prepare();
0197         } catch (IOException e) {
0198             e.printStackTrace();
0199         }
0200     }
0201 
0202     boolean isPlaying() {
0203         return mediaPlayer.isPlaying();
0204     }
0205 
0206     @Override
0207     public @NonNull String[] getSupportedPacketTypes() {
0208         return new String[]{PACKET_TYPE_FINDMYPHONE_REQUEST};
0209     }
0210 
0211     @Override
0212     public @NonNull String[] getOutgoingPacketTypes() {
0213         return ArrayUtils.EMPTY_STRING_ARRAY;
0214     }
0215 
0216     @Override
0217     public boolean hasSettings() {
0218         return true;
0219     }
0220 
0221     @Override
0222     public PluginSettingsFragment getSettingsFragment(Activity activity) {
0223         return FindMyPhoneSettingsFragment.newInstance(getPluginKey(), R.xml.findmyphoneplugin_preferences);
0224     }
0225 
0226     @NonNull
0227     @Override
0228     protected String[] getRequiredPermissions() {
0229         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
0230             return new String[]{Manifest.permission.POST_NOTIFICATIONS};
0231         } else {
0232             return ArrayUtils.EMPTY_STRING_ARRAY;
0233         }
0234     }
0235 
0236     @Override
0237     protected int getPermissionExplanation() {
0238         return R.string.findmyphone_notifications_explanation;
0239     }
0240 }