Index: chrome/android/java/src/org/chromium/chrome/browser/ChromeActivitySessionTracker.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivitySessionTracker.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivitySessionTracker.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..482dd8bb555ab739e272604d9b63b1bb7b0fac50 |
--- /dev/null |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivitySessionTracker.java |
@@ -0,0 +1,242 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.chrome.browser; |
+ |
+import android.app.Activity; |
+import android.content.SharedPreferences; |
+import android.provider.Settings; |
+ |
+import org.chromium.base.ApplicationState; |
+import org.chromium.base.ApplicationStatus; |
+import org.chromium.base.ApplicationStatus.ApplicationStateListener; |
+import org.chromium.base.ContextUtils; |
+import org.chromium.base.ThreadUtils; |
+import org.chromium.base.VisibleForTesting; |
+import org.chromium.base.metrics.RecordHistogram; |
+import org.chromium.chrome.browser.accessibility.FontSizePrefs; |
+import org.chromium.chrome.browser.browsing_data.BrowsingDataType; |
+import org.chromium.chrome.browser.browsing_data.TimePeriod; |
+import org.chromium.chrome.browser.metrics.UmaUtils; |
+import org.chromium.chrome.browser.metrics.VariationsSession; |
+import org.chromium.chrome.browser.notifications.NotificationPlatformBridge; |
+import org.chromium.chrome.browser.partnercustomizations.PartnerBrowserCustomizations; |
+import org.chromium.chrome.browser.preferences.PrefServiceBridge; |
+import org.chromium.chrome.browser.share.ShareHelper; |
+import org.chromium.chrome.browser.tabmodel.TabModelSelector; |
+import org.chromium.chrome.browser.util.FeatureUtilities; |
+import org.chromium.content.browser.ChildProcessLauncher; |
+ |
+import java.lang.ref.WeakReference; |
+import java.util.Locale; |
+ |
+/** |
+ * Tracks the foreground session state for the Chrome activities. |
+ */ |
+public class ChromeActivitySessionTracker { |
+ |
+ private static final String PREF_LOCALE = "locale"; |
+ |
+ private static ChromeActivitySessionTracker sInstance; |
+ |
+ private final PowerBroadcastReceiver mPowerBroadcastReceiver = new PowerBroadcastReceiver(); |
+ |
+ // Used to trigger variation changes (such as seed fetches) upon application foregrounding. |
+ private VariationsSession mVariationsSession; |
+ |
+ private ChromeApplication mApplication; |
+ private boolean mIsInitialized; |
+ private boolean mIsStarted; |
+ private boolean mIsFinishedCachingNativeFlags; |
+ |
+ /** |
+ * @return The activity session tracker for Chrome. |
+ */ |
+ public static ChromeActivitySessionTracker getInstance() { |
+ ThreadUtils.assertOnUiThread(); |
+ |
+ if (sInstance == null) sInstance = new ChromeActivitySessionTracker(); |
+ return sInstance; |
+ } |
+ |
+ /** |
+ * Constructor exposed for extensibility only. |
+ * @see #getInstance() |
+ */ |
+ protected ChromeActivitySessionTracker() { |
+ mApplication = (ChromeApplication) ContextUtils.getApplicationContext(); |
+ } |
+ |
+ /** |
+ * Handle any initialization that occurs once native has been loaded. |
+ */ |
+ public void initializeWithNative() { |
+ ThreadUtils.assertOnUiThread(); |
+ |
+ if (mIsInitialized) return; |
+ mIsInitialized = true; |
+ assert !mIsStarted; |
+ |
+ ApplicationStatus.registerApplicationStateListener(createApplicationStateListener()); |
+ mVariationsSession = mApplication.createVariationsSession(); |
+ } |
+ |
+ /** |
+ * Each top-level activity (those extending {@link ChromeActivity}) should call this during |
+ * its onStart phase. When called for the first time, this marks the beginning of a foreground |
+ * session and calls onForegroundSessionStart(). Subsequent calls are noops until |
+ * onForegroundSessionEnd() is called, to handle changing top-level Chrome activities in one |
+ * foreground session. |
+ */ |
+ public void onStartWithNative() { |
+ ThreadUtils.assertOnUiThread(); |
+ |
+ if (mIsStarted) return; |
+ mIsStarted = true; |
+ |
+ assert mIsInitialized; |
+ |
+ onForegroundSessionStart(); |
+ cacheNativeFlags(); |
+ } |
+ |
+ /** |
+ * Called when a top-level Chrome activity (ChromeTabbedActivity, FullscreenActivity) is |
+ * started in foreground. It will not be called again when other Chrome activities take over |
+ * (see onStart()), that is, when correct activity calls startActivity() for another Chrome |
+ * activity. |
+ */ |
+ private void onForegroundSessionStart() { |
+ UmaUtils.recordForegroundStartTime(); |
+ ChildProcessLauncher.onBroughtToForeground(); |
+ updatePasswordEchoState(); |
+ FontSizePrefs.getInstance(mApplication).onSystemFontScaleChanged(); |
+ updateAcceptLanguages(); |
+ mVariationsSession.start(mApplication); |
+ mPowerBroadcastReceiver.onForegroundSessionStart(); |
+ |
+ // Track the ratio of Chrome startups that are caused by notification clicks. |
+ // TODO(johnme): Add other reasons (and switch to recordEnumeratedHistogram). |
+ RecordHistogram.recordBooleanHistogram( |
+ "Startup.BringToForegroundReason", |
+ NotificationPlatformBridge.wasNotificationRecentlyClicked()); |
+ } |
+ |
+ /** |
+ * Called when last of Chrome activities is stopped, ending the foreground session. This will |
+ * not be called when a Chrome activity is stopped because another Chrome activity takes over. |
+ * This is ensured by ActivityStatus, which switches to track new activity when its started and |
+ * will not report the old one being stopped (see createStateListener() below). |
+ */ |
+ private void onForegroundSessionEnd() { |
+ if (!mIsStarted) return; |
+ ChromeApplication.flushPersistentData(); |
+ mIsStarted = false; |
+ mPowerBroadcastReceiver.onForegroundSessionEnd(); |
+ |
+ ChildProcessLauncher.onSentToBackground(); |
+ IntentHandler.clearPendingReferrer(); |
+ IntentHandler.clearPendingIncognitoUrl(); |
+ |
+ int totalTabCount = 0; |
+ for (WeakReference<Activity> reference : ApplicationStatus.getRunningActivities()) { |
+ Activity activity = reference.get(); |
+ if (activity instanceof ChromeActivity) { |
+ TabModelSelector tabModelSelector = |
+ ((ChromeActivity) activity).getTabModelSelector(); |
+ if (tabModelSelector != null) { |
+ totalTabCount += tabModelSelector.getTotalTabCount(); |
+ } |
+ } |
+ } |
+ RecordHistogram.recordCountHistogram( |
+ "Tab.TotalTabCount.BeforeLeavingApp", totalTabCount); |
+ } |
+ |
+ private void onForegroundActivityDestroyed() { |
+ if (ApplicationStatus.isEveryActivityDestroyed()) { |
+ // These will all be re-initialized when a new Activity starts / upon next use. |
+ PartnerBrowserCustomizations.destroy(); |
+ ShareHelper.clearSharedImages(); |
+ } |
+ } |
+ |
+ private ApplicationStateListener createApplicationStateListener() { |
+ return new ApplicationStateListener() { |
+ @Override |
+ public void onApplicationStateChange(int newState) { |
+ if (newState == ApplicationState.HAS_STOPPED_ACTIVITIES) { |
+ onForegroundSessionEnd(); |
+ } else if (newState == ApplicationState.HAS_DESTROYED_ACTIVITIES) { |
+ onForegroundActivityDestroyed(); |
+ } |
+ } |
+ }; |
+ } |
+ |
+ /** |
+ * Update the accept languages after changing Android locale setting. Doing so kills the |
+ * Activities but it doesn't kill the Application, so this should be called in |
+ * {@link #onStart} instead of {@link #initialize}. |
+ */ |
+ private void updateAcceptLanguages() { |
+ PrefServiceBridge instance = PrefServiceBridge.getInstance(); |
+ String localeString = Locale.getDefault().toString(); // ex) en_US, de_DE, zh_CN_#Hans |
+ if (hasLocaleChanged(localeString)) { |
+ instance.resetAcceptLanguages(localeString); |
+ // Clear cache so that accept-languages change can be applied immediately. |
+ // TODO(changwan): The underlying BrowsingDataRemover::Remove() is an asynchronous call. |
+ // So cache-clearing may not be effective if URL rendering can happen before |
+ // OnBrowsingDataRemoverDone() is called, in which case we may have to reload as well. |
+ // Check if it can happen. |
+ instance.clearBrowsingData( |
+ null, new int[]{ BrowsingDataType.CACHE }, TimePeriod.ALL_TIME); |
+ } |
+ } |
+ |
+ private boolean hasLocaleChanged(String newLocale) { |
+ String previousLocale = ContextUtils.getAppSharedPreferences().getString( |
+ PREF_LOCALE, ""); |
+ |
+ if (!previousLocale.equals(newLocale)) { |
+ SharedPreferences prefs = ContextUtils.getAppSharedPreferences(); |
+ SharedPreferences.Editor editor = prefs.edit(); |
+ editor.putString(PREF_LOCALE, newLocale); |
+ editor.apply(); |
+ return true; |
+ } |
+ return false; |
+ } |
+ |
+ /** |
+ * Honor the Android system setting about showing the last character of a password for a short |
+ * period of time. |
+ */ |
+ private void updatePasswordEchoState() { |
+ boolean systemEnabled = Settings.System.getInt(mApplication.getContentResolver(), |
+ Settings.System.TEXT_SHOW_PASSWORD, 1) == 1; |
+ if (PrefServiceBridge.getInstance().getPasswordEchoEnabled() == systemEnabled) return; |
+ |
+ PrefServiceBridge.getInstance().setPasswordEchoEnabled(systemEnabled); |
+ } |
+ |
+ /** |
+ * Caches flags that are needed by Activities that launch before the native library is loaded |
+ * and stores them in SharedPreferences. Because this function is called during launch after the |
+ * library has loaded, they won't affect the next launch until Chrome is restarted. |
+ */ |
+ private void cacheNativeFlags() { |
+ if (mIsFinishedCachingNativeFlags) return; |
+ FeatureUtilities.cacheNativeFlags(mApplication); |
+ mIsFinishedCachingNativeFlags = true; |
+ } |
+ |
+ /** |
+ * @return The PowerBroadcastReceiver for the browser process. |
+ */ |
+ @VisibleForTesting |
+ public PowerBroadcastReceiver getPowerBroadcastReceiverForTesting() { |
+ return mPowerBroadcastReceiver; |
+ } |
+} |