// Copyright 2015 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.metrics; import android.content.ComponentCallbacks; import android.content.Context; import android.content.res.Configuration; import org.chromium.base.ContextUtils; import org.chromium.chrome.browser.preferences.privacy.CrashReportingPermissionManager; import org.chromium.chrome.browser.preferences.privacy.PrivacyPreferencesManager; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tabmodel.TabModel; import org.chromium.chrome.browser.tabmodel.TabModelSelector; import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver; import org.chromium.content_public.browser.WebContents; /** * Mainly sets up session stats for chrome. A session is defined as the duration when the * application is in the foreground. Also used to communicate information between Chrome * and the framework's MetricService. */ public class UmaSessionStats { public static final String LAST_USED_TIME_PREF = "umasessionstats.lastusedtime"; private static final String SAMSUNG_MULTWINDOW_PACKAGE = "com.sec.feature.multiwindow"; private static long sNativeUmaSessionStats = 0; // TabModelSelector is needed to get the count of open tabs. We want to log the number of open // tabs on every page load. private TabModelSelector mTabModelSelector; private TabModelSelectorTabObserver mTabModelSelectorTabObserver; private final Context mContext; private final boolean mIsMultiWindowCapable; private ComponentCallbacks mComponentCallbacks; private boolean mKeyboardConnected = false; private final CrashReportingPermissionManager mReportingPermissionManager; public UmaSessionStats(Context context) { mContext = context; mIsMultiWindowCapable = context.getPackageManager().hasSystemFeature( SAMSUNG_MULTWINDOW_PACKAGE); mReportingPermissionManager = PrivacyPreferencesManager.getInstance(); } private void recordPageLoadStats(Tab tab) { WebContents webContents = tab.getWebContents(); boolean isDesktopUserAgent = webContents != null && webContents.getNavigationController().getUseDesktopUserAgent(); nativeRecordPageLoaded(isDesktopUserAgent); if (mKeyboardConnected) { nativeRecordPageLoadedWithKeyboard(); } // If the session has ended (i.e. chrome is in the background), escape early. Ideally we // could track this number as part of either the previous or next session but this isn't // possible since the TabSelector is needed to figure out the current number of open tabs. if (mTabModelSelector == null) return; TabModel regularModel = mTabModelSelector.getModel(false); nativeRecordTabCountPerLoad(getTabCountFromModel(regularModel)); } private int getTabCountFromModel(TabModel model) { return model == null ? 0 : model.getCount(); } /** * Starts a new session for logging. * @param tabModelSelector A TabModelSelector instance for recording tab counts on page loads. * If null, UmaSessionStats does not record page loads and tab counts. */ public void startNewSession(TabModelSelector tabModelSelector) { ensureNativeInitialized(); mTabModelSelector = tabModelSelector; if (mTabModelSelector != null) { mComponentCallbacks = new ComponentCallbacks() { @Override public void onLowMemory() { // Not required } @Override public void onConfigurationChanged(Configuration newConfig) { mKeyboardConnected = newConfig.keyboard != Configuration.KEYBOARD_NOKEYS; } }; mContext.registerComponentCallbacks(mComponentCallbacks); mKeyboardConnected = mContext.getResources().getConfiguration() .keyboard != Configuration.KEYBOARD_NOKEYS; mTabModelSelectorTabObserver = new TabModelSelectorTabObserver(mTabModelSelector) { @Override public void onPageLoadFinished(Tab tab) { recordPageLoadStats(tab); } }; } nativeUmaResumeSession(sNativeUmaSessionStats); updatePreferences(); updateMetricsServiceState(); } private static void ensureNativeInitialized() { // Lazily create the native object and the notification handler. These objects are never // destroyed. if (sNativeUmaSessionStats == 0) { sNativeUmaSessionStats = nativeInit(); } } /** * Logs screen ratio on Samsung MultiWindow devices. */ public void logMultiWindowStats(int windowArea, int displayArea, int instanceCount) { if (mIsMultiWindowCapable) { if (displayArea == 0) return; int areaPercent = (windowArea * 100) / displayArea; int safePercent = areaPercent > 0 ? areaPercent : 0; nativeRecordMultiWindowSession(safePercent, instanceCount); } } /** * Logs the current session. */ public void logAndEndSession() { if (mTabModelSelector != null) { mContext.unregisterComponentCallbacks(mComponentCallbacks); mTabModelSelectorTabObserver.destroy(); mTabModelSelector = null; } nativeUmaEndSession(sNativeUmaSessionStats); ContextUtils.getAppSharedPreferences() .edit() .putLong(LAST_USED_TIME_PREF, System.currentTimeMillis()) .apply(); } public static void logRendererCrash() { nativeLogRendererCrash(); } /** * Updates the metrics services based on a change of consent. This can happen during first-run * flow, and when the user changes their preferences. */ public static void changeMetricsReportingConsent(boolean consent) { PrivacyPreferencesManager privacyManager = PrivacyPreferencesManager.getInstance(); // Update the metrics reporting preference. privacyManager.setUsageAndCrashReporting(consent); // Perform native changes needed to reflect the new consent value. nativeChangeMetricsReportingConsent(consent); updateMetricsServiceState(); } /** * Updates the state of MetricsService to account for the user's preferences. */ public static void updateMetricsServiceState() { PrivacyPreferencesManager privacyManager = PrivacyPreferencesManager.getInstance(); // Ensure Android and Chrome local state prefs are in sync. privacyManager.syncUsageAndCrashReportingPrefs(); boolean mayUploadStats = privacyManager.isUmaUploadPermitted(); // Re-start the MetricsService with the given parameter, and current consent. nativeUpdateMetricsServiceState(mayUploadStats); } /** * Updates relevant Android and native preferences. */ private void updatePreferences() { PrivacyPreferencesManager prefManager = PrivacyPreferencesManager.getInstance(); prefManager.migrateUsageAndCrashPreferences(); // Update the metrics sampling state so it's available before the native feature list is // available. prefManager.setClientInMetricsSample(UmaUtils.isClientInMetricsReportingSample()); // Make sure preferences are in sync. prefManager.syncUsageAndCrashReportingPrefs(); } public static void registerExternalExperiment(String studyName, int[] experimentIds) { nativeRegisterExternalExperiment(studyName, experimentIds); } public static void registerSyntheticFieldTrial(String trialName, String groupName) { nativeRegisterSyntheticFieldTrial(trialName, groupName); } private static native long nativeInit(); private static native void nativeChangeMetricsReportingConsent(boolean consent); private static native void nativeUpdateMetricsServiceState(boolean mayUpload); private native void nativeUmaResumeSession(long nativeUmaSessionStats); private native void nativeUmaEndSession(long nativeUmaSessionStats); private static native void nativeLogRendererCrash(); private static native void nativeRegisterExternalExperiment( String studyName, int[] experimentIds); private static native void nativeRegisterSyntheticFieldTrial( String trialName, String groupName); private static native void nativeRecordMultiWindowSession(int areaPercent, int instanceCount); private static native void nativeRecordTabCountPerLoad(int numTabsOpen); private static native void nativeRecordPageLoaded(boolean isDesktopUserAgent); private static native void nativeRecordPageLoadedWithKeyboard(); }