Bug 1531047 - Part 2: Refactor TabQueue to a foreground service. r=JanH
authorVlad Baicu <vlad.baicu@softvision.ro>
Fri, 15 Mar 2019 11:21:29 +0000
changeset 522030 36132f5347e8b31f4fe5b28d0bd182f6fe606237
parent 522029 069dea6b6bc5b0b4bc43d00e3b6e55c7b25ae245
child 522031 ad59be7ee0c20a42f70989bcce84308fdd66619a
push id10871
push usercbrindusan@mozilla.com
push dateMon, 18 Mar 2019 15:49:32 +0000
treeherdermozilla-beta@018abdd16060 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersJanH
bugs1531047
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1531047 - Part 2: Refactor TabQueue to a foreground service. r=JanH Refactored the TabQueueService to be a foreground service from Android O onwards. The service now uses a foreground notification that briefly informs the user that a new tab is being added to the queue. Depends on D23528 Differential Revision: https://phabricator.services.mozilla.com/D23529
mobile/android/base/java/org/mozilla/gecko/LauncherActivity.java
mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueHelper.java
mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueService.java
mobile/android/base/locales/en-US/android_strings.dtd
mobile/android/base/strings.xml.in
--- a/mobile/android/base/java/org/mozilla/gecko/LauncherActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/LauncherActivity.java
@@ -1,15 +1,16 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
+import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.appwidget.AppWidgetManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Build;
@@ -103,20 +104,25 @@ public class LauncherActivity extends Ac
         intent.setClassName(getApplicationContext(), AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
         intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
         startActivity(intent);
     }
 
     /**
      * Launch tab queue service to display overlay.
      */
