Bug 1454686 - Allow users to stop and start LeanPlum and it's notifications; r?mcomella draft
authorPetru Lingurar <petru.lingurar@softvision.ro>
Thu, 03 May 2018 13:40:34 +0300
changeset 791060 746102cdbd656ad4ea057640074b9a83dec561bf
parent 791048 a2d1d4158bb4718d8bb31e1284e133aa99273600
push id108673
push userplingurar@mozilla.com
push dateThu, 03 May 2018 10:44:41 +0000
reviewersmcomella
bugs1454686, 1445798
milestone61.0a1
Bug 1454686 - Allow users to stop and start LeanPlum and it's notifications; r?mcomella Added a new SwitchPreference in the Notifications Settings screen that uses the Strings from bug #1445798 That new toggle will only be displayed if LeanPlum is available. Behaviour handled in GeckoPreferences. When the user toggles the feature an event is triggered and catched by BrowserApp which can either - initialize LeanPlum (if the toggle was off LP is not running) as it would normally do when the app first starts - stop LeanPlum reporting to servers, flush the per-session available messages and resets the LP started status so that it can be restarted in the same app session (like if the user toggles the feature again) Renamed stop() from MmaDelegate.java to make it clear that that method only stops tracking events to the server (method only called before when user disabled Health Report) but the already downloaded messages continue to be triggered and added 2 new appropriately named methods MmaDelegate.haveMessagesToDisplay() and MmaDelegate.stopShowingSessionMessages() to allow checking if the there have been messages downloaded for this session and to allow flushing them to avoid displaying them being displayed in the current session when the initially set conditions are met. When the app starts it now also check for the status of this notification setting and doesn't start LeanPlum if LP notifications are off as that has the potential to skew the data because the user doesn't get tipped anymore about the cool features we offer. Modified the description for Settings-Notification as Andreas suggested, removed the lint suppression for unused strings added in bug 1445798. MozReview-Commit-ID: HQuyZ5owLhW ***
mobile/android/app/lint.xml
mobile/android/app/src/main/res/xml/preferences_notifications.xml
mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
mobile/android/base/java/org/mozilla/gecko/mma/MmaDelegate.java
mobile/android/base/java/org/mozilla/gecko/mma/MmaInterface.java
mobile/android/base/java/org/mozilla/gecko/mma/MmaLeanplumImp.java
mobile/android/base/java/org/mozilla/gecko/mma/MmaStubImp.java
mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
mobile/android/base/locales/en-US/android_strings.dtd
mobile/android/base/strings.xml.in
--- a/mobile/android/app/lint.xml
+++ b/mobile/android/app/lint.xml
@@ -236,19 +236,16 @@
     <issue id="UnlocalizedSms" severity="error" />
     <issue id="UnusedNamespace" severity="error" />
     <issue id="UnusedQuantity" severity="error" />
     <issue id="UnusedResources" severity="error">
         <!-- The moz.build based build system leaves a .mkdir.done file lying around in the
              preprocessed_resources res/raw folder. Lint reports it as unused. We should get
              rid of the file eventually. See bug 1268948. -->
         <ignore path="**/raw/.mkdir.done" />
-        <!-- Needed to ignore currently unused resources added as per bug 1445798
-             Suppression to be removed when the along with the ptch for bug 1454686 -->
-        <ignore path="**/res/values/strings.xml" />
     </issue>
     <issue id="Usability" severity="error" />
     <issue id="UseCheckPermission" severity="error" />
     <issue id="UseCompoundDrawables" severity="error" />
     <issue id="UselessLeaf" severity="error" />
     <issue id="UsesMinSdkAttributes" severity="error" />
     <issue id="UsingHttp" severity="error" />
     <issue id="ViewHolder" severity="error" />
--- a/mobile/android/app/src/main/res/xml/preferences_notifications.xml
+++ b/mobile/android/app/src/main/res/xml/preferences_notifications.xml
@@ -1,7 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
     <SwitchPreference android:key="android.not_a_preference.notifications.whats_new"
         android:title="@string/pref_whats_new_notification"
         android:summary="@string/pref_whats_new_notification_summary"
         android:defaultValue="true" />
