diff --git a/mobile/android/base/generate_build_config.py b/mobile/android/base/generate_build_config.py index f2cfa8943b23..7be265097174 100644 --- a/mobile/android/base/generate_build_config.py +++ b/mobile/android/base/generate_build_config.py @@ -106,6 +106,7 @@ def _defines(): if CONFIG['MOZ_ANDROID_MMA']: DEFINES['MOZ_LEANPLUM_SDK_KEY'] = CONFIG['MOZ_LEANPLUM_SDK_KEY'] + DEFINES['MOZ_LEANPLUM_SDK_CLIENTID'] = CONFIG['MOZ_LEANPLUM_SDK_CLIENTID'] DEFINES['MOZ_BUILDID'] = open(os.path.join(buildconfig.topobjdir, 'buildid.h')).readline().split()[2] diff --git a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java index f2b37128f7a0..5dd55e7fe817 100644 --- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java +++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java @@ -20,6 +20,7 @@ import org.mozilla.gecko.home.HomeConfig.PanelType; import org.mozilla.gecko.menu.GeckoMenu; import org.mozilla.gecko.menu.GeckoMenuInflater; import org.mozilla.gecko.menu.MenuPanel; +import org.mozilla.gecko.mma.MmaDelegate; import org.mozilla.gecko.notifications.NotificationHelper; import org.mozilla.gecko.util.IntentUtils; import org.mozilla.gecko.mozglue.SafeIntent; @@ -121,6 +122,8 @@ import java.util.concurrent.TimeUnit; import static org.mozilla.gecko.Tabs.INTENT_EXTRA_SESSION_UUID; import static org.mozilla.gecko.Tabs.INTENT_EXTRA_TAB_ID; import static org.mozilla.gecko.Tabs.INVALID_TAB_ID; +import static org.mozilla.gecko.mma.MmaDelegate.DOWNLOAD_VIDEOS_OR_ANY_OTHER_MEDIA; +import static org.mozilla.gecko.mma.MmaDelegate.LOADS_ARTICLES; public abstract class GeckoApp extends GeckoActivity implements AnchoredPopup.OnVisibilityChangeListener, @@ -870,7 +873,15 @@ public abstract class GeckoApp extends GeckoActivity final SharedPreferences prefs = getSharedPreferences(); int count = prefs.getInt(PREFS_FLASH_USAGE, 0); prefs.edit().putInt(PREFS_FLASH_USAGE, ++count).apply(); + + } else if ("Mma:reader_available".equals(event)) { + MmaDelegate.track(LOADS_ARTICLES); + + } else if ("Mma:web_save_media".equals(event) || "Mma:web_save_image".equals(event)) { + MmaDelegate.track(DOWNLOAD_VIDEOS_OR_ANY_OTHER_MEDIA); + } + } /** @@ -1396,6 +1407,9 @@ public abstract class GeckoApp extends GeckoActivity "DevToolsAuth:Scan", "DOMFullScreen:Start", "DOMFullScreen:Stop", + "Mma:reader_available", + "Mma:web_save_image", + "Mma:web_save_media", "Permissions:Data", "PrivateBrowsing:Data", "RuntimePermissions:Check", @@ -2444,6 +2458,9 @@ public abstract class GeckoApp extends GeckoActivity "DevToolsAuth:Scan", "DOMFullScreen:Start", "DOMFullScreen:Stop", + "Mma:reader_available", + "Mma:web_save_image", + "Mma:web_save_media", "Permissions:Data", "PrivateBrowsing:Data", "RuntimePermissions:Check", diff --git a/mobile/android/base/java/org/mozilla/gecko/Tabs.java b/mobile/android/base/java/org/mozilla/gecko/Tabs.java index 8d59c1432f19..98a411d0e4e9 100644 --- a/mobile/android/base/java/org/mozilla/gecko/Tabs.java +++ b/mobile/android/base/java/org/mozilla/gecko/Tabs.java @@ -21,9 +21,11 @@ import org.mozilla.gecko.annotation.RobocopTarget; import org.mozilla.gecko.db.BrowserDB; import org.mozilla.gecko.distribution.PartnerBrowserCustomizationsClient; import org.mozilla.gecko.gfx.LayerView; +import org.mozilla.gecko.mma.MmaDelegate; import org.mozilla.gecko.mozglue.SafeIntent; import org.mozilla.gecko.notifications.WhatsNewReceiver; import org.mozilla.gecko.preferences.GeckoPreferences; +import org.mozilla.gecko.promotion.AddToHomeScreenPromotion; import org.mozilla.gecko.reader.ReaderModeUtils; import org.mozilla.gecko.util.BundleEventListener; import org.mozilla.gecko.util.EventCallback; @@ -49,6 +51,7 @@ import android.text.TextUtils; import android.util.Log; import static org.mozilla.gecko.Tab.TabType; +import static org.mozilla.gecko.mma.MmaDelegate.VISITING_A_WEBSITE_WITH_MATCH_TO_PAST_HISTORY; public class Tabs implements BundleEventListener { private static final String LOGTAG = "GeckoTabs"; @@ -1088,6 +1091,7 @@ public class Tabs implements BundleEventListener { if (!delayLoad && !background) { selectTab(tabToSelect.getId()); + tracking(url); } // Load favicon instantly for about:home page because it's already cached @@ -1095,9 +1099,17 @@ public class Tabs implements BundleEventListener { tabToSelect.loadFavicon(); } + return tabToSelect; } + private void tracking(String url) { + AddToHomeScreenPromotion.URLHistory history = AddToHomeScreenPromotion.getHistoryForURL(mAppContext, url); + if (history != null && history.visits > 0) { + MmaDelegate.track(VISITING_A_WEBSITE_WITH_MATCH_TO_PAST_HISTORY, history.visits); + } + } + /** * Opens a new tab and loads either about:home or, if PREFS_HOMEPAGE_FOR_EVERY_NEW_TAB is set, * the user's homepage. diff --git a/mobile/android/base/java/org/mozilla/gecko/Telemetry.java b/mobile/android/base/java/org/mozilla/gecko/Telemetry.java index 342445bf21c9..e74449cbd1c2 100644 --- a/mobile/android/base/java/org/mozilla/gecko/Telemetry.java +++ b/mobile/android/base/java/org/mozilla/gecko/Telemetry.java @@ -11,10 +11,18 @@ import org.mozilla.gecko.TelemetryContract.Event; import org.mozilla.gecko.TelemetryContract.Method; import org.mozilla.gecko.TelemetryContract.Reason; import org.mozilla.gecko.TelemetryContract.Session; +import org.mozilla.gecko.mma.MmaDelegate; import android.os.SystemClock; import android.util.Log; +import static org.mozilla.gecko.mma.MmaDelegate.INTERACT_WITH_SEARCH_URL_AREA; +import static org.mozilla.gecko.mma.MmaDelegate.LOAD_BOOKMARK; +import static org.mozilla.gecko.mma.MmaDelegate.SAVE_BOOKMARK; +import static org.mozilla.gecko.mma.MmaDelegate.SAVE_PASSWORD; +import static org.mozilla.gecko.mma.MmaDelegate.WHEN_USER_TAKE_A_SCREENSHOT; + + /** * All telemetry times are relative to one of two clocks: * @@ -208,6 +216,24 @@ public class Telemetry { String.class, eventName, String.class, method.toString(), timestamp, String.class, extras); } + mappingMmaTracking(eventName, method, extras); + } + + private static void mappingMmaTracking(String eventName, Method method, String extras) { + if (eventName == null || method == null || extras == null) { + return; + } + if (eventName.equalsIgnoreCase(Event.SAVE.toString()) && method == Method.MENU && extras.equals("bookmark")) { + MmaDelegate.track(SAVE_BOOKMARK); + } else if (eventName.equalsIgnoreCase(Event.LOAD_URL.toString()) && method == Method.LIST_ITEM && extras.equals("bookmarks")) { + MmaDelegate.track(LOAD_BOOKMARK); + } else if (eventName.equalsIgnoreCase(Event.SHOW.toString()) && method == Method.ACTIONBAR && extras.equals("urlbar-url")) { + MmaDelegate.track(INTERACT_WITH_SEARCH_URL_AREA); + } else if (eventName.equalsIgnoreCase(Event.SHARE.toString()) && method == Method.BUTTON && extras.equals("screenshot")) { + MmaDelegate.track(WHEN_USER_TAKE_A_SCREENSHOT); + } else if (eventName.equalsIgnoreCase(Event.ACTION.toString()) && method == Method.DOORHANGER && extras.equals("login-positive")) { + MmaDelegate.track(SAVE_PASSWORD); + } } public static void sendUIEvent(final Event event, final Method method, final long timestamp, diff --git a/mobile/android/base/java/org/mozilla/gecko/mma/MmaDelegate.java b/mobile/android/base/java/org/mozilla/gecko/mma/MmaDelegate.java index ba99cd173ebb..6009869b1d14 100644 --- a/mobile/android/base/java/org/mozilla/gecko/mma/MmaDelegate.java +++ b/mobile/android/base/java/org/mozilla/gecko/mma/MmaDelegate.java @@ -7,14 +7,37 @@ package org.mozilla.gecko.mma; import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.text.TextUtils; import android.util.Log; +import org.mozilla.gecko.Experiments; import org.mozilla.gecko.MmaConstants; import org.mozilla.gecko.PrefsHelper; +import org.mozilla.gecko.preferences.GeckoPreferences; +import org.mozilla.gecko.switchboard.SwitchBoard; + +import java.lang.ref.WeakReference; public class MmaDelegate { + public static final String LOADS_ARTICLES = "Loads articles"; + public static final String DOWNLOAD_VIDEOS_OR_ANY_OTHER_MEDIA = "Download videos or any other media"; + public static final String CLEAR_PRIVATE_DATA = "Clear Private Data"; + public static final String SAVE_BOOKMARK = "SaveBookmark"; + public static final String LOAD_BOOKMARK = "LoadBookmark"; + public static final String INTERACT_WITH_SEARCH_URL_AREA = "Interact with search url area"; + public static final String WHEN_USER_TAKE_A_SCREENSHOT = "When user take a screenshot"; + public static final String SAVE_PASSWORD = "SavePassword"; + public static final String VISITING_A_WEBSITE_WITH_MATCH_TO_PAST_HISTORY = "Visiting a website with match to past history"; + public static final String LAUNCH_BUT_NOT_DEFAULT_BROWSER = "Launch but not default browser"; + + private static final String TAG = "MmaDelegate"; private static final String KEY_PREF_BOOLEAN_MMA_ENABLED = "mma.enabled"; private static final String[] PREFS = { KEY_PREF_BOOLEAN_MMA_ENABLED }; @@ -22,9 +45,10 @@ public class MmaDelegate { private static boolean isGeckoPrefOn = false; private static MmaInterface mmaHelper = MmaConstants.getMma(); - + private static WeakReference applicationContext; public static void init(Activity activity) { + applicationContext = new WeakReference<>(activity.getApplicationContext()); setupPrefHandler(activity); } @@ -40,6 +64,9 @@ public class MmaDelegate { Log.d(TAG, "prefValue() called with: pref = [" + pref + "], value = [" + value + "]"); if (value) { mmaHelper.init(activity); + if (!isDefaultBrowser(activity)) { + mmaHelper.track(MmaDelegate.LAUNCH_BUT_NOT_DEFAULT_BROWSER); + } isGeckoPrefOn = true; } else { isGeckoPrefOn = false; @@ -50,15 +77,47 @@ public class MmaDelegate { PrefsHelper.addObserver(PREFS, handler); } + public static void track(String event) { - if (isGeckoPrefOn) { + if (isMmaEnabled()) { mmaHelper.track(event); } } public static void track(String event, long value) { - if (isGeckoPrefOn) { + if (isMmaEnabled()) { mmaHelper.track(event, value); } } + + private static boolean isMmaEnabled() { + if (applicationContext == null) { + return false; + } + + final Context context = applicationContext.get(); + 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); + + return inExperiment && healthReport && isGeckoPrefOn; + } + + + private 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())); + } + + } diff --git a/mobile/android/base/java/org/mozilla/gecko/mma/MmaLeanplumImp.java b/mobile/android/base/java/org/mozilla/gecko/mma/MmaLeanplumImp.java index 615bf5d8cf76..a706be24678d 100644 --- a/mobile/android/base/java/org/mozilla/gecko/mma/MmaLeanplumImp.java +++ b/mobile/android/base/java/org/mozilla/gecko/mma/MmaLeanplumImp.java @@ -7,17 +7,14 @@ package org.mozilla.gecko.mma; import android.app.Activity; -import android.app.Application; import android.content.Context; +import android.content.pm.PackageManager; import com.leanplum.Leanplum; import com.leanplum.LeanplumActivityHelper; -import com.leanplum.annotations.Parser; -import org.mozilla.gecko.ActivityHandlerHelper; import org.mozilla.gecko.AppConstants; import org.mozilla.gecko.MmaConstants; -import org.mozilla.gecko.util.ContextUtils; import java.util.HashMap; import java.util.Map; @@ -25,7 +22,10 @@ import java.util.Map; public class MmaLeanplumImp implements MmaInterface { @Override - public void init(Activity activity) { + public void init(final Activity activity) { + if (activity == null) { + return; + } Leanplum.setApplicationContext(activity.getApplicationContext()); LeanplumActivityHelper.enableLifecycleCallbacks(activity.getApplication()); @@ -36,18 +36,29 @@ public class MmaLeanplumImp implements MmaInterface { Leanplum.setAppIdForDevelopmentMode(MmaConstants.MOZ_LEANPLUM_SDK_CLIENTID, MmaConstants.MOZ_LEANPLUM_SDK_KEY); } - Map attributes = new HashMap<>(); - boolean installedFocus = ContextUtils.isPackageInstalled(activity, "org.mozilla.focus"); - boolean installedKlar = ContextUtils.isPackageInstalled(activity, "org.mozilla.klar"); + boolean installedFocus = isPackageInstalled(activity, "org.mozilla.focus"); + boolean installedKlar = isPackageInstalled(activity, "org.mozilla.klar"); if (installedFocus || installedKlar) { attributes.put("focus", "installed"); } Leanplum.start(activity, attributes); - Leanplum.track("Launch"); + // this is special to Leanplum. Since we defer LeanplumActivityHelper's onResume call till - // switchboard completes loading, we manually call it here. - LeanplumActivityHelper.onResume(activity); + // switchboard completes loading. We miss the call to LeanplumActivityHelper.onResume. + // So I manually call it here. + // + // There's a risk that if this is called after activity's onPause(Although I've + // tested it's seems okay). We should require their SDK to separate activity call back with + // SDK initialization and Activity lifecycle in the future. + // + // I put it under runOnUiThread because in current Leanplum's SDK design, this should be run in main thread. + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + LeanplumActivityHelper.onResume(activity); + } + }); } @Override @@ -71,4 +82,14 @@ public class MmaLeanplumImp implements MmaInterface { public void stop() { Leanplum.stop(); } + + private static boolean isPackageInstalled(final Context context, String packageName) { + try { + PackageManager pm = context.getPackageManager(); + pm.getPackageInfo(packageName, 0); + return true; + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } } diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/PrivateDataPreference.java b/mobile/android/base/java/org/mozilla/gecko/preferences/PrivateDataPreference.java index e622f1205572..c162b91b8589 100644 --- a/mobile/android/base/java/org/mozilla/gecko/preferences/PrivateDataPreference.java +++ b/mobile/android/base/java/org/mozilla/gecko/preferences/PrivateDataPreference.java @@ -8,6 +8,7 @@ package org.mozilla.gecko.preferences; import org.mozilla.gecko.EventDispatcher; import org.mozilla.gecko.Telemetry; import org.mozilla.gecko.TelemetryContract; +import org.mozilla.gecko.mma.MmaDelegate; import org.mozilla.gecko.util.GeckoBundle; import org.mozilla.gecko.icons.storage.DiskStorage; @@ -20,6 +21,9 @@ import android.content.Context; import android.util.AttributeSet; import android.util.Log; +import static org.mozilla.gecko.mma.MmaDelegate.CLEAR_PRIVATE_DATA; + + class PrivateDataPreference extends MultiPrefMultiChoicePreference { private static final String LOGTAG = "GeckoPrivateDataPreference"; private static final String PREF_KEY_PREFIX = "private.data."; @@ -58,5 +62,6 @@ class PrivateDataPreference extends MultiPrefMultiChoicePreference { // clear private data in gecko EventDispatcher.getInstance().dispatch("Sanitize:ClearData", data); + MmaDelegate.track(CLEAR_PRIVATE_DATA); } } diff --git a/mobile/android/base/java/org/mozilla/gecko/promotion/AddToHomeScreenPromotion.java b/mobile/android/base/java/org/mozilla/gecko/promotion/AddToHomeScreenPromotion.java index 9d9ebb519423..0d0b6363c880 100644 --- a/mobile/android/base/java/org/mozilla/gecko/promotion/AddToHomeScreenPromotion.java +++ b/mobile/android/base/java/org/mozilla/gecko/promotion/AddToHomeScreenPromotion.java @@ -36,7 +36,7 @@ import ch.boye.httpclientandroidlib.util.TextUtils; * Promote "Add to home screen" if user visits website often. */ public class AddToHomeScreenPromotion extends TabsTrayVisibilityAwareDelegate implements Tabs.OnTabsChangedListener { - private static class URLHistory { + public static class URLHistory { public final long visits; public final long lastVisit; @@ -213,7 +213,7 @@ public class AddToHomeScreenPromotion extends TabsTrayVisibilityAwareDelegate im return urlAnnotations.hasAcceptedOrDeclinedHomeScreenShortcut(context.getContentResolver(), url); } - protected URLHistory getHistoryForURL(Context context, String url) { + public static URLHistory getHistoryForURL(Context context, String url) { final GeckoProfile profile = GeckoProfile.get(context); final BrowserDB browserDB = BrowserDB.from(profile); diff --git a/mobile/android/chrome/content/Reader.js b/mobile/android/chrome/content/Reader.js index 50ab181348ad..c76b2372233f 100644 --- a/mobile/android/chrome/content/Reader.js +++ b/mobile/android/chrome/content/Reader.js @@ -195,11 +195,18 @@ var Reader = { if (browser.isArticle) { showPageAction("drawable://reader", Strings.reader.GetStringFromName("readerView.enter")); UITelemetry.addEvent("show.1", "button", null, "reader_available"); + this._sendMmaEvent("reader_available"); } else { UITelemetry.addEvent("show.1", "button", null, "reader_unavailable"); } }, + _sendMmaEvent: function(event) { + WindowEventDispatcher.sendRequest({ + type: "Mma:"+event, + }); + }, + _showSystemUI: function(visibility) { WindowEventDispatcher.sendRequest({ type: "SystemUI:Visibility", diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index ca391bd5fbef..5040dd50c3c2 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -888,11 +888,15 @@ var BrowserApp = { } }); + NativeWindow.contextmenus.add(stringGetter("contextmenu.saveImage"), NativeWindow.contextmenus.imageSaveableContext, function(aTarget) { UITelemetry.addEvent("action.1", "contextmenu", null, "web_save_image"); UITelemetry.addEvent("save.1", "contextmenu", null, "image"); + WindowEventDispatcher.sendRequest({ + type: "Mma:web_save_image", + }); RuntimePermissions.waitForPermissions(RuntimePermissions.WRITE_EXTERNAL_STORAGE).then(function(permissionGranted) { if (!permissionGranted) { @@ -938,6 +942,9 @@ var BrowserApp = { function(aTarget) { UITelemetry.addEvent("action.1", "contextmenu", null, "web_save_media"); UITelemetry.addEvent("save.1", "contextmenu", null, "media"); + WindowEventDispatcher.sendRequest({ + type: "Mma:web_save_media", + }); let url = aTarget.currentSrc || aTarget.src; diff --git a/mobile/android/config/proguard/proguard-leanplum.cfg b/mobile/android/config/proguard/proguard-leanplum.cfg index 928529cc404e..18e510d55eb7 100644 --- a/mobile/android/config/proguard/proguard-leanplum.cfg +++ b/mobile/android/config/proguard/proguard-leanplum.cfg @@ -336,9 +336,10 @@ -keep class com.leanplum.messagetemplates.BaseMessageOptions { *; } --dontwarn android.support.v7.** --keep class android.support.v7.** { *; } --keep interface android.support.v7.** { *; } +#-dontwarn android.support.v7.** +-keep class android.support.v7.app.AppCompatActivity +-keep class android.support.v7.app.ActionBarActivity +#-keep interface android.support.v7.** { *; } -printmapping out.map -renamesourcefileattribute SourceFile