+    @SuppressLint("NewApi")
     private void dispatchTabQueueIntent() {
         Intent intent = new Intent(getIntent());
         intent.setClass(getApplicationContext(), TabQueueService.class);
-        startService(intent);
+        if (AppConstants.Versions.preO) {
+            startService(intent);
+        } else {
+            startForegroundService(intent);
+        }
     }
 
     /**
      * Launch the browser activity.
      */
     private void dispatchNormalIntent() {
         Intent intent = new Intent(getIntent());
         intent.setClassName(getApplicationContext(), AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
--- a/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueHelper.java
@@ -1,22 +1,25 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.tabqueue;
 
+import android.annotation.TargetApi;
+import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.res.Resources;
 import android.graphics.PixelFormat;
+import android.os.Build;
 import android.support.v4.app.NotificationCompat;
 import android.support.v4.content.ContextCompat;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 import android.view.WindowManager;
 
 import org.json.JSONArray;
@@ -258,16 +261,46 @@ public class TabQueueHelper {
             builder.setChannelId(NotificationHelper.getInstance(context)
                     .getNotificationChannel(NotificationHelper.Channel.DEFAULT).getId());
         }
 
         NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
         notificationManager.notify(TabQueueHelper.TAB_QUEUE_NOTIFICATION_ID, builder.build());
     }
 
+    /**
+     * Displays a foreground service notification used from Android O prompting the user that a tab
+     * is being added to the queue.
+     *
+     * @param context
+     * @return startupNotification
+     */
+    @TargetApi(Build.VERSION_CODES.O)
+    public static Notification getStartupNotification(final Context context) {
+        final Resources resources = context.getResources();
+
+        NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
+        inboxStyle.setBigContentTitle(resources.getString(R.string.tab_queue_notification_prompt));
+        inboxStyle.setSummaryText(resources.getString(R.string.tab_queue_notification_title));
+
+        NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
+                .setSmallIcon(R.drawable.ic_status_logo)
+                .setContentTitle(resources.getString(R.string.tab_queue_notification_prompt))
+                .setContentText(resources.getString(R.string.tab_queue_notification_title))
+                .setStyle(inboxStyle)
+                .setColor(ContextCompat.getColor(context, R.color.fennec_ui_accent));
+
+        if (!AppConstants.Versions.preO) {
+            builder.setChannelId(NotificationHelper.getInstance(context)
+                    .getNotificationChannel(NotificationHelper.Channel.DEFAULT).getId());
+        }
+
+        return builder.build();
+    }
+
     public static boolean shouldOpenTabQueueUrls(final Context context) {
         ThreadUtils.assertNotOnUiThread();
 
         // TODO: Use profile shared prefs when bug 1147925 gets fixed.
         final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
 
         int tabsQueued = prefs.getInt(PREF_TAB_QUEUE_COUNT, 0);
 
--- a/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueService.java
@@ -48,17 +48,18 @@ import java.util.concurrent.Executors;
 
 
 /**
  * On launch this Service displays a View over the currently running process with an action to open the url in Fennec
  * immediately.  If the user takes no action, allowing the runnable to be processed after the specified
  * timeout (TOAST_TIMEOUT), the url is added to a file which is then read in Fennec on next launch, this allows the
  * user to quickly queue urls to open without having to open Fennec each time. If the Service receives an Intent whilst
  * the created View is still active, the old url is immediately processed and the View is re-purposed with the new
- * Intent data.
+ * Intent data. From Android O, due to background limitations, this is a foreground service as it may be started
+ * from the background.
  * <p/>
  * The SYSTEM_ALERT_WINDOW permission is used to allow us to insert a View from this Service which responds to user
  * interaction, whilst still allowing whatever is in the background to be seen and interacted with.
  * <p/>
  * Using an Activity to do this doesn't seem to work as there's an issue to do with the native android intent resolver
  * dialog not being hidden when the toast is shown.  Using an IntentService instead of a Service doesn't work as
  * each new Intent received kicks off the IntentService lifecycle anew which means that a new View is created each time,
  * meaning that we can't quickly queue the current data and re-purpose the View.  The asynchronous nature of the
@@ -121,16 +122,20 @@ public class TabQueueService extends Ser
                         WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                 PixelFormat.TRANSLUCENT);
 
         toastLayoutParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
     }
 
     @Override
     public int onStartCommand(final Intent intent, final int flags, final int startId) {
+        if (!AppConstants.Versions.preO) {
+            startForeground(TabQueueHelper.TAB_QUEUE_NOTIFICATION_ID, TabQueueHelper.getStartupNotification(TabQueueService.this));
+        }
+
         // If this is a redelivery then lets bypass the entire double tap to open now code as that's a big can of worms,
         // we also don't expect redeliveries because of the short time window associated with this feature.
         if (flags != START_FLAG_REDELIVERY) {
             final Context applicationContext = getApplicationContext();
             final SharedPreferences sharedPreferences = GeckoSharedPrefs.forApp(applicationContext);
 
             final String lastUrl = sharedPreferences.getString(GeckoPreferences.PREFS_TAB_QUEUE_LAST_SITE, "");
 
@@ -144,16 +149,17 @@ public class TabQueueService extends Ser
                 // Background thread because we could do some file IO if we have to remove a url from the list.
                 tabQueueHandler.post(() -> {
                     // If there is a runnable around, that means that the previous process hasn't yet completed, so
                     // we will need to prevent it from running and remove the view from the window manager.
                     // If there is no runnable around then the url has already been added to the list, so we'll
                     // need to remove it before proceeding or that url will open multiple times.
                     if (stopServiceRunnable != null) {
                         tabQueueHandler.removeCallbacks(stopServiceRunnable);
+                        stopForeground(false);
                         stopSelfResult(stopServiceRunnable.getStartId());
                         stopServiceRunnable = null;
                         removeView();
                     } else {
                         TabQueueHelper.removeURLFromFile(applicationContext, intentUrl, TabQueueHelper.FILE_NAME);
                     }
                     openNow(safeIntent.getUnsafe());
 
@@ -207,16 +213,17 @@ public class TabQueueService extends Ser
     }
 
     private void openNow(Intent intent) {
         Intent forwardIntent = new Intent(intent);
         forwardIntent.setClassName(getApplicationContext(), AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
         forwardIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         startActivity(forwardIntent);
 
+        stopForeground(false);
         TabQueueHelper.removeNotification(getApplicationContext());
 
         GeckoSharedPrefs.forApp(getApplicationContext()).edit().remove(GeckoPreferences.PREFS_TAB_QUEUE_LAST_SITE)
                                                                .remove(GeckoPreferences.PREFS_TAB_QUEUE_LAST_TIME)
                                                                .apply();
 
         executorService.submit(() -> {
             int queuedTabCount = TabQueueHelper.getTabQueueLength(TabQueueService.this);
@@ -322,16 +329,17 @@ public class TabQueueService extends Ser
 
         public void run(final boolean shouldRemoveView) {
             onRun();
 
             if (shouldRemoveView) {
                 removeView();
             }
 
+            stopForeground(false);
             stopSelfResult(startId);
         }
 
         /*package*/ int getStartId() {
             return startId;
         }
 
         public abstract void onRun();
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -336,16 +336,19 @@
 <!-- Localization note (tab_queue_prompt_permit_drawing_over_apps): This additional text is shown if the
      user needs to enable an Android setting in order to enable tab queues. -->
 <!ENTITY tab_queue_prompt_permit_drawing_over_apps "Turn on Permit drawing over other apps">
 <!ENTITY tab_queue_prompt_positive_action_button "Enable">
 <!ENTITY tab_queue_prompt_negative_action_button "Not now">
 <!-- Localization note (tab_queue_prompt_settings_button): This button is shown if the user needs to
      enable a permission in Android's setting in order to enable tab queues. -->
 <!ENTITY tab_queue_prompt_settings_button "Go to Settings">
+<!-- Localization note (tab_queue_notification_prompt): This is the text of the default notification
+shown from Android O while a tab is being queued.-->
+<!ENTITY tab_queue_notification_prompt "Adding new tab to queue&#8230;">
 <!ENTITY tab_queue_notification_title "&brandShortName;">
 <!-- Localization note (tab_queue_notification_text_plural2) : The
      formatD is replaced with the number of tabs queued.  The
      number of tabs queued is always more than one.  We can't use
      Android plural forms, sadly. See Bug #753859. -->
 <!ENTITY tab_queue_notification_text_plural2 "&formatD; tabs waiting">
 <!-- Localization note (tab_queue_notification_text_singular2) : This is the
      text of a notification; we expect only one tab queued. -->
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -297,16 +297,17 @@
   <string name="tab_queue_prompt_positive_action_button">&tab_queue_prompt_positive_action_button;</string>
   <string name="tab_queue_prompt_negative_action_button">&tab_queue_prompt_negative_action_button;</string>
   <string name="tab_queue_prompt_permit_drawing_over_apps">&tab_queue_prompt_permit_drawing_over_apps;</string>
   <string name="tab_queue_prompt_settings_button">&tab_queue_prompt_settings_button;</string>
   <string name="tab_queue_toast_message">&tab_queue_toast_message3;</string>
   <string name="tab_queue_toast_action">&tab_queue_toast_action;</string>
   <string name="tab_queue_notification_text_singular">&tab_queue_notification_text_singular2;</string>
   <string name="tab_queue_notification_text_plural">&tab_queue_notification_text_plural2;</string>
+  <string name="tab_queue_notification_prompt">&tab_queue_notification_prompt;</string>
   <string name="tab_queue_notification_title">&tab_queue_notification_title;</string>
   <string name="tab_queue_notification_settings">&tab_queue_notification_settings;</string>
 
   <string name="pref_default_browser">&pref_default_browser;</string>
   <string name="pref_default_browser_mozilla_support_tablet">&pref_default_browser_mozilla_support_tablet;</string>
 
   <string name="pref_about_firefox">&pref_about_firefox;</string>