+    <SwitchPreference android:key="android.not_a_preference.notifications.features.tips"
+        android:title="@string/pref_feature_tips_notification"
+        android:summary="@string/pref_feature_tips_notification_summary"
+        android:defaultValue="true" />
 </PreferenceScreen>
\ No newline at end of file
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -113,17 +113,16 @@ import org.mozilla.gecko.icons.IconsHelp
 import org.mozilla.gecko.icons.decoders.FaviconDecoder;
 import org.mozilla.gecko.icons.decoders.IconDirectoryEntry;
 import org.mozilla.gecko.icons.decoders.LoadFaviconResult;
 import org.mozilla.gecko.lwt.LightweightTheme;
 import org.mozilla.gecko.media.VideoPlayer;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.menu.GeckoMenuItem;
 import org.mozilla.gecko.mma.MmaDelegate;
-import org.mozilla.gecko.mozglue.GeckoLoader;
 import org.mozilla.gecko.mozglue.SafeIntent;
 import org.mozilla.gecko.notifications.NotificationHelper;
 import org.mozilla.gecko.overlays.ui.ShareDialog;
 import org.mozilla.gecko.permissions.Permissions;
 import org.mozilla.gecko.preferences.ClearOnShutdownPref;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.promotion.AddToHomeScreenPromotion;
 import org.mozilla.gecko.promotion.ReaderViewBookmarkPromotion;
@@ -277,16 +276,19 @@ public class BrowserApp extends GeckoApp
     public static final String TAB_HISTORY_FRAGMENT_TAG = "tabHistoryFragment";
 
     // When the static action bar is shown, only the real toolbar chrome should be
     // shown when the toolbar is visible. Causing the toolbar animator to also
     // show the snapshot causes the content to shift under the users finger.
     // See: Bug 1358554
     private boolean mShowingToolbarChromeForActionBar;
 
+    private SafeIntent safeStartingIntent;
+    private boolean isInAutomation;
+
     private static class MenuItemInfo implements Parcelable {
         public int id;
         public String label;
         public boolean checkable;
         public boolean checked;
         public boolean enabled = true;
         public boolean visible = true;
         public int parent;
@@ -719,20 +721,20 @@ public class BrowserApp extends GeckoApp
     }
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         final Context appContext = getApplicationContext();
 
         showSplashScreen = true;
 
-        final SafeIntent intent = new SafeIntent(getIntent());
-        final boolean isInAutomation = IntentUtils.getIsInAutomationFromEnvironment(intent);
-
-        GeckoProfile.setIntentArgs(intent.getStringExtra("args"));
+        safeStartingIntent = new SafeIntent(getIntent());
+        isInAutomation = IntentUtils.getIsInAutomationFromEnvironment(safeStartingIntent);
+
+        GeckoProfile.setIntentArgs(safeStartingIntent.getStringExtra("args"));
 
         if (!isInAutomation && AppConstants.MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE) {
             // Kick off download of app content as early as possible so that in the best case it's
             // available before the user starts using the browser.
             DownloadContentService.startStudy(this);
         }
 
         // This has to be prepared prior to calling GeckoApp.onCreate, because
@@ -742,17 +744,17 @@ public class BrowserApp extends GeckoApp
         app.prepareLightweightTheme();
 
         super.onCreate(savedInstanceState);
 
         if (mIsAbortingAppLaunch) {
           return;
         }
 
-        initSwitchboard(this, intent, isInAutomation);
+        initSwitchboard(this, safeStartingIntent, isInAutomation);
         initTelemetryUploader(isInAutomation);
 
         mBrowserChrome = (ViewGroup) findViewById(R.id.browser_chrome);
         mActionBarFlipper = (ViewFlipper) findViewById(R.id.browser_actionbar);
         mActionBar = (ActionModeCompatView) findViewById(R.id.actionbar);
 
         mVideoPlayer = (VideoPlayer) findViewById(R.id.video_player);
         mVideoPlayer.setFullScreenListener(new VideoPlayer.FullScreenListener() {
@@ -815,28 +817,28 @@ public class BrowserApp extends GeckoApp
                         GeckoAppShell.getHapticFeedbackDelegate().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
                         fragment.show(R.id.tab_history_panel, fragmentManager.beginTransaction(), TAB_HISTORY_FRAGMENT_TAG);
                     }
                 });
             }
         });
         mBrowserToolbar.setTabHistoryController(tabHistoryController);
 
