Bug 1465323 - Update PackageAddedReceiver; r?JanH draft
authorPetru Lingurar <petru.lingurar@softvision.ro>
Tue, 26 Jun 2018 12:09:41 +0300
changeset 817984 b20c9125e9ba3e1ae7de5ba0616cb02c4bc7b2cc
parent 817983 42ac3d92b32fad825d64411ebefd0d48207f1f3d
child 817985 00a3b199cd673687fb03b8b59600dd00f69fcb29
push id116231
push userplingurar@mozilla.com
push dateFri, 13 Jul 2018 19:23:06 +0000
reviewersJanH
bugs1465323
milestone63.0a1
Bug 1465323 - Update PackageAddedReceiver; r?JanH This Receiver was used for implicit broadcasts and registered statically. Refactored MmaDelegate() to register it dynamically in the init() method, called in activity's onCreate and unregister it in activity's onDestroy. This way we will still get notified immediately if the user installs any of the apps we are interested in, even though he might not return to Fennec immediately after. This will help to better asses the impact of suggestions to install recommended packages. For the cases in which the user installs the packages without us suggesting to or if he kills our app before completing the new install, we will trigger a check for the install status of the packages in MmaDelegate().init(). Also cleaned the code a little. MozReview-Commit-ID: I00mLS2snzj
mobile/android/base/AndroidManifest.xml.in
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/PackageAddedReceiver.java
mobile/android/base/java/org/mozilla/gecko/util/PackageUtil.java
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -466,25 +466,16 @@
         <!-- DON'T EXPORT THIS, please! An attacker could delete arbitrary files. -->
         <service
             android:exported="false"
             android:name="org.mozilla.gecko.cleanup.FileCleanupService"
             android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
         <receiver
-            android:name="org.mozilla.gecko.mma.PackageAddedReceiver"
-            android:exported="false">
-            <intent-filter>
-                 <action android:name="android.intent.action.PACKAGE_ADDED"/>
-                 <data android:scheme="package"/>
-            </intent-filter>
-        </receiver>
-
-        <receiver
             android:name="org.mozilla.gecko.PackageReplacedReceiver"
             android:exported="false">
             <intent-filter>
                 <action android:name="android.intent.action.MY_PACKAGE_REPLACED"></action>
             </intent-filter>
         </receiver>
 
         <service
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -1672,16 +1672,18 @@ public class BrowserApp extends GeckoApp
             delegate.onDestroy(this);
         }
 
         deleteTempFiles(getApplicationContext());
 
         NotificationHelper.destroy();
         GeckoNetworkManager.destroy();
 
