From 7d1de4a5e843f9058da1438d04a32412ff857b3e Mon Sep 17 00:00:00 2001 From: Ganblejs Date: Sun, 9 Oct 2022 14:53:04 +0200 Subject: [PATCH] Bangle.js: Bump flavor targetSdkVersion to 31 This also touches parts of the app not only used for bangle.js. E.g. pending intents gets new flags from SDK 23 inclusive. Bluetooth permissions are updated to work on SDK 31. Permission handling is updated to the new way for doing it with introduction of a new function. This is called for newer sdk versions. bump Bangle.js flavor targetSdkVersion to 31 update comments re SDK 31 set the 'exported=true' I introduced to false instead - except for three places add uses-permission for handling bluetooth in order to work on api >30 add if-blocks adding FLAG_IMMUTABLE to PendingIntents on api >30 add link to bluetooth documentation Add comment to banglejs manifest. Add requirement annotation to ControlCenterv bump compileSdkVersion to 31 add "OpenAppSettings" permission popup while working out individual permission popups on android 13 if SDK < 31 do permissions one by one, else send user to app info page to switch permissions manually working solution, but needs cleaning do some cleaning, not done though remove some logging remove import Log tweak and remove toasts in new permissions handling Change conditions `> Build.VERSION_CODES.Q` to `>= Build.VERSION_CODES.R` matching the style used everywhere else Revert "Change conditions `> Build.VERSION_CODES.Q` to `>= Build.VERSION_CODES.R` matching the style used everywhere else" This reverts commit 2929629ff43fbb685eb3d15e42459f321f68fa11. Revert "add if-blocks adding FLAG_IMMUTABLE to PendingIntents on api >30" This reverts commit ed8e1df7bb8b71fee745fbf9d10747d47c8f6cb8. Pending intents gets `PendingIntent.FLAG_IMMUTABLE` if `(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)`. Bangle.js: undo `@RequiresApi` code R ... to remove error in Android Studio where declared required api was higher then minSDK version. Use FLAG_MUTABLE for reply to test notification This should fix Gadgetbridge crashing when replying to the test notification from the debug activity. As reported here: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/2924#issuecomment-917282 Change to use FLAG_IMMUTABLE/_MUTABLE from SDK 23 ... as suggested by Android Studio. This is supposed to make the app more secure by not allowing certain changes to pending intents where they are not expected. If I understood correctly. Add PendingIntentUtils class to manage mutability --- app/build.gradle | 8 ++- app/src/banglejs/AndroidManifest.xml | 30 ++++++++ app/src/main/AndroidManifest.xml | 39 ++++++---- .../gadgetbridge/SleepAlarmWidget.java | 9 +-- .../freeyourgadget/gadgetbridge/Widget.java | 12 ++-- .../activities/ControlCenterv2.java | 72 ++++++++++++++++--- .../activities/DebugActivity.java | 7 +- .../database/PeriodicExporter.java | 4 +- .../externalevents/AlarmReceiver.java | 4 +- .../service/AbstractDeviceSupport.java | 9 +-- .../AutoConnectIntervalReceiver.java | 3 +- .../freeyourgadget/gadgetbridge/util/GB.java | 33 ++++----- .../gadgetbridge/util/PendingIntentUtils.java | 61 ++++++++++++++++ 13 files changed, 230 insertions(+), 61 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PendingIntentUtils.java diff --git a/app/build.gradle b/app/build.gradle index c86b26426..4fd3a5630 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -46,7 +46,7 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } - compileSdkVersion 29 + compileSdkVersion 31 buildToolsVersion "31.0.0" defaultConfig { @@ -101,8 +101,8 @@ android { resValue "string", "about_activity_title", "@string/about_activity_title_banglejs_main" resValue "string", "about_description", "@string/about_description_banglejs_main" resValue "string", "gadgetbridge_running", "@string/gadgetbridge_running_banglejs_main" - targetSdkVersion 30 // Bangle.js flavor only - We need SDK 30 for play store - // Note: app/src/banglejs/AndroidManifest.xml contains some extra permissions we need to make SDK 30 work + targetSdkVersion 31 // Bangle.js flavor only - We need SDK 31 for updates pushed to Play Store from 2022-11-01 + // Note: app/src/banglejs/AndroidManifest.xml contains some extra permissions we need to make SDK 30 and up work } } @@ -223,6 +223,8 @@ dependencies { implementation "androidx.legacy:legacy-support-v4:1.0.0" implementation "androidx.gridlayout:gridlayout:1.0.0" implementation "androidx.palette:palette:1.0.0" + implementation "androidx.activity:activity:1.4.0" + implementation "androidx.fragment:fragment:1.4.0" implementation "com.google.android.material:material:1.4.0" implementation 'com.google.android.flexbox:flexbox:3.0.0' diff --git a/app/src/banglejs/AndroidManifest.xml b/app/src/banglejs/AndroidManifest.xml index f285bad1f..525452a9e 100644 --- a/app/src/banglejs/AndroidManifest.xml +++ b/app/src/banglejs/AndroidManifest.xml @@ -12,4 +12,34 @@ --> + + + + + + + + + + + + + + + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d7f79c804..795931d76 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -77,7 +77,8 @@ android:name=".activities.ControlCenterv2" android:label="@string/title_activity_controlcenter" android:theme="@style/SplashTheme" - android:launchMode="singleTop"> + android:launchMode="singleTop" + android:exported="true"> @@ -134,7 +135,8 @@ + android:parentActivityName=".activities.ControlCenterv2" + android:exported="true"> @@ -355,7 +357,8 @@ + android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" + android:exported="false"> @@ -368,7 +371,8 @@ + android:enabled="true" + android:exported="false"> @@ -376,13 +380,15 @@ + android:enabled="true" + android:exported="false"> - + @@ -390,7 +396,8 @@ + android:permission="android.permission.RECEIVE_BOOT_COMPLETED" + android:exported="false"> @@ -583,7 +590,8 @@ + android:label="@string/appwidget_sleep_alarm_widget_label" + android:exported="false"> @@ -595,7 +603,8 @@ + android:label="@string/widget_listing_label" + android:exported="false"> @@ -614,13 +623,15 @@ android:launchMode="singleInstance" android:theme="@style/Theme.AppCompat.Light.Dialog" /> - + - + @@ -632,7 +643,8 @@ android:clearTaskOnLaunch="true" android:label="@string/app_configure" android:launchMode="singleTask" - android:parentActivityName=".activities.appmanager.AppManagerActivity"> + android:parentActivityName=".activities.appmanager.AppManagerActivity" + android:exported="true"> @@ -719,7 +731,8 @@ android:name=".activities.GpxReceiverActivity" android:label="@string/gpx_receiver_activity_title" android:screenOrientation="portrait" - android:windowSoftInputMode="stateHidden"> + android:windowSoftInputMode="stateHidden" + android:exported="false"> diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/SleepAlarmWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/SleepAlarmWidget.java index 9ce53d3e5..33c817012 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/SleepAlarmWidget.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/SleepAlarmWidget.java @@ -38,6 +38,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; +import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils; import nodomain.freeyourgadget.gadgetbridge.util.WidgetPreferenceStorage; /** @@ -62,8 +63,8 @@ public class SleepAlarmWidget extends AppWidgetProvider { Intent intent = new Intent(context, SleepAlarmWidget.class); intent.setAction(ACTION_CLICK); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); - PendingIntent clickPI = PendingIntent.getBroadcast( - context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent clickPI = PendingIntentUtils.getBroadcast( + context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT, false); views.setOnClickPendingIntent(R.id.sleepalarmwidget_text, clickPI); // Instruct the widget manager to update the widget @@ -147,8 +148,8 @@ public class SleepAlarmWidget extends AppWidgetProvider { AlarmManager am = (AlarmManager) packageContext.getSystemService(Context.ALARM_SERVICE); // TODO: launch the alarm configuration activity when clicking the alarm in the status bar Intent intent = new Intent(packageContext, ConfigureAlarms.class); - PendingIntent pi = PendingIntent.getBroadcast(packageContext, 0, intent, - PendingIntent.FLAG_CANCEL_CURRENT); + PendingIntent pi = PendingIntentUtils.getBroadcast(packageContext, 0, intent, + PendingIntent.FLAG_CANCEL_CURRENT, false); am.setAlarmClock(new AlarmManager.AlarmClockInfo(triggerTime, pi), pi); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Widget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Widget.java index b820a146c..4ea91e834 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Widget.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Widget.java @@ -42,6 +42,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.Build; import android.os.Bundle; import android.view.View; import android.widget.RemoteViews; @@ -67,6 +68,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; import nodomain.freeyourgadget.gadgetbridge.util.FormatUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; +import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils; import nodomain.freeyourgadget.gadgetbridge.util.WidgetPreferenceStorage; public class Widget extends AppWidgetProvider { @@ -108,25 +110,25 @@ public class Widget extends AppWidgetProvider { Intent intent = new Intent(context, Widget.class); intent.setAction(WIDGET_CLICK); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); - PendingIntent refreshDataIntent = PendingIntent.getBroadcast( - context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent refreshDataIntent = PendingIntentUtils.getBroadcast( + context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT, false); views.setOnClickPendingIntent(R.id.todaywidget_header_container, refreshDataIntent); //open GB main window Intent startMainIntent = new Intent(context, ControlCenterv2.class); - PendingIntent startMainPIntent = PendingIntent.getActivity(context, 0, startMainIntent, 0); + PendingIntent startMainPIntent = PendingIntentUtils.getActivity(context, 0, startMainIntent, 0, false); views.setOnClickPendingIntent(R.id.todaywidget_header_icon, startMainPIntent); //alarms popup menu Intent startAlarmListIntent = new Intent(context, WidgetAlarmsActivity.class); startAlarmListIntent.putExtra(GBDevice.EXTRA_DEVICE, deviceForWidget); - PendingIntent startAlarmListPIntent = PendingIntent.getActivity(context, appWidgetId, startAlarmListIntent, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent startAlarmListPIntent = PendingIntentUtils.getActivity(context, appWidgetId, startAlarmListIntent, PendingIntent.FLAG_UPDATE_CURRENT, false); views.setOnClickPendingIntent(R.id.todaywidget_header_alarm_icon, startAlarmListPIntent); //charts Intent startChartsIntent = new Intent(context, ChartsActivity.class); startChartsIntent.putExtra(GBDevice.EXTRA_DEVICE, deviceForWidget); - PendingIntent startChartsPIntent = PendingIntent.getActivity(context, appWidgetId, startChartsIntent, PendingIntent.FLAG_CANCEL_CURRENT); + PendingIntent startChartsPIntent = PendingIntentUtils.getActivity(context, appWidgetId, startChartsIntent, PendingIntent.FLAG_CANCEL_CURRENT, false); views.setOnClickPendingIntent(R.id.todaywidget_bottom_layout, startChartsPIntent); long[] dailyTotals = getSteps(deviceForWidget); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java index 26cdeb3ad..be89f5b18 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java @@ -32,13 +32,17 @@ import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.provider.Settings; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.view.MenuItem; import android.view.View; import android.widget.Toast; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatDelegate; @@ -284,7 +288,7 @@ public class ControlCenterv2 extends AppCompatActivity } } - if (!android.provider.Settings.canDrawOverlays(getApplicationContext())) { + if (!Settings.canDrawOverlays(getApplicationContext())) { // If diplay over other apps access hasn't been granted // Put up a dialog explaining why we need permissions (Polite, but also Play Store policy) // When accepted, we open the Activity for permission to display over other apps. @@ -294,6 +298,10 @@ public class ControlCenterv2 extends AppCompatActivity } } + + // Put up a dialog explaining why we need permissions (Polite, but also Play Store policy) + // When accepted, we open the Activity for permission to display over other apps. + // Check all the other permissions that we need to for Android M + later checkAndRequestPermissions(true); } @@ -478,12 +486,27 @@ public class ControlCenterv2 extends AppCompatActivity } } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Build.VERSION.SDK_INT <= Build.VERSION_CODES.S) { if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_DENIED) { wantedPermissions.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION); } } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.QUERY_ALL_PACKAGES) == PackageManager.PERMISSION_DENIED) { + wantedPermissions.add(Manifest.permission.QUERY_ALL_PACKAGES); + } + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_DENIED) { + wantedPermissions.add(Manifest.permission.BLUETOOTH_SCAN); + } + if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_DENIED) { + wantedPermissions.add(Manifest.permission.BLUETOOTH_CONNECT); + } + } + if (BuildConfig.INTERNET_ACCESS) { if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.INTERNET) == PackageManager.PERMISSION_DENIED) { wantedPermissions.add(Manifest.permission.INTERNET); @@ -512,22 +535,30 @@ public class ControlCenterv2 extends AppCompatActivity if (!wantedPermissions.isEmpty()) { if (showDialogFirst) { - // Show a dialog - thus will then call checkAndRequestPermissions(false) + // Show a dialog - this will then call checkAndRequestPermissions(false) DialogFragment dialog = new LocationPermissionsDialogFragment(); dialog.show(getSupportFragmentManager(), "LocationPermissionsDialogFragment"); + //requestMultiplePermissionsLauncher.launch(wantedPermissions.toArray(new String[0])); } else { GB.toast(this, getString(R.string.permission_granting_mandatory), Toast.LENGTH_LONG, GB.ERROR); - ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[0]), 0); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { + ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[0]), 0); + } else { + requestMultiplePermissionsLauncher.launch(wantedPermissions.toArray(new String[0])); + //ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[0]), 0); //Actually this still works if I test it, not sure if the new way is more reliable or not... + } } } } - // HACK: On Lineage we have to do this so that the permission dialog pops up - if (fakeStateListener == null) { - fakeStateListener = new PhoneStateListener(); - TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); - telephonyManager.listen(fakeStateListener, PhoneStateListener.LISTEN_CALL_STATE); - telephonyManager.listen(fakeStateListener, PhoneStateListener.LISTEN_NONE); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { // The enclosed hack in it's current state cause crash on Banglejs builds tarkgetSDK=31 on a Android 13 device. + // HACK: On Lineage we have to do this so that the permission dialog pops up + if (fakeStateListener == null) { + fakeStateListener = new PhoneStateListener(); + TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); + telephonyManager.listen(fakeStateListener, PhoneStateListener.LISTEN_CALL_STATE); + telephonyManager.listen(fakeStateListener, PhoneStateListener.LISTEN_NONE); + } } } @@ -583,6 +614,7 @@ public class ControlCenterv2 extends AppCompatActivity getContext().getString(R.string.app_name), getContext().getString(R.string.ok))) .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + @RequiresApi(api = Build.VERSION_CODES.M) public void onClick(DialogInterface dialog, int id) { try { startActivity(new Intent(android.provider.Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS)); @@ -629,6 +661,7 @@ public class ControlCenterv2 extends AppCompatActivity getContext().getString(R.string.app_name), getContext().getString(R.string.ok))) .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + @RequiresApi(api = Build.VERSION_CODES.M) public void onClick(DialogInterface dialog, int id) { Intent enableIntent = new Intent(android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION); startActivity(enableIntent); @@ -640,6 +673,7 @@ public class ControlCenterv2 extends AppCompatActivity } } + /// Called from checkAndRequestPermissions - this puts up a dialog explaining we need permissions, and then calls checkAndRequestPermissions (via an intent) when 'ok' pressed public static class LocationPermissionsDialogFragment extends DialogFragment { ControlCenterv2 controlCenter; @@ -661,4 +695,22 @@ public class ControlCenterv2 extends AppCompatActivity return builder.create(); } } + + // Register the permissions callback, which handles the user's response to the + // system permissions dialog. Save the return value, an instance of + // ActivityResultLauncher, as an instance variable. + public ActivityResultLauncher requestMultiplePermissionsLauncher = + registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), isGranted -> { + if (isGranted.containsValue(true)) { + // Permission is granted. Continue the action or workflow in your + // app. + } else { + // Explain to the user that the feature is unavailable because the + // feature requires a permission that the user has denied. At the + // same time, respect the user's decision. Don't link to system + // settings in an effort to convince the user to change their + // decision. + GB.toast(this, getString(R.string.permission_granting_mandatory), Toast.LENGTH_LONG, GB.ERROR); + } + }); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java index 7f39d39d8..d113f641b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java @@ -116,6 +116,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.GB; +import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; import nodomain.freeyourgadget.gadgetbridge.util.WidgetPreferenceStorage; @@ -915,15 +916,15 @@ public class DebugActivity extends AbstractGBActivity { Intent notificationIntent = new Intent(getApplicationContext(), DebugActivity.class); notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, - notificationIntent, 0); + PendingIntent pendingIntent = PendingIntentUtils.getActivity(getApplicationContext(), 0, + notificationIntent, 0, false); RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_REPLY) .build(); Intent replyIntent = new Intent(ACTION_REPLY); - PendingIntent replyPendingIntent = PendingIntent.getBroadcast(this, 0, replyIntent, 0); + PendingIntent replyPendingIntent = PendingIntentUtils.getBroadcast(this, 0, replyIntent, 0, true); NotificationCompat.Action action = new NotificationCompat.Action.Builder(android.R.drawable.ic_input_add, "Reply", replyPendingIntent) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/PeriodicExporter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/PeriodicExporter.java index fb3ac9610..36861f5a2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/PeriodicExporter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/PeriodicExporter.java @@ -22,6 +22,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.os.Build; import android.os.SystemClock; import org.slf4j.Logger; @@ -33,6 +34,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs; +import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; /** @@ -56,7 +58,7 @@ public class PeriodicExporter extends BroadcastReceiver { public static void scheduleAlarm(Context context, Integer autoExportInterval, boolean autoExportEnabled) { Intent i = new Intent(context, PeriodicExporter.class); - PendingIntent pi = PendingIntent.getBroadcast(context, 0, i, 0); + PendingIntent pi = PendingIntentUtils.getBroadcast(context, 0, i, 0, false); AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); am.cancel(pi); if (!autoExportEnabled) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AlarmReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AlarmReceiver.java index 1b408cf5b..f1a93f9c2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AlarmReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AlarmReceiver.java @@ -22,6 +22,7 @@ import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.os.Build; import net.e175.klaus.solarpositioning.DeltaT; import net.e175.klaus.solarpositioning.SPA; @@ -37,6 +38,7 @@ import nodomain.freeyourgadget.gadgetbridge.BuildConfig; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs; +import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils; public class AlarmReceiver extends BroadcastReceiver { private static final Logger LOG = LoggerFactory.getLogger(AlarmReceiver.class); @@ -45,7 +47,7 @@ public class AlarmReceiver extends BroadcastReceiver { Context context = GBApplication.getContext(); Intent intent = new Intent("DAILY_ALARM"); intent.setPackage(BuildConfig.APPLICATION_ID); - PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0); + PendingIntent pendingIntent = PendingIntentUtils.getBroadcast(context, 0, intent, 0, false); AlarmManager am = (AlarmManager) (context.getSystemService(Context.ALARM_SERVICE)); if (am != null) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java index 52f15f1cd..a970ab721 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java @@ -95,6 +95,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.NavigationInfoSpec; import nodomain.freeyourgadget.gadgetbridge.service.receivers.GBCallControlReceiver; import nodomain.freeyourgadget.gadgetbridge.service.receivers.GBMusicControlReceiver; import nodomain.freeyourgadget.gadgetbridge.util.GB; +import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils; import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID; import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_CHANNEL_ID; @@ -253,7 +254,7 @@ public abstract class AbstractDeviceSupport implements DeviceSupport { intent.putExtra(FindPhoneActivity.EXTRA_RING, ring); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - PendingIntent pi = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent pi = PendingIntentUtils.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT, false); NotificationCompat.Builder notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID ) .setSmallIcon(R.drawable.ic_notification) @@ -389,14 +390,14 @@ public abstract class AbstractDeviceSupport implements DeviceSupport { Uri screenshotURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".screenshot_provider", new File(fullpath)); intent.setDataAndType(screenshotURI, "image/*"); - PendingIntent pIntent = PendingIntent.getActivity(context, 0, intent, 0); + PendingIntent pIntent = PendingIntentUtils.getActivity(context, 0, intent, 0, false); Intent shareIntent = new Intent(Intent.ACTION_SEND); shareIntent.setType("image/*"); shareIntent.putExtra(Intent.EXTRA_STREAM, screenshotURI); - PendingIntent pendingShareIntent = PendingIntent.getActivity(context, 0, Intent.createChooser(shareIntent, "share screenshot"), - PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent pendingShareIntent = PendingIntentUtils.getActivity(context, 0, Intent.createChooser(shareIntent, "share screenshot"), + PendingIntent.FLAG_UPDATE_CURRENT, false); NotificationCompat.Action action = new NotificationCompat.Action.Builder(android.R.drawable.ic_menu_share, "share", pendingShareIntent).build(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/AutoConnectIntervalReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/AutoConnectIntervalReceiver.java index a425fb0d9..dbfc5b860 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/AutoConnectIntervalReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/AutoConnectIntervalReceiver.java @@ -36,6 +36,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; +import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils; public class AutoConnectIntervalReceiver extends BroadcastReceiver { @@ -101,7 +102,7 @@ public class AutoConnectIntervalReceiver extends BroadcastReceiver { AlarmManager am = (AlarmManager) (GBApplication.getContext().getSystemService(Context.ALARM_SERVICE)); Intent intent = new Intent("GB_RECONNECT"); intent.setPackage(BuildConfig.APPLICATION_ID); - PendingIntent pendingIntent = PendingIntent.getBroadcast(GBApplication.getContext(), 0, intent, 0); + PendingIntent pendingIntent = PendingIntentUtils.getBroadcast(GBApplication.getContext(), 0, intent, 0, false); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { am.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, Calendar.getInstance(). getTimeInMillis() + delay * 1000, pendingIntent); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java index 4b850a78e..edcf372ae 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java @@ -27,6 +27,7 @@ import android.bluetooth.BluetoothAdapter; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.os.Build; import android.os.Handler; import android.os.Looper; import android.text.Html; @@ -146,8 +147,8 @@ public class GB { Intent notificationIntent = new Intent(context, ControlCenterv2.class); notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, - notificationIntent, 0); + PendingIntent pendingIntent = PendingIntentUtils.getActivity(context, 0, + notificationIntent, 0, false); return pendingIntent; } @@ -188,18 +189,18 @@ public class GB { Intent deviceCommunicationServiceIntent = new Intent(context, DeviceCommunicationService.class); if (connected) { deviceCommunicationServiceIntent.setAction(DeviceService.ACTION_DISCONNECT); - PendingIntent disconnectPendingIntent = PendingIntent.getService(context, 0, deviceCommunicationServiceIntent, PendingIntent.FLAG_ONE_SHOT); + PendingIntent disconnectPendingIntent = PendingIntentUtils.getService(context, 0, deviceCommunicationServiceIntent, PendingIntent.FLAG_ONE_SHOT, false); builder.addAction(R.drawable.ic_notification_disconnected, context.getString(R.string.controlcenter_disconnect), disconnectPendingIntent); if (DeviceHelper.getInstance().getCoordinator(device).supportsActivityDataFetching()) { deviceCommunicationServiceIntent.setAction(DeviceService.ACTION_FETCH_RECORDED_DATA); deviceCommunicationServiceIntent.putExtra(EXTRA_RECORDED_DATA_TYPES, ActivityKind.TYPE_ACTIVITY); - PendingIntent fetchPendingIntent = PendingIntent.getService(context, 1, deviceCommunicationServiceIntent, PendingIntent.FLAG_ONE_SHOT); + PendingIntent fetchPendingIntent = PendingIntentUtils.getService(context, 1, deviceCommunicationServiceIntent, PendingIntent.FLAG_ONE_SHOT, false); builder.addAction(R.drawable.ic_refresh, context.getString(R.string.controlcenter_fetch_activity_data), fetchPendingIntent); } } else if (device.getState().equals(GBDevice.State.WAITING_FOR_RECONNECT) || device.getState().equals(GBDevice.State.NOT_CONNECTED)) { deviceCommunicationServiceIntent.setAction(DeviceService.ACTION_CONNECT); deviceCommunicationServiceIntent.putExtra(GBDevice.EXTRA_DEVICE, device); - PendingIntent reconnectPendingIntent = PendingIntent.getService(context, 2, deviceCommunicationServiceIntent, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent reconnectPendingIntent = PendingIntentUtils.getService(context, 2, deviceCommunicationServiceIntent, PendingIntent.FLAG_UPDATE_CURRENT, false); builder.addAction(R.drawable.ic_notification, context.getString(R.string.controlcenter_connect), reconnectPendingIntent); } }else{ @@ -243,7 +244,7 @@ public class GB { Intent deviceCommunicationServiceIntent = new Intent(context, DeviceCommunicationService.class); deviceCommunicationServiceIntent.setAction(DeviceService.ACTION_FETCH_RECORDED_DATA); deviceCommunicationServiceIntent.putExtra(EXTRA_RECORDED_DATA_TYPES, ActivityKind.TYPE_ACTIVITY); - PendingIntent fetchPendingIntent = PendingIntent.getService(context, 1, deviceCommunicationServiceIntent, PendingIntent.FLAG_ONE_SHOT); + PendingIntent fetchPendingIntent = PendingIntentUtils.getService(context, 1, deviceCommunicationServiceIntent, PendingIntent.FLAG_ONE_SHOT, false); builder.addAction(R.drawable.ic_refresh, context.getString(R.string.controlcenter_fetch_activity_data), fetchPendingIntent); } } @@ -272,7 +273,7 @@ public class GB { if (GBApplication.getPrefs().getString("last_device_address", null) != null) { Intent deviceCommunicationServiceIntent = new Intent(context, DeviceCommunicationService.class); deviceCommunicationServiceIntent.setAction(DeviceService.ACTION_CONNECT); - PendingIntent reconnectPendingIntent = PendingIntent.getService(context, 2, deviceCommunicationServiceIntent, PendingIntent.FLAG_ONE_SHOT); + PendingIntent reconnectPendingIntent = PendingIntentUtils.getService(context, 2, deviceCommunicationServiceIntent, PendingIntent.FLAG_ONE_SHOT, false); builder.addAction(R.drawable.ic_notification, context.getString(R.string.controlcenter_connect), reconnectPendingIntent); } @@ -480,8 +481,8 @@ public class GB { Intent notificationIntent = new Intent(context, ControlCenterv2.class); notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, - notificationIntent, 0); + PendingIntent pendingIntent = PendingIntentUtils.getActivity(context, 0, + notificationIntent, 0, false); NotificationCompat.Builder nb = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID_TRANSFER) .setTicker((title == null) ? context.getString(R.string.app_name) : title) @@ -515,7 +516,7 @@ public class GB { public static void createGpsNotification(Context context, int numDevices) { Intent notificationIntent = new Intent(context, ControlCenterv2.class); notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0); + PendingIntent pendingIntent = PendingIntentUtils.getActivity(context, 0, notificationIntent, 0, false); NotificationCompat.Builder nb = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID_GPS) .setTicker(context.getString(R.string.notification_gps_title)) @@ -538,8 +539,8 @@ public class GB { Intent notificationIntent = new Intent(context, ControlCenterv2.class); notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, - notificationIntent, 0); + PendingIntent pendingIntent = PendingIntentUtils.getActivity(context, 0, + notificationIntent, 0, false); NotificationCompat.Builder nb = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) .setContentTitle(context.getString(R.string.app_name)) @@ -568,8 +569,8 @@ public class GB { Intent notificationIntent = new Intent(context, ControlCenterv2.class); notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, - notificationIntent, 0); + PendingIntent pendingIntent = PendingIntentUtils.getActivity(context, 0, + notificationIntent, 0, false); NotificationCompat.Builder nb = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID_LOW_BATTERY) .setContentTitle(context.getString(R.string.notif_battery_low_title)) @@ -602,8 +603,8 @@ public class GB { Intent notificationIntent = new Intent(context, SettingsActivity.class); notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, - notificationIntent, 0); + PendingIntent pendingIntent = PendingIntentUtils.getActivity(context, 0, + notificationIntent, 0, false); NotificationCompat.Builder nb = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) .setContentTitle(context.getString(R.string.notif_export_failed_title)) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PendingIntentUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PendingIntentUtils.java new file mode 100644 index 000000000..eaedfd0d9 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PendingIntentUtils.java @@ -0,0 +1,61 @@ +package nodomain.freeyourgadget.gadgetbridge.util; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Build; + +public class PendingIntentUtils { + + public static PendingIntent getBroadcast(Context context, + int requestCode, + Intent intent, + int flags, + boolean makeMutable) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (makeMutable) { + flags |= PendingIntent.FLAG_MUTABLE; + } else { + flags |= PendingIntent.FLAG_IMMUTABLE; + } + return PendingIntent.getBroadcast(context, requestCode, intent, flags); + } + + return PendingIntent.getBroadcast(context, requestCode, intent, flags); + } + + public static PendingIntent getActivity(Context context, + int requestCode, + Intent intent, + int flags, + boolean makeMutable) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (makeMutable) { + flags |= PendingIntent.FLAG_MUTABLE; + } else { + flags |= PendingIntent.FLAG_IMMUTABLE; + } + return PendingIntent.getActivity(context, requestCode, intent, flags); + } + + return PendingIntent.getActivity(context, requestCode, intent, flags); + } + + public static PendingIntent getService(Context context, + int requestCode, + Intent intent, + int flags, + boolean makeMutable) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (makeMutable) { + flags |= PendingIntent.FLAG_MUTABLE; + } else { + flags |= PendingIntent.FLAG_IMMUTABLE; + } + return PendingIntent.getService(context, requestCode, intent, flags); + } + + return PendingIntent.getService(context, requestCode, intent, flags); + } + +}