From 70755e0b8ce3379ac37dd8f623443da0bbe87b73 Mon Sep 17 00:00:00 2001 From: Petru Lingurar Date: Mon, 10 Jun 2019 15:52:58 +0000 Subject: [PATCH] Bug 1550291 - Android Q: Use the overlay permission or a foreground notification to start the crash handler; r=VladBaicu Android Q doesn't allow starting Activities from background. https://developer.android.com/preview/privacy/background-activity-starts We can either piggy-back the SYSTEM_ALERT_WINDOW permission given by the user for the Tab Queue functionality or use a foreground notification from where users could start `CrashReporterActivity`. Differential Revision: https://phabricator.services.mozilla.com/D33029 --HG-- extra : moz-landing-system : lando --- .../android/app/src/main/res/values/ids.xml | 1 + .../mozilla/gecko/CrashHandlerService.java | 91 ++++++++++++++++--- .../mozilla/gecko/CrashReporterActivity.java | 9 ++ .../base/locales/en-US/android_strings.dtd | 5 + mobile/android/base/strings.xml.in | 3 + 5 files changed, 98 insertions(+), 11 deletions(-) diff --git a/mobile/android/app/src/main/res/values/ids.xml b/mobile/android/app/src/main/res/values/ids.xml index f72cf13626c1..a1aada9b09e6 100644 --- a/mobile/android/app/src/main/res/values/ids.xml +++ b/mobile/android/app/src/main/res/values/ids.xml @@ -23,4 +23,5 @@ + diff --git a/mobile/android/base/java/org/mozilla/gecko/CrashHandlerService.java b/mobile/android/base/java/org/mozilla/gecko/CrashHandlerService.java index 442ab696445f..a8a4e7eddeb7 100644 --- a/mobile/android/base/java/org/mozilla/gecko/CrashHandlerService.java +++ b/mobile/android/base/java/org/mozilla/gecko/CrashHandlerService.java @@ -1,23 +1,92 @@ package org.mozilla.gecko; -import android.app.IntentService; +import android.annotation.TargetApi; +import android.app.Notification; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; import android.content.Intent; -import android.support.annotation.Nullable; +import android.os.IBinder; +import android.provider.Settings; +import org.mozilla.gecko.notifications.NotificationHelper; -public class CrashHandlerService extends IntentService { +public class CrashHandlerService extends Service { + private static final String ACTION_STOP = "action_stop"; + // Build.VERSION_CODES.Q placeholder. While Android Q is in Beta it shares API 28 with Android P. + private static final int ANDROID_Q = 29; - public CrashHandlerService() { - super("Crash Handler"); + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (ACTION_STOP.equals(intent.getAction())) { + dismissNotification(); + } else { + intent.setClass(this, CrashReporterActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + if (AppConstants.Versions.feature29Plus) { + startCrashHandling(intent); + } else { + startActivity(intent); + + // Avoid ANR due to background limitations on Oreo+ + System.exit(0); + } + } + + return Service.START_NOT_STICKY; } @Override - protected void onHandleIntent(@Nullable Intent intent) { - intent.setClass(this, CrashReporterActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); + public IBinder onBind(Intent intent) { + return null; + } - // Avoid ANR due to background limitations on Oreo+ - System.exit(0); + /** + * Call this for any necessary cleanup like removing the foreground notification shown on Android Q+. + */ + public static void reportingStarted(final Context context) { + if (AppConstants.Versions.feature29Plus) { + final Intent intent = new Intent(context, CrashHandlerService.class); + intent.setAction(ACTION_STOP); + context.startService(intent); + } + } + + @TargetApi(ANDROID_Q) + private Notification getActivityNotification(final Context context, final Intent activityIntent) { + final Intent dismissNotificationIntent = new Intent(ACTION_STOP, null, this, this.getClass()); + final PendingIntent dismissNotification = PendingIntent.getService(this, 0, dismissNotificationIntent, PendingIntent.FLAG_CANCEL_CURRENT); + final PendingIntent startReporterActivity = PendingIntent.getActivity(this, 0, activityIntent, 0); + final String notificationChannelId = NotificationHelper.getInstance(context) + .getNotificationChannel(NotificationHelper.Channel.CRASH_HANDLER).getId(); + + return new Notification.Builder(this, notificationChannelId) + .setSmallIcon(R.drawable.ic_status_logo) + .setContentTitle(getString(R.string.crash_notification_title)) + .setContentText(getString(R.string.crash_notification_message)) + .setDefaults(Notification.DEFAULT_ALL) + .setContentIntent(startReporterActivity) + .addAction(0, getString(R.string.crash_notification_negative_button_text), dismissNotification) + .build(); + } + + @TargetApi(ANDROID_Q) + private void dismissNotification() { + stopForeground(Service.STOP_FOREGROUND_REMOVE); + } + + @TargetApi(ANDROID_Q) + private void startCrashHandling(final Intent activityIntent) { + // Piggy-back the SYSTEM_ALERT_WINDOW permission given by the user for the Tab Queue functionality. + // Otherwise fallback to display a foreground notification, this being the only way we can + // start an activity from background. + // https://developer.android.com/preview/privacy/background-activity-starts#conditions-allow-activity-starts + if (Settings.canDrawOverlays(this)) { + startActivity(activityIntent); + } else { + final Notification notification = getActivityNotification(this, activityIntent); + startForeground(R.id.mediaControlNotification, notification); + } } } diff --git a/mobile/android/base/java/org/mozilla/gecko/CrashReporterActivity.java b/mobile/android/base/java/org/mozilla/gecko/CrashReporterActivity.java index d1d7e4bd6c7a..c4bcfbcd3b92 100644 --- a/mobile/android/base/java/org/mozilla/gecko/CrashReporterActivity.java +++ b/mobile/android/base/java/org/mozilla/gecko/CrashReporterActivity.java @@ -139,6 +139,8 @@ public class CrashReporterActivity extends AppCompatActivity @Override @SuppressLint("WrongThread") // We don't have a worker thread for the TelemetryDispatcher public void onCreate(Bundle savedInstanceState) { + informReporterStarted(); + super.onCreate(savedInstanceState); // mHandler is created here so runnables can be run on the main thread mHandler = new Handler(); @@ -596,4 +598,11 @@ public class CrashReporterActivity extends AppCompatActivity private String unescape(String string) { return string.replaceAll("\\\\\\\\", "\\").replaceAll("\\\\n", "\n").replaceAll("\\\\t", "\t"); } + + /** + * Inform other parts of the app that user started the crash reporting process. + */ + private void informReporterStarted() { + CrashHandlerService.reportingStarted(this); + } } diff --git a/mobile/android/base/locales/en-US/android_strings.dtd b/mobile/android/base/locales/en-US/android_strings.dtd index c51ea862ebf6..43ebb908c73f 100644 --- a/mobile/android/base/locales/en-US/android_strings.dtd +++ b/mobile/android/base/locales/en-US/android_strings.dtd @@ -68,6 +68,11 @@ + + + + diff --git a/mobile/android/base/strings.xml.in b/mobile/android/base/strings.xml.in index 0774178e21e5..ef7468484272 100644 --- a/mobile/android/base/strings.xml.in +++ b/mobile/android/base/strings.xml.in @@ -71,6 +71,9 @@ &sending_crash_report; &crash_close_label; &crash_restart_label; + &crash_notification_title; + &crash_notification_message; + &crash_notification_negative_button_text; &url_bar_default_text2; &url_bar_qrcode_text2;