+        MmaDelegate.flushResources(this);
+
         super.onDestroy();
     }
 
     @Override
     protected void initializeChrome() {
         super.initializeChrome();
 
         mDoorHangerPopup.setAnchor(mBrowserToolbar.getDoorHangerAnchor());
--- a/mobile/android/base/java/org/mozilla/gecko/mma/MmaDelegate.java
+++ b/mobile/android/base/java/org/mozilla/gecko/mma/MmaDelegate.java
@@ -3,37 +3,34 @@
  * 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.mma;
 
 import android.app.Activity;
 import android.content.Context;
-import android.content.Intent;
 import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.net.Uri;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
 import android.text.TextUtils;
 
 import org.mozilla.gecko.Experiments;
 import org.mozilla.gecko.MmaConstants;
 import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.activitystream.homepanel.ActivityStreamConfiguration;
 import org.mozilla.gecko.firstrun.PanelConfig;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.switchboard.SwitchBoard;
 import org.mozilla.gecko.util.ContextUtils;
+import org.mozilla.gecko.util.PackageUtil;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import java.util.HashMap;
 import java.util.Map;
 import java.util.UUID;
 
 
 public class MmaDelegate {
@@ -68,21 +65,25 @@ public class MmaDelegate {
     private static final String PACKAGE_NAME_KLAR = "org.mozilla.klar";
     private static final String PACKAGE_NAME_FOCUS = "org.mozilla.focus";
     private static final String PACKAGE_NAME_POCKET = "com.ideashower.readitlater.pro";
 
     private static final String TAG = "MmaDelegate";
 
     public static final String KEY_ANDROID_PREF_STRING_LEANPLUM_DEVICE_ID = "android.not_a_preference.leanplum.device_id";
     private static final String KEY_ANDROID_PREF_BOOLEAN_FENNEC_IS_DEFAULT = "android.not_a_preference.fennec.default.browser.status";
+    private static final String KEY_ANDROID_PREF_BOOLEAN_FOCUS_INSTALLED = "android.not_a_preference.focus.package.installed";
+    private static final String KEY_ANDROID_PREF_BOOLEAN_KLAR_INSTALLED = "android.not_a_preference.klar.package.installed";
 
     private static final String DEBUG_LEANPLUM_DEVICE_ID = "8effda84-99df-11e7-abc4-cec278b6b50a";
 
     private static final MmaInterface mmaHelper = MmaConstants.getMma();
     private static Context applicationContext;
+    private static PackageAddedReceiver packageAddedReceiver;
+    private static String activityName;     // used to retrieve Activity's SharedPreferences
 
     public static void init(final Activity activity,
                             final MmaVariablesChangedListener remoteVariablesListener) {
         applicationContext = activity.getApplicationContext();
         // Since user attributes are gathered in Fennec, not in MMA implementation,
         // we gather the information here then pass to mmaHelper.init()
         // Note that generateUserAttribute always return a non null HashMap.
         final Map<String, Object> attributes = gatherUserAttributes(activity);
@@ -92,28 +93,42 @@ public class MmaDelegate {
         // above two config setup required to be invoked before mmaHelper.init.
         mmaHelper.init(activity, attributes);
 
         if (!isDefaultBrowser(activity)) {
             mmaHelper.event(MmaDelegate.LAUNCH_BUT_NOT_DEFAULT_BROWSER);
         }
         mmaHelper.event(MmaDelegate.LAUNCH_BROWSER);
 
+        activityName = activity.getLocalClassName();
+        registerInstalledPackagesReceiver(activity);
+        notifyAboutPreviouslyInstalledPackages(activity);
+
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 mmaHelper.listenOnceForVariableChanges(remoteVariablesListener);
             }
         });
     }
 
+    /**
+     * Stop LeanPlum functionality.
+     */
     public static void stop() {
         mmaHelper.stop();
     }
 
+    /**
+     * Clear resources used while the app was in foreground.
+     */
+    public static void flushResources(@NonNull final Activity activity) {
+        unregisterInstalledPackagesReceiver(activity);
+    }
+
     /* 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<>();
 
         attributes.put(USER_ATT_FOCUS_INSTALLED, ContextUtils.isPackageInstalled(context, PACKAGE_NAME_FOCUS));
         attributes.put(USER_ATT_KLAR_INSTALLED, ContextUtils.isPackageInstalled(context, PACKAGE_NAME_KLAR));
@@ -125,42 +140,84 @@ public class MmaDelegate {
         return attributes;
     }
 
     public static void notifyDefaultBrowserStatus(Activity activity) {
         if (!isMmaAllowed(activity)) {
             return;
         }
 
-        final SharedPreferences sharedPreferences = activity.getPreferences(Context.MODE_PRIVATE);
+        final SharedPreferences prefs = activity.getPreferences(Context.MODE_PRIVATE);
         final boolean isFennecDefaultBrowser = isDefaultBrowser(activity);
 
         // Only if this is not the first run of LeanPlum and we previously tracked default browser status
         // we can check for changes
-        if (sharedPreferences.contains(KEY_ANDROID_PREF_BOOLEAN_FENNEC_IS_DEFAULT)) {
+        if (prefs.contains(KEY_ANDROID_PREF_BOOLEAN_FENNEC_IS_DEFAULT)) {
             // Will only inform LeanPlum of the event if Fennec was not previously the default browser
-            if (!sharedPreferences.getBoolean(KEY_ANDROID_PREF_BOOLEAN_FENNEC_IS_DEFAULT, true) && isFennecDefaultBrowser) {
+            if (!prefs.getBoolean(KEY_ANDROID_PREF_BOOLEAN_FENNEC_IS_DEFAULT, true) && isFennecDefaultBrowser) {
                 track(CHANGED_DEFAULT_TO_FENNEC);
             }
         }
 
-        sharedPreferences.edit().putBoolean(KEY_ANDROID_PREF_BOOLEAN_FENNEC_IS_DEFAULT, isFennecDefaultBrowser).apply();
+        prefs.edit().putBoolean(KEY_ANDROID_PREF_BOOLEAN_FENNEC_IS_DEFAULT, isFennecDefaultBrowser).apply();
     }
 
+    /**
+     * Track packages installs while the app was not running.
+     */
+    private static void notifyAboutPreviouslyInstalledPackages(@NonNull final Activity activity) {
+        if (!isMmaAllowed(activity)) {
+            return;
+        }
+
+        final SharedPreferences prefs = activity.getPreferences(Context.MODE_PRIVATE);
+        boolean isFocusInstalled = ContextUtils.isPackageInstalled(activity, PACKAGE_NAME_FOCUS);
+        boolean isKlarInstalled = ContextUtils.isPackageInstalled(activity, PACKAGE_NAME_KLAR);
+
+        // Only if this is not the first run of LeanPlum and we previously tracked this events
+        // we can check for changes
+        if (prefs.contains(KEY_ANDROID_PREF_BOOLEAN_FOCUS_INSTALLED)) {
+            // Will only inform LeanPlum of the event if the package was not installed before
+            if (!prefs.getBoolean(KEY_ANDROID_PREF_BOOLEAN_FOCUS_INSTALLED, true) && isFocusInstalled) {
+                // Already know Mma is enabled, safe to call directly and avoid a superfluous check
+                mmaHelper.event(INSTALLED_FOCUS);
+            }
+            if (!prefs.getBoolean(KEY_ANDROID_PREF_BOOLEAN_KLAR_INSTALLED, true) && isKlarInstalled) {
+                mmaHelper.event(INSTALLED_KLAR);
+            }
+        }
+
+        final SharedPreferences.Editor editor = prefs.edit();
+        editor.putBoolean(KEY_ANDROID_PREF_BOOLEAN_FOCUS_INSTALLED, isFocusInstalled);
+        editor.putBoolean(KEY_ANDROID_PREF_BOOLEAN_KLAR_INSTALLED, isKlarInstalled);
+        editor.apply();
+    }
+
+    // Method called only by the Receiver which is registered in Activity's onCreate
+    // and unregistered in Activity's onDestroy()
     static void trackJustInstalledPackage(@NonNull final Context context, @NonNull final String packageName,
                                           final boolean firstTimeInstall) {
         if (!isMmaAllowed(context)) {
             return;
         }
 
-        if (packageName.equals(PACKAGE_NAME_FOCUS) && firstTimeInstall) {
+        final boolean justInstalledFocus = packageName.equals(PACKAGE_NAME_FOCUS) && firstTimeInstall;
+        final boolean justInstalledKlar = packageName.equals(PACKAGE_NAME_KLAR) && firstTimeInstall;
+
+        if (justInstalledFocus) {
             // Already know Mma is enabled, safe to call directly and avoid a superfluous check
             mmaHelper.event(INSTALLED_FOCUS);
-        } else if (packageName.equals(PACKAGE_NAME_KLAR) && firstTimeInstall) {
+            final SharedPreferences prefs =
+                    applicationContext.getSharedPreferences(activityName, Context.MODE_PRIVATE);
+            prefs.edit().putBoolean(KEY_ANDROID_PREF_BOOLEAN_FOCUS_INSTALLED, justInstalledFocus).apply();
+        } else if (justInstalledKlar) {
             mmaHelper.event(INSTALLED_KLAR);
+            final SharedPreferences prefs =
+                    applicationContext.getSharedPreferences(activityName, Context.MODE_PRIVATE);
+            prefs.edit().putBoolean(KEY_ANDROID_PREF_BOOLEAN_KLAR_INSTALLED, justInstalledKlar).apply();
         }
     }
 
     public static void track(String event) {
         if (applicationContext != null && isMmaAllowed(applicationContext)) {
             mmaHelper.event(event);
         }
     }
@@ -197,24 +254,18 @@ public class MmaDelegate {
         final Tab selectedTab = Tabs.getInstance().getSelectedTab();
         // if selected tab is private, mma should be disabled.
         final boolean isInPrivateBrowsing = selectedTab != null && selectedTab.isPrivate();
 
         return isMmaAvailable && !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;
-        }
-        final String packageName = info.activityInfo.packageName;
-        return (TextUtils.equals(packageName, context.getPackageName()));
+        final String defaultBrowserPackageName = PackageUtil.getDefaultBrowserPackage(context);
+        return (TextUtils.equals(defaultBrowserPackageName, context.getPackageName()));
     }
 
     // Always use pass-in context. Do not use applicationContext here. applicationContext will be null if MmaDelegate.init() is not called.
     public static boolean handleGcmMessage(@NonNull Context context, String from, @NonNull Bundle bundle) {
         if (isMmaAllowed(context)) {
             mmaHelper.setCustomIcon(R.drawable.ic_status_logo);
             return mmaHelper.handleGcmMessage(context, from, bundle);
         } else {
@@ -226,23 +277,33 @@ public class MmaDelegate {
         return mmaHelper.getPanelConfig(context, panelConfigType, useLocalValues);
     }
 
     private static String getDeviceId(Activity activity) {
         if (SwitchBoard.isInExperiment(activity, Experiments.LEANPLUM_DEBUG)) {
             return DEBUG_LEANPLUM_DEVICE_ID;
         }
 
-        final SharedPreferences sharedPreferences = activity.getPreferences(Context.MODE_PRIVATE);
-        String deviceId = sharedPreferences.getString(KEY_ANDROID_PREF_STRING_LEANPLUM_DEVICE_ID, null);
+        final SharedPreferences prefs = activity.getPreferences(Context.MODE_PRIVATE);
+        String deviceId = prefs.getString(KEY_ANDROID_PREF_STRING_LEANPLUM_DEVICE_ID, null);
         if (deviceId == null) {
             deviceId = UUID.randomUUID().toString();
-            sharedPreferences.edit().putString(KEY_ANDROID_PREF_STRING_LEANPLUM_DEVICE_ID, deviceId).apply();
+            prefs.edit().putString(KEY_ANDROID_PREF_STRING_LEANPLUM_DEVICE_ID, deviceId).apply();
         }
         return deviceId;
     }
 
+    private static void registerInstalledPackagesReceiver(@NonNull final Activity activity) {
+        packageAddedReceiver = new PackageAddedReceiver();
+        activity.registerReceiver(packageAddedReceiver, PackageAddedReceiver.getIntentFilter());
+    }
+
+    private static void unregisterInstalledPackagesReceiver(@NonNull final Activity activity) {
+        activity.unregisterReceiver(packageAddedReceiver);
+        packageAddedReceiver = null;
+    }
+
     public interface MmaVariablesChangedListener {
         void onRemoteVariablesChanged();
 
         void onRemoteVariablesUnavailable();
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/mma/PackageAddedReceiver.java
+++ b/mobile/android/base/java/org/mozilla/gecko/mma/PackageAddedReceiver.java
@@ -1,37 +1,49 @@
 package org.mozilla.gecko.mma;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.support.annotation.NonNull;
 
 
 /**
  * Used to inform as soon as possible of any applications being installed.
  */
 public class PackageAddedReceiver extends BroadcastReceiver {
+    private static final String intentAction = Intent.ACTION_PACKAGE_ADDED;
+    private static final String dataScheme = "package";
 
     @Override
     public void onReceive(Context context, Intent intent) {
         if (intent.getAction() != null && intent.getData() != null) {
             final String updatedPackage = getInstalledPackageName(intent.getData());
             try {
                 MmaDelegate.trackJustInstalledPackage(context, updatedPackage,
                         getIfFirstTimeInstall(context, updatedPackage));
             } catch (PackageManager.NameNotFoundException e) {
                 /* Nothing to do */
             }
         }
     }
 
+    public static IntentFilter getIntentFilter() {
+        final IntentFilter intentFilter = new IntentFilter();
+
+        intentFilter.addAction(intentAction);
+        intentFilter.addDataScheme(dataScheme);
+
+        return intentFilter;
+    }
+
     // Our intent filter uses the "package" scheme
     // So the intent we receive would be in the form package:org.mozilla.klar
     private String getInstalledPackageName(@NonNull Uri intentData) {
         return intentData.getSchemeSpecificPart();
     }
 
     private boolean getIfFirstTimeInstall(@NonNull Context context, @NonNull final String packageName)
             throws PackageManager.NameNotFoundException {
--- a/mobile/android/base/java/org/mozilla/gecko/util/PackageUtil.java
+++ b/mobile/android/base/java/org/mozilla/gecko/util/PackageUtil.java
@@ -2,16 +2,17 @@
  * 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.util;
 
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
 import android.support.annotation.NonNull;
 
 import java.util.List;
 
 public class PackageUtil {
 
@@ -47,16 +48,25 @@ public class PackageUtil {
             if (resolveInfo.activityInfo.packageName.equals(it.activityInfo.packageName)) {
                 return resolveInfo;
             }
         }
 
         return null;
     }
 
+    public static String getDefaultBrowserPackage(@NonNull final Context context) {
+        final ResolveInfo resolveInfo = getDefaultBrowser(context);
+        if (resolveInfo != null) {
+            return resolveInfo.activityInfo.packageName;
+        } else {
+            return null;
+        }
+    }
+
     /**
      * Information of activities which could handle a web-page-browsing intent.
      *
      * @param context
      * @return a List of activity's information. If default browser set, the list might only has one item.
      */
     private static List<ResolveInfo> resolveBrowsers(@NonNull Context context) {
         final Intent browserIntent = new Intent(Intent.ACTION_VIEW, TEST_URI);