-        final String action = intent.getAction();
+        final String action = safeStartingIntent.getAction();
         if (Intent.ACTION_VIEW.equals(action)) {
             // Show the target URL immediately in the toolbar.
-            mBrowserToolbar.setTitle(intent.getDataString());
-
-            showTabQueuePromptIfApplicable(intent);
+            mBrowserToolbar.setTitle(safeStartingIntent.getDataString());
+
+            showTabQueuePromptIfApplicable(safeStartingIntent);
         } else if (ACTION_VIEW_MULTIPLE.equals(action) && savedInstanceState == null) {
             // We only want to handle this intent if savedInstanceState is null. In the case where
             // savedInstanceState is not null this activity is being re-created and we already
             // opened tabs for the URLs the last time. Our session store will take care of restoring
             // them.
-            openMultipleTabsFromIntent(intent);
+            openMultipleTabsFromIntent(safeStartingIntent);
         } else if (GuestSession.NOTIFICATION_INTENT.equals(action)) {
             GuestSession.onNotificationIntentReceived(this);
         } else if (TabQueueHelper.LOAD_URLS_ACTION.equals(action)) {
             Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.NOTIFICATION, "tabqueue");
         }
 
         if (HardwareUtils.isTablet()) {
             mTabStrip = (TabStripInterface) (((ViewStub) findViewById(R.id.tablet_tab_strip)).inflate());
@@ -896,16 +898,17 @@ public class BrowserApp extends GeckoApp
             "Tab:Added",
             "Video:Play",
             "CharEncoding:Data",
             "CharEncoding:State",
             "Settings:Show",
             "Updater:Launch",
             "Sanitize:Finished",
             "Sanitize:OpenTabs",
+            "NotificationSettings:UpdateFeatureTipsStatus",
             null);
 
         EventDispatcher.getInstance().registerBackgroundThreadListener(this,
             "Experiments:GetActive",
             "Experiments:SetOverride",
             "Experiments:ClearOverride",
             "Favicon:Request",
             "Feedback:MaybeLater",
@@ -1012,17 +1015,18 @@ public class BrowserApp extends GeckoApp
         final String serverExtra = intent.getStringExtra(INTENT_KEY_SWITCHBOARD_SERVER);
         final String serverUrl = TextUtils.isEmpty(serverExtra) ? SWITCHBOARD_SERVER : serverExtra;
         new AsyncConfigLoader(context, serverUrl) {
             @Override
             protected Void doInBackground(Void... params) {
                 super.doInBackground(params);
                 SwitchBoard.loadConfig(context, serverUrl);
                 if (SwitchBoard.isInExperiment(context, Experiments.LEANPLUM) &&
-                        GeckoPreferences.getBooleanPref(context, GeckoPreferences.PREFS_HEALTHREPORT_UPLOAD_ENABLED, true)) {
+                        GeckoPreferences.getBooleanPref(context, GeckoPreferences.PREFS_HEALTHREPORT_UPLOAD_ENABLED, true) &&
+                        GeckoPreferences.getBooleanPref(context, GeckoPreferences.PREFS_NOTIFICATIONS_FEATURES_TIPS, true)) {
                     // Do LeanPlum start/init here
                     MmaDelegate.init(BrowserApp.this);
                 }
                 return null;
             }
         }.execute();
     }
 
@@ -1729,16 +1733,17 @@ public class BrowserApp extends GeckoApp
             "Tab:Added",
             "Video:Play",
             "CharEncoding:Data",
             "CharEncoding:State",
             "Settings:Show",
             "Updater:Launch",
             "Sanitize:Finished",
             "Sanitize:OpenTabs",
+            "NotificationSettings:UpdateFeatureTipsStatus",
             null);
 
         EventDispatcher.getInstance().unregisterBackgroundThreadListener(this,
             "Experiments:GetActive",
             "Experiments:SetOverride",
             "Experiments:ClearOverride",
             "Favicon:Request",
             "Feedback:MaybeLater",
@@ -2313,16 +2318,26 @@ public class BrowserApp extends GeckoApp
                             contentProviderClient,
                             location, hasImage, metadata);
                 } finally {
                     contentProviderClient.release();
                 }
 
                 break;
 
