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 }