diff options
Diffstat (limited to 'mobile/android/base/java/org/mozilla/gecko/promotion/AddToHomeScreenPromotion.java')
-rw-r--r-- | mobile/android/base/java/org/mozilla/gecko/promotion/AddToHomeScreenPromotion.java | 237 |
1 files changed, 237 insertions, 0 deletions
diff --git a/mobile/android/base/java/org/mozilla/gecko/promotion/AddToHomeScreenPromotion.java b/mobile/android/base/java/org/mozilla/gecko/promotion/AddToHomeScreenPromotion.java new file mode 100644 index 000000000..c1eeb6bd5 --- /dev/null +++ b/mobile/android/base/java/org/mozilla/gecko/promotion/AddToHomeScreenPromotion.java @@ -0,0 +1,237 @@ +/* -*- 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.promotion; + +import android.app.Activity; +import android.content.Context; +import android.database.Cursor; +import android.os.Bundle; +import android.support.annotation.CallSuper; +import android.util.Log; + +import com.keepsafe.switchboard.SwitchBoard; + +import org.json.JSONException; +import org.json.JSONObject; +import org.mozilla.gecko.AboutPages; +import org.mozilla.gecko.BrowserApp; +import org.mozilla.gecko.GeckoProfile; +import org.mozilla.gecko.Tab; +import org.mozilla.gecko.Tabs; +import org.mozilla.gecko.db.BrowserContract; +import org.mozilla.gecko.db.BrowserDB; +import org.mozilla.gecko.db.UrlAnnotations; +import org.mozilla.gecko.delegates.TabsTrayVisibilityAwareDelegate; +import org.mozilla.gecko.Experiments; +import org.mozilla.gecko.util.ThreadUtils; + +import java.lang.ref.WeakReference; + +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 final long visits; + public final long lastVisit; + + private URLHistory(long visits, long lastVisit) { + this.visits = visits; + this.lastVisit = lastVisit; + } + } + + private static final String LOGTAG = "GeckoPromoteShortcut"; + + private static final String EXPERIMENT_MINIMUM_TOTAL_VISITS = "minimumTotalVisits"; + private static final String EXPERIMENT_LAST_VISIT_MINIMUM_AGE = "lastVisitMinimumAgeMs"; + private static final String EXPERIMENT_LAST_VISIT_MAXIMUM_AGE = "lastVisitMaximumAgeMs"; + + private WeakReference<Activity> activityReference; + private boolean isEnabled; + private int minimumVisits; + private int lastVisitMinimumAgeMs; + private int lastVisitMaximumAgeMs; + + @CallSuper + @Override + public void onCreate(BrowserApp browserApp, Bundle savedInstanceState) { + super.onCreate(browserApp, savedInstanceState); + activityReference = new WeakReference<Activity>(browserApp); + + initializeExperiment(browserApp); + } + + @Override + public void onResume(BrowserApp browserApp) { + Tabs.registerOnTabsChangedListener(this); + } + + @Override + public void onPause(BrowserApp browserApp) { + Tabs.unregisterOnTabsChangedListener(this); + } + + private void initializeExperiment(Context context) { + if (!SwitchBoard.isInExperiment(context, Experiments.PROMOTE_ADD_TO_HOMESCREEN)) { + Log.v(LOGTAG, "Experiment not enabled"); + // Experiment is not enabled. No need to try to read values. + return; + } + + JSONObject values = SwitchBoard.getExperimentValuesFromJson(context, Experiments.PROMOTE_ADD_TO_HOMESCREEN); + if (values == null) { + // We didn't get any values for this experiment. Let's disable it instead of picking default + // values that might be bad. + return; + } + + try { + initializeWithValues( + values.getInt(EXPERIMENT_MINIMUM_TOTAL_VISITS), + values.getInt(EXPERIMENT_LAST_VISIT_MINIMUM_AGE), + values.getInt(EXPERIMENT_LAST_VISIT_MAXIMUM_AGE)); + } catch (JSONException e) { + Log.w(LOGTAG, "Could not read experiment values", e); + } + } + + private void initializeWithValues(int minimumVisits, int lastVisitMinimumAgeMs, int lastVisitMaximumAgeMs) { + this.isEnabled = true; + + this.minimumVisits = minimumVisits; + this.lastVisitMinimumAgeMs = lastVisitMinimumAgeMs; + this.lastVisitMaximumAgeMs = lastVisitMaximumAgeMs; + } + + @Override + public void onTabChanged(final Tab tab, Tabs.TabEvents msg, String data) { + if (tab == null) { + return; + } + + if (!Tabs.getInstance().isSelectedTab(tab)) { + // We only ever want to show this promotion for the current tab. + return; + } + + if (Tabs.TabEvents.LOADED != msg) { + return; + } + + if (tab.isPrivate()) { + // Never show the prompt for private browsing tabs. + return; + } + + if (isTabsTrayVisible()) { + // We only want to show this prompt if this tab is in the foreground and not on top + // of the tabs tray. + return; + } + + ThreadUtils.postToBackgroundThread(new Runnable() { + @Override + public void run() { + maybeShowPromotionForUrl(tab.getURL(), tab.getTitle()); + } + }); + } + + private void maybeShowPromotionForUrl(String url, String title) { + if (!isEnabled) { + return; + } + + final Context context = activityReference.get(); + if (context == null) { + return; + } + + if (!shouldShowPromotion(context, url, title)) { + return; + } + + HomeScreenPrompt.show(context, url, title); + } + + private boolean shouldShowPromotion(Context context, String url, String title) { + if (TextUtils.isEmpty(url) || TextUtils.isEmpty(title)) { + // We require an URL and a title for the shortcut. + return false; + } + + if (AboutPages.isAboutPage(url)) { + // No promotion for our internal sites. + return false; + } + + if (!url.startsWith("https://")) { + // Only promote websites that are served over HTTPS. + return false; + } + + URLHistory history = getHistoryForURL(context, url); + if (history == null) { + // There's no history for this URL yet or we can't read it right now. Just ignore. + return false; + } + + if (history.visits < minimumVisits) { + // This URL has not been visited often enough. + return false; + } + + if (history.lastVisit > System.currentTimeMillis() - lastVisitMinimumAgeMs) { + // The last visit is too new. Do not show promotion. This is mostly to avoid that the + // promotion shows up for a quick refreshs and in the worst case the last visit could + // be the current visit (race). + return false; + } + + if (history.lastVisit < System.currentTimeMillis() - lastVisitMaximumAgeMs) { + // The last visit is to old. Do not show promotion. + return false; + } + + if (hasAcceptedOrDeclinedHomeScreenShortcut(context, url)) { + // The user has already created a shortcut in the past or actively declined to create one. + // Let's not ask again for this url - We do not want to be annoying. + return false; + } + + return true; + } + + protected boolean hasAcceptedOrDeclinedHomeScreenShortcut(Context context, String url) { + final UrlAnnotations urlAnnotations = BrowserDB.from(context).getUrlAnnotations(); + return urlAnnotations.hasAcceptedOrDeclinedHomeScreenShortcut(context.getContentResolver(), url); + } + + protected URLHistory getHistoryForURL(Context context, String url) { + final GeckoProfile profile = GeckoProfile.get(context); + final BrowserDB browserDB = BrowserDB.from(profile); + + Cursor cursor = null; + try { + cursor = browserDB.getHistoryForURL(context.getContentResolver(), url); + + if (cursor.moveToFirst()) { + return new URLHistory( + cursor.getInt(cursor.getColumnIndex(BrowserContract.History.VISITS)), + cursor.getLong(cursor.getColumnIndex(BrowserContract.History.DATE_LAST_VISITED))); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + + return null; + } +} |