blob: 91841b7f39b904da93eeb29a6be1b67772693b9b [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.website;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Process;
import android.preference.Preference;
import android.provider.Settings;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.ContentSettingsType;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
import org.chromium.ui.text.SpanApplier;
import org.chromium.ui.text.SpanApplier.SpanInfo;
/**
* A base class for dealing with website settings categories.
*/
public class SiteSettingsCategory {
// Valid values for passing to fromString() in this class.
public static final String CATEGORY_ALL_SITES = "all_sites";
public static final String CATEGORY_ADS = "ads";
public static final String CATEGORY_AUTOPLAY = "autoplay";
public static final String CATEGORY_BACKGROUND_SYNC = "background_sync";
public static final String CATEGORY_CAMERA = "camera";
public static final String CATEGORY_CLIPBOARD = "clipboard";
public static final String CATEGORY_COOKIES = "cookies";
public static final String CATEGORY_DEVICE_LOCATION = "device_location";
public static final String CATEGORY_JAVASCRIPT = "javascript";
public static final String CATEGORY_MICROPHONE = "microphone";
public static final String CATEGORY_NOTIFICATIONS = "notifications";
public static final String CATEGORY_POPUPS = "popups";
public static final String CATEGORY_PROTECTED_MEDIA = "protected_content";
public static final String CATEGORY_SOUND = "sound";
public static final String CATEGORY_USE_STORAGE = "use_storage";
public static final String CATEGORY_USB = "usb";
// The id of this category.
private String mCategory;
// The id of a permission in Android M that governs this category. Can be blank if Android has
// no equivalent permission for the category.
private String mAndroidPermission;
// The content settings type that this category represents. Can be -1 if the category has no
// content settings type (such as All Sites).
private int mContentSettingsType = -1;
/**
* Construct a SiteSettingsCategory.
* @param category The string id of the category to construct.
* @param androidPermission A string containing the id of a toggle-able permission in Android
* that this category represents (or blank, if Android does not expose that permission).
* @param contentSettingsType The content settings type that this category represents (or -1
* if the category does not have a contentSettingsType, such as All Sites).
*/
protected SiteSettingsCategory(
String category, String androidPermission, int contentSettingsType) {
mCategory = category;
mAndroidPermission = androidPermission;
mContentSettingsType = contentSettingsType;
}
/**
* Construct a SiteSettingsCategory from a string.
* @param category The string id of the category to construct. See valid values above.
*/
public static SiteSettingsCategory fromString(String category) {
assert !category.isEmpty();
if (CATEGORY_ALL_SITES.equals(category)) {
return new SiteSettingsCategory(CATEGORY_ALL_SITES, "", -1);
}
if (CATEGORY_ADS.equals(category) && adsCategoryEnabled()) {
return new SiteSettingsCategory(
CATEGORY_ADS, "", ContentSettingsType.CONTENT_SETTINGS_TYPE_ADS);
}
if (CATEGORY_AUTOPLAY.equals(category)) {
return new SiteSettingsCategory(CATEGORY_AUTOPLAY, "",
ContentSettingsType.CONTENT_SETTINGS_TYPE_AUTOPLAY);
}
if (CATEGORY_BACKGROUND_SYNC.equals(category)) {
return new SiteSettingsCategory(CATEGORY_BACKGROUND_SYNC, "",
ContentSettingsType.CONTENT_SETTINGS_TYPE_BACKGROUND_SYNC);
}
if (CATEGORY_CAMERA.equals(category)) {
return new SiteSettingsCategory(
SiteSettingsCategory.CATEGORY_CAMERA,
android.Manifest.permission.CAMERA,
ContentSettingsType.CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA);
}
if (CATEGORY_CLIPBOARD.equals(category)) {
return new SiteSettingsCategory(CATEGORY_CLIPBOARD, "",
ContentSettingsType.CONTENT_SETTINGS_TYPE_CLIPBOARD_READ);
}
if (CATEGORY_COOKIES.equals(category)) {
return new SiteSettingsCategory(CATEGORY_COOKIES, "",
ContentSettingsType.CONTENT_SETTINGS_TYPE_COOKIES);
}
if (CATEGORY_JAVASCRIPT.equals(category)) {
return new SiteSettingsCategory(CATEGORY_JAVASCRIPT, "",
ContentSettingsType.CONTENT_SETTINGS_TYPE_JAVASCRIPT);
}
if (CATEGORY_DEVICE_LOCATION.equals(category)) {
return new LocationCategory();
}
if (CATEGORY_MICROPHONE.equals(category)) {
return new SiteSettingsCategory(
SiteSettingsCategory.CATEGORY_MICROPHONE,
android.Manifest.permission.RECORD_AUDIO,
ContentSettingsType.CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC);
}
if (CATEGORY_NOTIFICATIONS.equals(category)) {
return new SiteSettingsCategory(CATEGORY_NOTIFICATIONS, "",
ContentSettingsType.CONTENT_SETTINGS_TYPE_NOTIFICATIONS);
}
if (CATEGORY_POPUPS.equals(category)) {
return new SiteSettingsCategory(CATEGORY_POPUPS, "",
ContentSettingsType.CONTENT_SETTINGS_TYPE_POPUPS);
}
if (CATEGORY_PROTECTED_MEDIA.equals(category)) {
return new SiteSettingsCategory(CATEGORY_PROTECTED_MEDIA, "",
ContentSettingsType.CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER);
}
if (CATEGORY_SOUND.equals(category)) {
return new SiteSettingsCategory(
CATEGORY_SOUND, "", ContentSettingsType.CONTENT_SETTINGS_TYPE_SOUND);
}
if (CATEGORY_USE_STORAGE.equals(category)) {
return new SiteSettingsCategory(CATEGORY_USE_STORAGE, "", -1);
}
if (CATEGORY_USB.equals(category)) {
return new SiteSettingsCategory(
CATEGORY_USB, "", ContentSettingsType.CONTENT_SETTINGS_TYPE_USB_CHOOSER_DATA);
}
return null;
}
/**
* Construct a SiteSettingsCategory from a content settings type. Note that not all categories
* are associated with a content settings type (e.g. All Sites). Such categories must be created
* fromString().
*/
public static SiteSettingsCategory fromContentSettingsType(int contentSettingsType) {
if (contentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_ADS) {
return fromString(CATEGORY_ADS);
}
if (contentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_AUTOPLAY) {
return fromString(CATEGORY_AUTOPLAY);
}
if (contentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_BACKGROUND_SYNC) {
return fromString(CATEGORY_BACKGROUND_SYNC);
}
if (contentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_CLIPBOARD_READ) {
return fromString(CATEGORY_CLIPBOARD);
}
if (contentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_COOKIES) {
return fromString(CATEGORY_COOKIES);
}
if (contentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_GEOLOCATION) {
return fromString(CATEGORY_DEVICE_LOCATION);
}
if (contentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_JAVASCRIPT) {
return fromString(CATEGORY_JAVASCRIPT);
}
if (contentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA) {
return fromString(CATEGORY_CAMERA);
}
if (contentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC) {
return fromString(CATEGORY_MICROPHONE);
}
if (contentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_NOTIFICATIONS) {
return fromString(CATEGORY_NOTIFICATIONS);
}
if (contentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_POPUPS) {
return fromString(CATEGORY_POPUPS);
}
if (contentSettingsType
== ContentSettingsType.CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER) {
return fromString(CATEGORY_PROTECTED_MEDIA);
}
if (contentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_SOUND) {
return fromString(CATEGORY_SOUND);
}
if (contentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_USB_CHOOSER_DATA) {
return fromString(CATEGORY_USB);
}
return null;
}
/**
* Returns the content settings type for this category, or -1 if no such type exists.
*/
public int toContentSettingsType() {
return mContentSettingsType;
}
/**
* Returns whether this category is the All Sites category.
*/
public boolean showAllSites() {
return CATEGORY_ALL_SITES.equals(mCategory);
}
/**
* Returns whether this category is the Ads category.
*/
public boolean showAdsSites() {
return mContentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_ADS;
}
/**
* Returns whether this category is the Autoplay category.
*/
public boolean showAutoplaySites() {
return mContentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_AUTOPLAY;
}
/**
* Returns whether this category is the Background Sync category.
*/
public boolean showBackgroundSyncSites() {
return mContentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_BACKGROUND_SYNC;
}
/**
* Returns whether this category is the Camera category.
*/
public boolean showCameraSites() {
return mContentSettingsType
== ContentSettingsType.CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA;
}
/**
* Returns whether this category is the Clipboard category.
*/
public boolean showClipboardSites() {
return mContentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_CLIPBOARD_READ;
}
/**
* Returns whether this category is the Cookies category.
*/
public boolean showCookiesSites() {
return mContentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_COOKIES;
}
/**
* Returns whether this category is the Geolocation category.
*/
public boolean showGeolocationSites() {
return mContentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_GEOLOCATION;
}
/**
* Returns whether this category is the JavaScript category.
*/
public boolean showJavaScriptSites() {
return mContentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_JAVASCRIPT;
}
/**
* Returns whether this category is the Microphone category.
*/
public boolean showMicrophoneSites() {
return mContentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC;
}
/**
* Returns whether this category is the Notifications category.
*/
public boolean showNotificationsSites() {
return mContentSettingsType
== ContentSettingsType.CONTENT_SETTINGS_TYPE_NOTIFICATIONS;
}
/**
* Returns whether this category is the Popup category.
*/
public boolean showPopupSites() {
return mContentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_POPUPS;
}
/**
* Returns whether this category is the Protected Media category.
*/
public boolean showProtectedMediaSites() {
return mContentSettingsType
== ContentSettingsType.CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER;
}
/**
* Returns whether this category is the Sound category.
*/
public boolean showSoundSites() {
return mContentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_SOUND;
}
/**
* Returns whether this category is the Storage category.
*/
public boolean showStorageSites() {
return CATEGORY_USE_STORAGE.equals(mCategory);
}
/**
* Returns whether this category is the USB category.
*/
public boolean showUsbDevices() {
return mContentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_USB_CHOOSER_DATA;
}
/**
* Returns whether the Ads category is enabled via an experiment flag.
*/
public static boolean adsCategoryEnabled() {
return ChromeFeatureList.isEnabled("SubresourceFilterExperimentalUI");
}
/**
* Returns whether the current category is managed either by enterprise policy or by the
* custodian of a supervised account.
*/
public boolean isManaged() {
PrefServiceBridge prefs = PrefServiceBridge.getInstance();
if (showBackgroundSyncSites()) return prefs.isBackgroundSyncManaged();
if (showCookiesSites()) return !prefs.isAcceptCookiesUserModifiable();
if (showGeolocationSites()) {
return !prefs.isAllowLocationUserModifiable();
}
if (showJavaScriptSites()) return prefs.javaScriptManaged();
if (showCameraSites()) return !prefs.isCameraUserModifiable();
if (showMicrophoneSites()) return !prefs.isMicUserModifiable();
if (showPopupSites()) return prefs.isPopupsManaged();
return false;
}
/**
* Returns whether the current category is managed by the custodian (e.g. parent, not an
* enterprise admin) of the account if the account is supervised.
*/
public boolean isManagedByCustodian() {
PrefServiceBridge prefs = PrefServiceBridge.getInstance();
if (showCookiesSites()) return prefs.isAcceptCookiesManagedByCustodian();
if (showGeolocationSites()) {
return prefs.isAllowLocationManagedByCustodian();
}
if (showCameraSites()) {
return prefs.isCameraManagedByCustodian();
}
if (showMicrophoneSites()) {
return prefs.isMicManagedByCustodian();
}
return false;
}
/**
* Configure a preference to show when when the Android permission for this category is
* disabled.
* @param osWarning A preference to hold the first permission warning. After calling this
* method, if osWarning has no title, the preference should not be added to the
* preference screen.
* @param osWarningExtra A preference to hold any additional permission warning (if any). After
* calling this method, if osWarningExtra has no title, the preference
* should not be added to the preference screen.
* @param activity The current activity.
* @param category The category associated with the warnings.
* @param specificCategory Whether the warnings refer to a single category or is an aggregate
* for many permissions.
*/
public void configurePermissionIsOffPreferences(Preference osWarning, Preference osWarningExtra,
Activity activity, boolean specificCategory) {
Intent perAppIntent = getIntentToEnableOsPerAppPermission(activity);
Intent globalIntent = getIntentToEnableOsGlobalPermission(activity);
String perAppMessage = getMessageForEnablingOsPerAppPermission(activity, !specificCategory);
String globalMessage = getMessageForEnablingOsGlobalPermission(activity);
Resources resources = activity.getResources();
int color = ApiCompatibilityUtils.getColor(resources, R.color.pref_accent_color);
ForegroundColorSpan linkSpan = new ForegroundColorSpan(color);
if (perAppIntent != null) {
SpannableString messageWithLink = SpanApplier.applySpans(
perAppMessage, new SpanInfo("<link>", "</link>", linkSpan));
osWarning.setTitle(messageWithLink);
osWarning.setIntent(perAppIntent);
if (!specificCategory) {
osWarning.setIcon(getDisabledInAndroidIcon(activity));
}
}
if (globalIntent != null) {
SpannableString messageWithLink = SpanApplier.applySpans(
globalMessage, new SpanInfo("<link>", "</link>", linkSpan));
osWarningExtra.setTitle(messageWithLink);
osWarningExtra.setIntent(globalIntent);
if (!specificCategory) {
if (perAppIntent == null) {
osWarningExtra.setIcon(getDisabledInAndroidIcon(activity));
} else {
Drawable transparent = new ColorDrawable(Color.TRANSPARENT);
osWarningExtra.setIcon(transparent);
}
}
}
}
/**
* Returns the icon for permissions that have been disabled by Android.
*/
Drawable getDisabledInAndroidIcon(Activity activity) {
Drawable icon = ApiCompatibilityUtils.getDrawable(activity.getResources(),
R.drawable.exclamation_triangle);
icon.mutate();
int disabledColor = ApiCompatibilityUtils.getColor(activity.getResources(),
R.color.pref_accent_color);
icon.setColorFilter(disabledColor, PorterDuff.Mode.SRC_IN);
return icon;
}
/**
* Returns whether the permission is enabled in Android, both globally and per-app. If the
* permission does not have a per-app setting or a global setting, true is assumed for either
* that is missing (or both).
*/
boolean enabledInAndroid(Context context) {
return enabledGlobally() && enabledForChrome(context);
}
/**
* Returns whether a permission is enabled across Android. Not all permissions can be disabled
* globally, so the default is true, but can be overwritten in sub-classes.
*/
protected boolean enabledGlobally() {
return true;
}
/**
* Returns whether a permission is enabled for Chrome specifically.
*/
protected boolean enabledForChrome(Context context) {
if (mAndroidPermission.isEmpty()) return true;
return permissionOnInAndroid(mAndroidPermission, context);
}
/**
* Returns whether to show the 'permission blocked' message. Majority of the time, that is
* warranted when the permission is either blocked per app or globally. But there are exceptions
* to this, so the sub-classes can overwrite.
*/
boolean showPermissionBlockedMessage(Context context) {
return !enabledForChrome(context) || !enabledGlobally();
}
/**
* Returns the OS Intent to use to enable a per-app permission, or null if the permission is
* already enabled. Android M and above provides two ways of doing this for some permissions,
* most notably Location, one that is per-app and another that is global.
*/
private Intent getIntentToEnableOsPerAppPermission(Context context) {
if (enabledForChrome(context)) return null;
return getAppInfoIntent(context);
}
/**
* Returns the OS Intent to use to enable a permission globally, or null if there is no global
* permission. Android M and above provides two ways of doing this for some permissions, most
* notably Location, one that is per-app and another that is global.
*/
protected Intent getIntentToEnableOsGlobalPermission(Context context) {
return null;
}
/**
* Returns the message to display when per-app permission is blocked.
* @param plural Whether it applies to one per-app permission or multiple.
*/
protected String getMessageForEnablingOsPerAppPermission(Activity activity, boolean plural) {
return activity.getResources().getString(plural
? R.string.android_permission_off_plural
: R.string.android_permission_off);
}
/**
* Returns the message to display when per-app permission is blocked.
*/
protected String getMessageForEnablingOsGlobalPermission(Activity activity) {
return null;
}
/**
* Returns an Intent to show the App Info page for the current app.
*/
private Intent getAppInfoIntent(Context context) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(
new Uri.Builder().scheme("package").opaquePart(context.getPackageName()).build());
return intent;
}
/**
* Returns whether a per-app permission is enabled.
* @param permission The string of the permission to check.
*/
private boolean permissionOnInAndroid(String permission, Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return true;
return PackageManager.PERMISSION_GRANTED == ApiCompatibilityUtils.checkPermission(
context, permission, Process.myPid(), Process.myUid());
}
}