+            case "NotificationSettings:UpdateFeatureTipsStatus":
+                if (message.getBoolean("status")) {
+                    initSwitchboard(this, safeStartingIntent, isInAutomation);
+                } else {
+                    MmaDelegate.stopReportingEvents();
+                    MmaDelegate.stopShowingSessionMessages();
+                    MmaDelegate.allowRestartInCurrentAppSession();
+                }
+                break;
+
             default:
                 super.handleMessage(event, message, callback);
                 break;
         }
     }
 
     /**
      * Use a dummy Intent to do a default browser check.
--- a/mobile/android/base/java/org/mozilla/gecko/mma/MmaDelegate.java
+++ b/mobile/android/base/java/org/mozilla/gecko/mma/MmaDelegate.java
@@ -89,18 +89,30 @@ public class MmaDelegate {
 
         if (!isDefaultBrowser(activity)) {
             mmaHelper.event(MmaDelegate.LAUNCH_BUT_NOT_DEFAULT_BROWSER);
         }
         mmaHelper.event(MmaDelegate.LAUNCH_BROWSER);
 
     }
 
-    public static void stop() {
-        mmaHelper.stop();
+    public static void stopReportingEvents() {
+        mmaHelper.stopEventsReporting();
+    }
+
+    public static void stopShowingSessionMessages() {
+        mmaHelper.stopShowingSessionMessages();
+    }
+
+    public static void allowRestartInCurrentAppSession() {
+        mmaHelper.allowRestartInCurrentAppSession();
+    }
+
+    public static boolean haveMessagesToDisplay() {
+        return mmaHelper.haveMessagesToDisplay();
     }
 
     /* This method must be called at background thread to avoid performance issues in some API level */
     @NonNull
     private static Map<String, Object> gatherUserAttributes(final Context context) {
 
         final Map<String, Object> attributes = new HashMap<>();
 
@@ -166,22 +178,23 @@ public class MmaDelegate {
     // 2. handleGcmMessage(), it's called from GcmListenerService (where we handle GCM messages). Context is from the Service
     private static boolean isMmaEnabled(Context context) {
 
         if (context == null) {
             return false;
         }
         final boolean healthReport = GeckoPreferences.getBooleanPref(context, GeckoPreferences.PREFS_HEALTHREPORT_UPLOAD_ENABLED, true);
         final boolean inExperiment = SwitchBoard.isInExperiment(context, Experiments.LEANPLUM);
+        final boolean messagesAllowed = GeckoPreferences.getBooleanPref(context, GeckoPreferences.PREFS_NOTIFICATIONS_FEATURES_TIPS, true);
         final Tab selectedTab = Tabs.getInstance().getSelectedTab();
 
         // if selected tab is private, mma should be disabled.
         final boolean isInPrivateBrowsing = selectedTab != null && selectedTab.isPrivate();
         // only check Gecko Pref when Gecko is running
-        return inExperiment && healthReport && !isInPrivateBrowsing;
+        return inExperiment && healthReport && messagesAllowed && !isInPrivateBrowsing;
     }
 
     public static boolean isDefaultBrowser(Context context) {
         final Intent viewIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.mozilla.org"));
         final ResolveInfo info = context.getPackageManager().resolveActivity(viewIntent, PackageManager.MATCH_DEFAULT_ONLY);
         if (info == null) {
             // No default is set
             return false;
--- a/mobile/android/base/java/org/mozilla/gecko/mma/MmaInterface.java
+++ b/mobile/android/base/java/org/mozilla/gecko/mma/MmaInterface.java
@@ -23,14 +23,20 @@ public interface MmaInterface {
     void setCustomIcon(@DrawableRes int iconResId);
 
     void start(Context context);
 
     void event(String mmaEvent);
 
     void event(String mmaEvent, double value);
 
-    void stop();
+    void stopEventsReporting();
+
+    void stopShowingSessionMessages();
+
+    void allowRestartInCurrentAppSession();
+
+    boolean haveMessagesToDisplay();
 
     @CheckResult boolean handleGcmMessage(Context context, String from, Bundle bundle);
 
     void setDeviceId(@NonNull String deviceId);
 }
--- a/mobile/android/base/java/org/mozilla/gecko/mma/MmaLeanplumImp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/mma/MmaLeanplumImp.java
@@ -15,16 +15,18 @@ import android.support.annotation.Drawab
 import android.support.annotation.NonNull;
 import android.support.v4.app.NotificationCompat;
 
 import com.leanplum.Leanplum;
 import com.leanplum.LeanplumActivityHelper;
 import com.leanplum.LeanplumPushNotificationCustomizer;
 import com.leanplum.LeanplumPushService;
 import com.leanplum.internal.Constants;
+import com.leanplum.internal.LeanplumInternal;
+import com.leanplum.internal.VarCache;
 
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.MmaConstants;
 
 import java.util.Map;
 import java.util.UUID;
 
 
@@ -96,21 +98,37 @@ public class MmaLeanplumImp implements M
 
     @Override
     public void event(String leanplumEvent, double value) {
         Leanplum.track(leanplumEvent, value);
 
     }
 
     @Override
-    public void stop() {
+    public void stopEventsReporting() {
         Leanplum.stop();
     }
 
     @Override
+    public void stopShowingSessionMessages() {
+        VarCache.reset();
+    }
+
+    @Override
+    public void allowRestartInCurrentAppSession() {
+        LeanplumInternal.setCalledStart(false);
+        LeanplumInternal.setHasStarted(false);
+    }
+
+    @Override
+    public boolean haveMessagesToDisplay() {
+        return VarCache.messages() != null && !VarCache.messages().isEmpty();
+    }
+
+    @Override
     public boolean handleGcmMessage(Context context, String from, Bundle bundle) {
         if (from != null && from.equals(MmaConstants.MOZ_MMA_SENDER_ID) && bundle.containsKey(Constants.Keys.PUSH_MESSAGE_TEXT)) {
             LeanplumPushService.handleNotification(context, bundle);
             return true;
         }
         return false;
     }
 
--- a/mobile/android/base/java/org/mozilla/gecko/mma/MmaStubImp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/mma/MmaStubImp.java
@@ -37,18 +37,33 @@ public class MmaStubImp implements MmaIn
     }
 
     @Override
     public void event(String leanplumEvent, double value) {
 
     }
 
     @Override
-    public void stop() {
+    public void stopEventsReporting() {
+
+    }
+
+    @Override
+    public void stopShowingSessionMessages() {
+
+    }
 
+    @Override
+    public void allowRestartInCurrentAppSession() {
+
+    }
+
+    @Override
+    public boolean haveMessagesToDisplay() {
+        return false;
     }
 
     @Override
     public boolean handleGcmMessage(Context context, String from, Bundle bundle) {
         return false;
     }
 
     @Override
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
+++ b/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
@@ -51,32 +51,34 @@ import org.json.JSONArray;
 import org.mozilla.gecko.AboutPages;
 import org.mozilla.gecko.AdjustConstants;
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.BrowserLocaleManager;
 import org.mozilla.gecko.DataReportingNotification;
 import org.mozilla.gecko.DynamicToolbar;
 import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.Experiments;
 import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.LocaleManager;
 import org.mozilla.gecko.Locales;
 import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.SnackbarBuilder;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.TelemetryContract.Method;
 import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
 import org.mozilla.gecko.mma.MmaDelegate;
 import org.mozilla.gecko.permissions.Permissions;
 import org.mozilla.gecko.restrictions.Restrictable;
 import org.mozilla.gecko.restrictions.Restrictions;
+import org.mozilla.gecko.switchboard.SwitchBoard;
 import org.mozilla.gecko.tabqueue.TabQueueHelper;
 import org.mozilla.gecko.tabqueue.TabQueuePrompt;
 import org.mozilla.gecko.updater.UpdateService;
 import org.mozilla.gecko.updater.UpdateServiceHelper;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.ContextUtils;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
@@ -140,16 +142,17 @@ public class GeckoPreferences
     private static final String PREFS_SCREEN_ADVANCED = NON_PREF_PREFIX + "advanced_screen";
     public static final String PREFS_HOMEPAGE = NON_PREF_PREFIX + "homepage";
     public static final String PREFS_HOMEPAGE_FOR_EVERY_NEW_TAB = NON_PREF_PREFIX + "newtab.load_homepage";
     public static final String PREFS_HOMEPAGE_PARTNER_COPY = GeckoPreferences.PREFS_HOMEPAGE + ".partner";
     public static final String PREFS_HISTORY_SAVED_SEARCH = NON_PREF_PREFIX + "search.search_history.enabled";
     private static final String PREFS_FAQ_LINK = NON_PREF_PREFIX + "faq.link";
     private static final String PREFS_FEEDBACK_LINK = NON_PREF_PREFIX + "feedback.link";
     public static final String PREFS_NOTIFICATIONS_WHATS_NEW = NON_PREF_PREFIX + "notifications.whats_new";
+    public static final String PREFS_NOTIFICATIONS_FEATURES_TIPS = NON_PREF_PREFIX + "notifications.features.tips";
     public static final String PREFS_APP_UPDATE_LAST_BUILD_ID = "app.update.last_build_id";
     public static final String PREFS_READ_PARTNER_CUSTOMIZATIONS_PROVIDER = NON_PREF_PREFIX + "distribution.read_partner_customizations_provider";
     public static final String PREFS_READ_PARTNER_BOOKMARKS_PROVIDER = NON_PREF_PREFIX + "distribution.read_partner_bookmarks_provider";
     public static final String PREFS_CUSTOM_TABS = NON_PREF_PREFIX + "customtabs_58";
     public static final String PREFS_CATEGORY_EXPERIMENTAL_FEATURES = NON_PREF_PREFIX + "category_experimental";
     public static final String PREFS_COMPACT_TABS = NON_PREF_PREFIX + "compact_tabs";
     public static final String PREFS_SHOW_QUIT_MENU = NON_PREF_PREFIX + "distribution.show_quit_menu";
     public static final String PREFS_SEARCH_SUGGESTIONS_ENABLED = "browser.search.suggest.enabled";
@@ -808,16 +811,33 @@ public class GeckoPreferences
                         continue;
                     }
                 } else if (PREFS_COMPACT_TABS.equals(key)) {
                     if (HardwareUtils.isTablet()) {
                         preferences.removePreference(pref);
                         i--;
                         continue;
                     }
+                } else if (PREFS_NOTIFICATIONS_FEATURES_TIPS.equals(key)) {
+                    // Use the PreferenceScreen that will be displayed to access SharedPreferences and check other stored preferences
+                    final SharedPreferences sharedPrefs = preferences.getSharedPreferences();
+
+                    if (!SwitchBoard.isInExperiment(this, Experiments.LEANPLUM_DEBUG) &&
+                            !SwitchBoard.isInExperiment(this, Experiments.LEANPLUM) ||
+                            // MMA can only work if Health Report is enabled
+                            !sharedPrefs.contains(PREFS_HEALTHREPORT_UPLOAD_ENABLED) ||
+                            // But if the app started with the features enabled and the user then disabled Health Report
+                            // the user must still be able to stop pending messages.
+                            !sharedPrefs.getBoolean(PREFS_HEALTHREPORT_UPLOAD_ENABLED, true) &&
+                                    !MmaDelegate.haveMessagesToDisplay()) {
+
+                        preferences.removePreference(pref);
+                        i--;
+                        continue;
+                    }
                 }
 
                 // Some Preference UI elements are not actually preferences,
                 // but they require a key to work correctly. For example,
                 // "Clear private data" requires a key for its state to be
                 // saved when the orientation changes. It uses the
                 // "android.not_a_preference.privacy.clear" key - which doesn't
                 // exist in Gecko - to satisfy this requirement.
@@ -1101,32 +1121,36 @@ public class GeckoPreferences
         } else if (PREFS_UPDATER_AUTODOWNLOAD.equals(prefName)) {
             UpdateServiceHelper.setAutoDownloadPolicy(this, UpdateService.AutoDownloadPolicy.get((String) newValue));
         } else if (PREFS_UPDATER_URL.equals(prefName)) {
             UpdateServiceHelper.setUpdateUrl(this, (String) newValue);
         } else if (PREFS_HEALTHREPORT_UPLOAD_ENABLED.equals(prefName)) {
             final Boolean newBooleanValue = (Boolean) newValue;
             AdjustConstants.getAdjustHelper().setEnabled(newBooleanValue);
             if (!newBooleanValue) {
-                MmaDelegate.stop();
+                MmaDelegate.stopReportingEvents();
             }
         } else if (PREFS_GEO_REPORTING.equals(prefName)) {
             if ((Boolean) newValue) {
                 enableStumbler((CheckBoxPreference) preference);
                 return false;
             } else {
                 broadcastStumblerPref(GeckoPreferences.this, false);
                 return true;
             }
         } else if (PREFS_TAB_QUEUE.equals(prefName)) {
             if ((Boolean) newValue && !TabQueueHelper.canDrawOverlays(this)) {
                 Intent promptIntent = new Intent(this, TabQueuePrompt.class);
                 startActivityForResult(promptIntent, REQUEST_CODE_TAB_QUEUE);
                 return false;
             }
+        } else if (PREFS_NOTIFICATIONS_FEATURES_TIPS.equals(prefName)) {
+            final GeckoBundle newStatus = new GeckoBundle(1);
+            newStatus.putBoolean("status", !((SwitchPreference) preference).isChecked());
+            EventDispatcher.getInstance().dispatch("NotificationSettings:UpdateFeatureTipsStatus", newStatus);
         } else if (HANDLERS.containsKey(prefName)) {
             PrefHandler handler = HANDLERS.get(prefName);
             handler.onChange(this, preference, newValue);
         } else if (PREFS_SEARCH_SUGGESTIONS_ENABLED.equals(prefName)) {
             // Tell Gecko to transmit the current search engine data again, so
             // BrowserSearch is notified immediately about the new enabled state.
             EventDispatcher.getInstance().dispatch("SearchEngines:GetVisible", null);
         }
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -201,17 +201,17 @@
      it is applicable. -->
 <!ENTITY pref_search_hint2 "TIP: Add any website to your list of search providers by long-pressing on its search field and then touching the &formatI; icon.">
 <!ENTITY pref_category_advanced "Advanced">
 <!-- Localization note (pref_category_advanced_summary3): “data saver” in this
      context means consuming less data, e.g. by not loading images, not
      “storing data”. -->
 <!ENTITY pref_category_advanced_summary3 "Restore tabs, data saver, developer tools">
 <!ENTITY pref_category_notifications "Notifications">
-<!ENTITY pref_category_notifications_summary "New features, website updates">
+<!ENTITY pref_category_notifications_summary2 "New features, product tips">
 <!ENTITY pref_developer_remotedebugging_usb "Remote debugging via USB">
 <!ENTITY pref_developer_remotedebugging_wifi "Remote debugging via Wi-Fi">
 <!ENTITY pref_developer_remotedebugging_wifi_disabled_summary "Wi-Fi debugging requires your device to have a QR code reader app installed.">
 <!ENTITY pref_remember_signons2 "Remember logins">
 <!ENTITY pref_manage_logins "Manage logins">
 
 <!ENTITY pref_category_home "Home">
 <!ENTITY pref_category_home_summary "Customize your homepage">
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -162,17 +162,17 @@
 
   <string name="pref_category_advanced">&pref_category_advanced;</string>
   <string name="pref_category_advanced_summary">&pref_category_advanced_summary3;</string>
   <string name="pref_developer_remotedebugging_usb">&pref_developer_remotedebugging_usb;</string>
   <string name="pref_developer_remotedebugging_wifi">&pref_developer_remotedebugging_wifi;</string>
   <string name="pref_developer_remotedebugging_wifi_disabled_summary">&pref_developer_remotedebugging_wifi_disabled_summary;</string>
 
   <string name="pref_category_notifications">&pref_category_notifications;</string>
-  <string name="pref_category_notifications_summary">&pref_category_notifications_summary;</string>
+  <string name="pref_category_notifications_summary">&pref_category_notifications_summary2;</string>
 
   <string name="pref_category_home">&pref_category_home;</string>
   <string name="pref_category_home_summary">&pref_category_home_summary;</string>
   <string name="pref_category_home_panels">&pref_category_home_panels;</string>
   <string name="pref_home_updates_wifi">&pref_home_updates_wifi;</string>
   <string name="pref_category_home_add_ons">&pref_category_home_add_ons;</string>
   <string name="pref_home_updates">&pref_home_updates2;</string>
   <string name="pref_home_updates_enabled">&pref_home_updates_enabled;</string>