// 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.datausage; import android.app.Activity; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.support.v7.app.AlertDialog; import android.text.TextUtils; import android.view.View; import android.widget.CheckBox; import android.widget.TextView; import org.chromium.base.ContextUtils; import org.chromium.base.FieldTrialList; import org.chromium.base.metrics.RecordHistogram; import org.chromium.chrome.R; import org.chromium.chrome.browser.EmbedContentViewActivity; import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.sessions.SessionTabHelper; import org.chromium.chrome.browser.tab.Tab; import org.chromium.components.variations.VariationsAssociatedData; import org.chromium.content_public.browser.LoadUrlParams; import org.chromium.content_public.browser.WebContents; import org.chromium.content_public.common.Referrer; /** * Entry point to manage all UI details for measuring data use within a Tab. */ public class DataUseTabUIManager { private static final String SHARED_PREF_DATA_USE_DIALOG_OPT_OUT = "data_use_dialog_opt_out"; private static final String DATA_USE_FIELD_TRIAL = "ExternalDataUseObserver"; /** * Data use started UI snackbar will not be shown if {@link DISABLE_DATA_USE_STARTED_UI_PARAM} * fieldtrial parameter is set to {@value DISABLE_DATA_USE_UI_PARAM_VALUE}. */ private static final String DISABLE_DATA_USE_STARTED_UI_PARAM = "disable_data_use_started_ui"; /** * Data use ended UI snackbar/dialog will not be shown if {@link * DISABLE_DATA_USE_ENDED_UI_PARAM} fieldtrial parameter is set to * {@value DISABLE_DATA_USE_UI_PARAM_VALUE}. */ private static final String DISABLE_DATA_USE_ENDED_UI_PARAM = "disable_data_use_ended_ui"; /** * Data use ended dialog will not be shown if {@link DISABLE_DATA_USE_ENDED_DIALOG_PARAM} * fieldtrial parameter is set to {@value DISABLE_DATA_USE_UI_PARAM_VALUE}. */ private static final String DISABLE_DATA_USE_ENDED_DIALOG_PARAM = "disable_data_use_ended_dialog"; private static final String DISABLE_DATA_USE_UI_PARAM_VALUE = "true"; /** * Represents the possible user actions with the data use snackbars and dialog. This must * remain in sync with DataUsage.UIAction in tools/metrics/histograms/histograms.xml. */ public static class DataUsageUIAction { public static final int STARTED_SNACKBAR_SHOWN = 0; public static final int STARTED_SNACKBAR_MORE_CLICKED = 1; public static final int ENDED_SNACKBAR_SHOWN = 2; public static final int ENDED_SNACKBAR_MORE_CLICKED = 3; public static final int DIALOG_SHOWN = 4; public static final int DIALOG_CONTINUE_CLICKED = 5; public static final int DIALOG_CANCEL_CLICKED = 6; public static final int DIALOG_LEARN_MORE_CLICKED = 7; public static final int DIALOG_OPTED_OUT = 8; public static final int INDEX_BOUNDARY = 9; } /** * Returns true if data use tracking has started within a Tab. When data use tracking has * started, returns true only once to signify the started event. * * @param tab The tab that may have started tracking data use. * @return true If data use tracking has indeed started. */ public static boolean checkAndResetDataUseTrackingStarted(Tab tab) { return nativeCheckAndResetDataUseTrackingStarted( SessionTabHelper.sessionIdForTab(tab.getWebContents()), tab.getProfile()); } /** * Notifies that the user clicked "Continue" when the dialog box warning about exiting data use * was shown. * * @param tab The tab on which the dialog box was shown. */ public static void userClickedContinueOnDialogBox(Tab tab) { nativeUserClickedContinueOnDialogBox( SessionTabHelper.sessionIdForTab(tab.getWebContents()), tab.getProfile()); } /** * Returns true if data use tracking is currently active on {@link tab} but will stop if the * navigation continues. Should only be called before the navigation starts. * * @param tab The tab that is being queried for data use tracking. * @param pageTransitionType transition type of the navigation * @param packageName package name of the app package that started this navigation. * @return true If {@link tab} is currently tracked but would stop if the navigation were to * continue. */ public static boolean wouldDataUseTrackingEnd(Tab tab, String url, int pageTransitionType) { return nativeWouldDataUseTrackingEnd(tab.getWebContents(), SessionTabHelper.sessionIdForTab(tab.getWebContents()), url, pageTransitionType, tab.getProfile()); } /** * Returns true if data use tracking has ended within a Tab. When data use tracking has * ended, returns true only once to signify the ended event. * * @param tab The tab that may have ended tracking data use. * @return true If data use tracking has indeed ended. */ public static boolean checkAndResetDataUseTrackingEnded(Tab tab) { return nativeCheckAndResetDataUseTrackingEnded( SessionTabHelper.sessionIdForTab(tab.getWebContents()), tab.getProfile()); } /** * Tells native code that a custom tab is navigating to a url from the given client app package. * * @param tab The custom tab that is navigating. * @param packageName The client app package for the custom tab loading a url. * @param url URL that is being loaded in the custom tab. */ public static void onCustomTabInitialNavigation(Tab tab, String packageName, String url) { nativeOnCustomTabInitialNavigation(SessionTabHelper.sessionIdForTab(tab.getWebContents()), packageName, url, tab.getProfile()); } /** * Returns whether a navigation should be paused to show a dialog telling the user that data use * tracking has ended within a Tab. If the navigation should be paused, shows a dialog with the * option to cancel the navigation or continue. * * @param activity Current activity. * @param tab The tab to see if tracking has ended in. * @param url URL that is pending. * @param pageTransitionType The type of transition. see * {@link org.chromium.content.browser.PageTransition} for valid values. * @param referrerUrl URL for the referrer. * @return true If the URL loading should be overriden. */ public static boolean shouldOverrideUrlLoading(Activity activity, final Tab tab, final String url, final int pageTransitionType, final String referrerUrl) { if (shouldShowDataUseEndedUI() && !shouldShowDataUseEndedSnackbar(activity) && wouldDataUseTrackingEnd(tab, url, pageTransitionType)) { startDataUseDialog(activity, tab, url, pageTransitionType, referrerUrl); return true; } return false; } /** * Shows a dialog with the option to cancel the navigation or continue. Also allows the user to * opt out of seeing this dialog again. * * @param activity Current activity. * @param tab The tab loading the url. * @param url URL that is pending. * @param pageTransitionType The type of transition. see * {@link org.chromium.content.browser.PageTransition} for valid values. * @param referrerUrl URL for the referrer. */ private static void startDataUseDialog(final Activity activity, final Tab tab, final String url, final int pageTransitionType, final String referrerUrl) { View dataUseDialogView = View.inflate(activity, R.layout.data_use_dialog, null); final TextView textView = (TextView) dataUseDialogView.findViewById(R.id.data_use_message); textView.setText(getDataUseUIString(DataUseUIMessage.DATA_USE_TRACKING_ENDED_MESSAGE)); final CheckBox checkBox = (CheckBox) dataUseDialogView.findViewById(R.id.data_use_checkbox); checkBox.setText( getDataUseUIString(DataUseUIMessage.DATA_USE_TRACKING_ENDED_CHECKBOX_MESSAGE)); View learnMore = dataUseDialogView.findViewById(R.id.learn_more); learnMore.setOnClickListener(new android.view.View.OnClickListener() { @Override public void onClick(View v) { EmbedContentViewActivity.show(activity, getDataUseUIString(DataUseUIMessage.DATA_USE_LEARN_MORE_TITLE), getDataUseUIString(DataUseUIMessage.DATA_USE_LEARN_MORE_LINK_URL)); recordDataUseUIAction(DataUsageUIAction.DIALOG_LEARN_MORE_CLICKED); } }); new AlertDialog.Builder(activity, R.style.AlertDialogTheme) .setTitle(getDataUseUIString(DataUseUIMessage.DATA_USE_TRACKING_ENDED_TITLE)) .setView(dataUseDialogView) .setPositiveButton( getDataUseUIString(DataUseUIMessage.DATA_USE_TRACKING_ENDED_CONTINUE), new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { setOptedOutOfDataUseDialog(activity, checkBox.isChecked()); LoadUrlParams loadUrlParams = new LoadUrlParams(url, pageTransitionType); if (!TextUtils.isEmpty(referrerUrl)) { Referrer referrer = new Referrer(referrerUrl, Referrer.REFERRER_POLICY_ALWAYS); loadUrlParams.setReferrer(referrer); } tab.loadUrl(loadUrlParams); recordDataUseUIAction(DataUsageUIAction.DIALOG_CONTINUE_CLICKED); userClickedContinueOnDialogBox(tab); } }) .setNegativeButton(R.string.cancel, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { setOptedOutOfDataUseDialog(activity, checkBox.isChecked()); recordDataUseUIAction(DataUsageUIAction.DIALOG_CANCEL_CLICKED); } }) .show(); recordDataUseUIAction(DataUsageUIAction.DIALOG_SHOWN); } /** * @return true if the data use tracking started UI (snackbar) should be shown. */ public static boolean shouldShowDataUseStartedUI() { // UI should be shown only when field trial is active, not disabled in Finch and in // non-roaming-cellular connection. return FieldTrialList.trialExists(DATA_USE_FIELD_TRIAL) && !DISABLE_DATA_USE_UI_PARAM_VALUE.equals( VariationsAssociatedData.getVariationParamValue( DATA_USE_FIELD_TRIAL, DISABLE_DATA_USE_STARTED_UI_PARAM)) && nativeIsNonRoamingCellularConnection(); } /** * @return true if the data use tracking ended UI (snackbar or interstitial) should be shown. */ public static boolean shouldShowDataUseEndedUI() { // UI should be shown only when field trial is active, not disabled in Finch and in // non-roaming-cellular connection. return FieldTrialList.trialExists(DATA_USE_FIELD_TRIAL) && !DISABLE_DATA_USE_UI_PARAM_VALUE.equals( VariationsAssociatedData.getVariationParamValue( DATA_USE_FIELD_TRIAL, DISABLE_DATA_USE_ENDED_UI_PARAM)) && nativeIsNonRoamingCellularConnection(); } /** * Returns true if the data use ended snackbar should be shown instead of the dialog. The * snackbar will be shown if the user has opted out of seeing the data use ended dialog or if * the dialog is diabled by the fieldtrial. * * @param context An Android context. * @return true If the data use ended snackbar should be shown. */ public static boolean shouldShowDataUseEndedSnackbar(Context context) { assert shouldShowDataUseEndedUI(); return ContextUtils.getAppSharedPreferences().getBoolean( SHARED_PREF_DATA_USE_DIALOG_OPT_OUT, false) || DISABLE_DATA_USE_UI_PARAM_VALUE.equals( VariationsAssociatedData.getVariationParamValue( DATA_USE_FIELD_TRIAL, DISABLE_DATA_USE_ENDED_DIALOG_PARAM)); } /** * Sets whether the user has opted out of seeing the data use dialog. * * @param context An Android context. * @param optedOut Whether the user has opted out of seeing the data use dialog. */ private static void setOptedOutOfDataUseDialog(Context context, boolean optedOut) { ContextUtils.getAppSharedPreferences().edit() .putBoolean(SHARED_PREF_DATA_USE_DIALOG_OPT_OUT, optedOut) .apply(); if (optedOut) { recordDataUseUIAction(DataUsageUIAction.DIALOG_OPTED_OUT); } } /** * Record the DataUsage.UIAction histogram. * @param action Action with the data use tracking snackbar or dialog. */ public static void recordDataUseUIAction(int action) { assert action >= 0 && action < DataUsageUIAction.INDEX_BOUNDARY; RecordHistogram.recordEnumeratedHistogram( "DataUsage.UIAction", action, DataUsageUIAction.INDEX_BOUNDARY); } /** * Gets native strings which may be overridden by Finch. */ public static String getDataUseUIString(int messageID) { assert messageID >= 0 && messageID < DataUseUIMessage.DATA_USE_UI_MESSAGE_MAX; return nativeGetDataUseUIString(messageID); } private static native boolean nativeCheckAndResetDataUseTrackingStarted( int tabId, Profile profile); private static native boolean nativeCheckAndResetDataUseTrackingEnded( int tabId, Profile profile); private static native void nativeUserClickedContinueOnDialogBox(int tabId, Profile profile); private static native boolean nativeWouldDataUseTrackingEnd(WebContents webContents, int tabId, String url, int pageTransitionType, Profile jprofile); private static native void nativeOnCustomTabInitialNavigation(int tabID, String packageName, String url, Profile profile); private static native String nativeGetDataUseUIString(int messageID); private static native boolean nativeIsNonRoamingCellularConnection(); }