blob: 1189e5a0fbeb80834992c3a127bfa8a1a3883936 [file] [log] [blame]
// 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.preferences.privacy;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import org.chromium.base.CommandLine;
import org.chromium.base.ContextUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.device.DeviceClassManager;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
import org.chromium.chrome.browser.survey.SurveyController;
import org.chromium.components.minidump_uploader.util.CrashReportingPermissionManager;
import org.chromium.components.minidump_uploader.util.NetworkPermissionUtil;
/**
* Reads, writes, and migrates preferences related to network usage and privacy.
*/
public class PrivacyPreferencesManager implements CrashReportingPermissionManager{
static final String DEPRECATED_PREF_CRASH_DUMP_UPLOAD = "crash_dump_upload";
static final String DEPRECATED_PREF_CRASH_DUMP_UPLOAD_NO_CELLULAR =
"crash_dump_upload_no_cellular";
private static final String DEPRECATED_PREF_CELLULAR_EXPERIMENT = "cellular_experiment";
private static final String DEPRECATED_PREF_PHYSICAL_WEB = "physical_web";
private static final String DEPRECATED_PREF_PHYSICAL_WEB_SHARING = "physical_web_sharing";
private static final String DEPRECATED_PREF_PHYSICAL_WEB_HAS_DEFERRED_METRICS_KEY =
"PhysicalWeb.HasDeferredMetrics";
private static final String DEPRECATED_PREF_PHYSICAL_WEB_OPT_IN_DECLINE_BUTTON_PRESS_COUNT =
"PhysicalWeb.OptIn.DeclineButtonPressed";
private static final String DEPRECATED_PREF_PHYSICAL_WEB_OPT_IN_ENABLE_BUTTON_PRESS_COUNT =
"PhysicalWeb.OptIn.EnableButtonPressed";
private static final String DEPRECATED_PREF_PHYSICAL_WEB_PREFS_FEATURE_DISABLED_COUNT =
"PhysicalWeb.Prefs.FeatureDisabled";
private static final String DEPRECATED_PREF_PHYSICAL_WEB_PREFS_FEATURE_ENABLED_COUNT =
"PhysicalWeb.Prefs.FeatureEnabled";
private static final String DEPRECATED_PREF_PHYSICAL_WEB_PREFS_LOCATION_DENIED_COUNT =
"PhysicalWeb.Prefs.LocationDenied";
private static final String DEPRECATED_PREF_PHYSICAL_WEB_PREFS_LOCATION_GRANTED_COUNT =
"PhysicalWeb.Prefs.LocationGranted";
private static final String DEPRECATED_PREF_PHYSICAL_WEB_PWS_BACKGROUND_RESOLVE_TIMES =
"PhysicalWeb.ResolveTime.Background";
private static final String DEPRECATED_PREF_PHYSICAL_WEB_PWS_FOREGROUND_RESOLVE_TIMES =
"PhysicalWeb.ResolveTime.Foreground";
private static final String DEPRECATED_PREF_PHYSICAL_WEB_PWS_REFRESH_RESOLVE_TIMES =
"PhysicalWeb.ResolveTime.Refresh";
private static final String DEPRECATED_PREF_PHYSICAL_WEB_URL_SELECTED_COUNT =
"PhysicalWeb.UrlSelected";
private static final String DEPRECATED_PREF_PHYSICAL_WEB_TOTAL_URLS_INITIAL_COUNTS =
"PhysicalWeb.TotalUrls.OnInitialDisplay";
private static final String DEPRECATED_PREF_PHYSICAL_WEB_TOTAL_URLS_REFRESH_COUNTS =
"PhysicalWeb.TotalUrls.OnRefresh";
private static final String DEPRECATED_PREF_PHYSICAL_WEB_ACTIVITY_REFERRALS =
"PhysicalWeb.ActivityReferral";
private static final String DEPRECATED_PREF_PHYSICAL_WEB_PHYSICAL_WEB_STATE =
"PhysicalWeb.State";
public static final String PREF_METRICS_REPORTING = "metrics_reporting";
private static final String PREF_METRICS_IN_SAMPLE = "in_metrics_sample";
private static final String PREF_NETWORK_PREDICTIONS = "network_predictions";
private static final String PREF_BANDWIDTH_OLD = "prefetch_bandwidth";
private static final String PREF_BANDWIDTH_NO_CELLULAR_OLD = "prefetch_bandwidth_no_cellular";
private static final String ALLOW_PRERENDER_OLD = "allow_prefetch";
@SuppressLint("StaticFieldLeak")
private static PrivacyPreferencesManager sInstance;
private final Context mContext;
private final SharedPreferences mSharedPreferences;
@VisibleForTesting
PrivacyPreferencesManager(Context context) {
mContext = context;
mSharedPreferences = ContextUtils.getAppSharedPreferences();
migratePhysicalWebPreferences();
migrateUsageAndCrashPreferences();
}
public static PrivacyPreferencesManager getInstance() {
if (sInstance == null) {
sInstance = new PrivacyPreferencesManager(ContextUtils.getApplicationContext());
}
return sInstance;
}
// TODO(https://crbug.com/826540): Remove some time after 5/2019.
public void migratePhysicalWebPreferences() {
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.remove(DEPRECATED_PREF_PHYSICAL_WEB)
.remove(DEPRECATED_PREF_PHYSICAL_WEB_SHARING)
.remove(DEPRECATED_PREF_PHYSICAL_WEB_HAS_DEFERRED_METRICS_KEY)
.remove(DEPRECATED_PREF_PHYSICAL_WEB_OPT_IN_DECLINE_BUTTON_PRESS_COUNT)
.remove(DEPRECATED_PREF_PHYSICAL_WEB_OPT_IN_ENABLE_BUTTON_PRESS_COUNT)
.remove(DEPRECATED_PREF_PHYSICAL_WEB_PREFS_FEATURE_DISABLED_COUNT)
.remove(DEPRECATED_PREF_PHYSICAL_WEB_PREFS_FEATURE_ENABLED_COUNT)
.remove(DEPRECATED_PREF_PHYSICAL_WEB_PREFS_LOCATION_DENIED_COUNT)
.remove(DEPRECATED_PREF_PHYSICAL_WEB_PREFS_LOCATION_GRANTED_COUNT)
.remove(DEPRECATED_PREF_PHYSICAL_WEB_PWS_BACKGROUND_RESOLVE_TIMES)
.remove(DEPRECATED_PREF_PHYSICAL_WEB_PWS_FOREGROUND_RESOLVE_TIMES)
.remove(DEPRECATED_PREF_PHYSICAL_WEB_PWS_REFRESH_RESOLVE_TIMES)
.remove(DEPRECATED_PREF_PHYSICAL_WEB_URL_SELECTED_COUNT)
.remove(DEPRECATED_PREF_PHYSICAL_WEB_TOTAL_URLS_INITIAL_COUNTS)
.remove(DEPRECATED_PREF_PHYSICAL_WEB_TOTAL_URLS_REFRESH_COUNTS)
.remove(DEPRECATED_PREF_PHYSICAL_WEB_ACTIVITY_REFERRALS)
.remove(DEPRECATED_PREF_PHYSICAL_WEB_PHYSICAL_WEB_STATE)
.apply();
}
public void migrateUsageAndCrashPreferences() {
SharedPreferences.Editor editor = mSharedPreferences.edit();
if (mSharedPreferences.contains(DEPRECATED_PREF_CRASH_DUMP_UPLOAD)) {
String crashDumpNeverUpload = "crash_dump_never_upload";
setUsageAndCrashReporting(
!mSharedPreferences
.getString(DEPRECATED_PREF_CRASH_DUMP_UPLOAD, crashDumpNeverUpload)
.equals(crashDumpNeverUpload));
// Remove both this preference and the related one. If the related one is not removed
// now, later migrations could read from it and clobber the state.
editor.remove(DEPRECATED_PREF_CRASH_DUMP_UPLOAD);
if (mSharedPreferences.contains(DEPRECATED_PREF_CRASH_DUMP_UPLOAD_NO_CELLULAR)) {
editor.remove(DEPRECATED_PREF_CRASH_DUMP_UPLOAD_NO_CELLULAR);
}
} else if (mSharedPreferences.contains(DEPRECATED_PREF_CRASH_DUMP_UPLOAD_NO_CELLULAR)) {
setUsageAndCrashReporting(mSharedPreferences.getBoolean(
DEPRECATED_PREF_CRASH_DUMP_UPLOAD_NO_CELLULAR, false));
editor.remove(DEPRECATED_PREF_CRASH_DUMP_UPLOAD_NO_CELLULAR);
}
if (mSharedPreferences.contains(DEPRECATED_PREF_CELLULAR_EXPERIMENT)) {
editor.remove(DEPRECATED_PREF_CELLULAR_EXPERIMENT);
}
editor.apply();
}
/**
* Migrate and delete old preferences. Note that migration has to happen in Android-specific
* code because we need to access ALLOW_PRERENDER sharedPreference.
* TODO(bnc) https://crbug.com/394845. This change is planned for M38. After a year or so, it
* would be worth considering removing this migration code (also removing accessors in
* PrefServiceBridge and pref_service_bridge), and reverting to default for users
* who had set preferences but have not used Chrome for a year. This change would be subject to
* privacy review.
*/
public void migrateNetworkPredictionPreferences() {
PrefServiceBridge prefService = PrefServiceBridge.getInstance();
// See if PREF_NETWORK_PREDICTIONS is an old boolean value.
boolean predictionOptionIsBoolean = false;
try {
mSharedPreferences.getString(PREF_NETWORK_PREDICTIONS, "");
} catch (ClassCastException ex) {
predictionOptionIsBoolean = true;
}
// Nothing to do if the user or this migration code has already set the new
// preference.
if (!predictionOptionIsBoolean
&& prefService.obsoleteNetworkPredictionOptionsHasUserSetting()) {
return;
}
// Nothing to do if the old preferences are unset.
if (!predictionOptionIsBoolean
&& !mSharedPreferences.contains(PREF_BANDWIDTH_OLD)
&& !mSharedPreferences.contains(PREF_BANDWIDTH_NO_CELLULAR_OLD)) {
return;
}
// Migrate if the old preferences are at their default values.
// (Note that for PREF_BANDWIDTH*, if the setting is default, then there is no way to tell
// whether the user has set it.)
final String prefBandwidthDefault = BandwidthType.PRERENDER_ON_WIFI.title();
final String prefBandwidth =
mSharedPreferences.getString(PREF_BANDWIDTH_OLD, prefBandwidthDefault);
boolean prefBandwidthNoCellularDefault = true;
boolean prefBandwidthNoCellular = mSharedPreferences.getBoolean(
PREF_BANDWIDTH_NO_CELLULAR_OLD, prefBandwidthNoCellularDefault);
if (!(prefBandwidthDefault.equals(prefBandwidth))
|| (prefBandwidthNoCellular != prefBandwidthNoCellularDefault)) {
boolean newValue = true;
// Observe PREF_BANDWIDTH on mobile network capable devices.
if (isMobileNetworkCapable()) {
if (mSharedPreferences.contains(PREF_BANDWIDTH_OLD)) {
BandwidthType prefetchBandwidthTypePref = BandwidthType.getBandwidthFromTitle(
prefBandwidth);
if (BandwidthType.NEVER_PRERENDER.equals(prefetchBandwidthTypePref)) {
newValue = false;
} else if (BandwidthType.PRERENDER_ON_WIFI.equals(prefetchBandwidthTypePref)
|| BandwidthType.ALWAYS_PRERENDER.equals(prefetchBandwidthTypePref)) {
newValue = true;
}
}
// Observe PREF_BANDWIDTH_NO_CELLULAR on devices without mobile network.
} else {
if (mSharedPreferences.contains(PREF_BANDWIDTH_NO_CELLULAR_OLD)) {
newValue = prefBandwidthNoCellular;
}
}
// Save new value in Chrome PrefService.
prefService.setNetworkPredictionEnabled(newValue);
}
// Delete old sharedPreferences.
SharedPreferences.Editor sharedPreferencesEditor = mSharedPreferences.edit();
// Delete PREF_BANDWIDTH and PREF_BANDWIDTH_NO_CELLULAR: just migrated these options.
if (mSharedPreferences.contains(PREF_BANDWIDTH_OLD)) {
sharedPreferencesEditor.remove(PREF_BANDWIDTH_OLD);
}
if (mSharedPreferences.contains(PREF_BANDWIDTH_NO_CELLULAR_OLD)) {
sharedPreferencesEditor.remove(PREF_BANDWIDTH_NO_CELLULAR_OLD);
}
// Also delete ALLOW_PRERENDER, which was updated based on PREF_BANDWIDTH[_NO_CELLULAR] and
// network connectivity type, therefore does not carry additional information.
if (mSharedPreferences.contains(ALLOW_PRERENDER_OLD)) {
sharedPreferencesEditor.remove(ALLOW_PRERENDER_OLD);
}
// Delete bool PREF_NETWORK_PREDICTIONS so that string values can be stored. Note that this
// SharedPreference carries no information, because it used to be overwritten by
// kNetworkPredictionEnabled on startup, and now it is overwritten by
// kNetworkPredictionOptions on startup.
if (mSharedPreferences.contains(PREF_NETWORK_PREDICTIONS)) {
sharedPreferencesEditor.remove(PREF_NETWORK_PREDICTIONS);
}
sharedPreferencesEditor.apply();
}
protected boolean isNetworkAvailable() {
ConnectivityManager connectivityManager =
(ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
return (networkInfo != null && networkInfo.isConnected());
}
protected boolean isMobileNetworkCapable() {
ConnectivityManager connectivityManager =
(ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
// Android telephony team said it is OK to continue using getNetworkInfo() for our purposes.
// We cannot use ConnectivityManager#getAllNetworks() because that one only reports enabled
// networks. See crbug.com/532455.
@SuppressWarnings("deprecation")
NetworkInfo networkInfo = connectivityManager
.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
return networkInfo != null;
}
/**
* Checks whether prerender should be allowed and updates the preference if it is not set yet.
* @return Whether prerendering should be allowed.
*/
public boolean shouldPrerender() {
if (!DeviceClassManager.enablePrerendering()) return false;
migrateNetworkPredictionPreferences();
return PrefServiceBridge.getInstance().canPrefetchAndPrerender();
}
/**
* Sets the usage and crash reporting preference ON or OFF.
*
* @param enabled A boolean corresponding whether usage and crash reports uploads are allowed.
*/
public void setUsageAndCrashReporting(boolean enabled) {
mSharedPreferences.edit().putBoolean(PREF_METRICS_REPORTING, enabled).apply();
syncUsageAndCrashReportingPrefs();
if (!enabled) {
SurveyController.getInstance().clearCache(ContextUtils.getApplicationContext());
}
}
/**
* Update usage and crash preferences based on Android preferences if possible in case they are
* out of sync.
*/
public void syncUsageAndCrashReportingPrefs() {
if (PrefServiceBridge.isInitialized()) {
PrefServiceBridge.getInstance().setMetricsReportingEnabled(
isUsageAndCrashReportingPermittedByUser());
}
}
/**
* Sets whether this client is in-sample for usage metrics and crash reporting. See
* {@link org.chromium.chrome.browser.metrics.UmaUtils#isClientInMetricsSample} for details.
*/
public void setClientInMetricsSample(boolean inSample) {
mSharedPreferences.edit().putBoolean(PREF_METRICS_IN_SAMPLE, inSample).apply();
}
/**
* Checks whether this client is in-sample for usage metrics and crash reporting. See
* {@link org.chromium.chrome.browser.metrics.UmaUtils#isClientInMetricsSample} for details.
*
* @returns boolean Whether client is in-sample.
*/
@Override
public boolean isClientInMetricsSample() {
// The default value is true to avoid sampling out crashes that occur before native code has
// been initialized on first run. We'd rather have some extra crashes than none from that
// time.
return mSharedPreferences.getBoolean(PREF_METRICS_IN_SAMPLE, true);
}
/**
* Checks whether uploading of crash dumps is permitted for the available network(s).
*
* @return whether uploading crash dumps is permitted.
*/
@Override
public boolean isNetworkAvailableForCrashUploads() {
ConnectivityManager connectivityManager =
(ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
return NetworkPermissionUtil.isNetworkUnmetered(connectivityManager);
}
/**
* Checks whether uploading of usage metrics and crash dumps is currently permitted, based on
* user consent only. This doesn't take network condition or experimental state (i.e. disabling
* upload) into consideration. A crash dump may be retried if this check passes.
*
* @return whether the user has consented to reporting usage metrics and crash dumps.
*/
@Override
public boolean isUsageAndCrashReportingPermittedByUser() {
return mSharedPreferences.getBoolean(PREF_METRICS_REPORTING, false);
}
/**
* Check whether the command line switch is used to force uploading if at all possible. Used by
* test devices to avoid UI manipulation.
*
* @return whether uploading should be enabled if at all possible.
*/
@Override
public boolean isUploadEnabledForTests() {
return CommandLine.getInstance().hasSwitch(ChromeSwitches.FORCE_CRASH_DUMP_UPLOAD);
}
/**
* @return Whether uploading usage metrics is currently permitted.
*/
public boolean isMetricsUploadPermitted() {
return isNetworkAvailable()
&& (isUsageAndCrashReportingPermittedByUser() || isUploadEnabledForTests());
}
}