| // 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.content.Context; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.content.res.Resources; |
| import android.net.Uri; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.preference.ListPreference; |
| import android.preference.Preference; |
| import android.preference.Preference.OnPreferenceChangeListener; |
| import android.preference.Preference.OnPreferenceClickListener; |
| import android.preference.PreferenceFragment; |
| import android.preference.PreferenceScreen; |
| import android.provider.Settings; |
| import android.support.v7.app.AlertDialog; |
| import android.text.format.Formatter; |
| import android.widget.ListAdapter; |
| import android.widget.ListView; |
| |
| import org.chromium.base.VisibleForTesting; |
| import org.chromium.chrome.R; |
| import org.chromium.chrome.browser.ChromeFeatureList; |
| import org.chromium.chrome.browser.ContentSettingsType; |
| import org.chromium.chrome.browser.notifications.channels.SiteChannelsManager; |
| import org.chromium.chrome.browser.preferences.PrefServiceBridge; |
| import org.chromium.chrome.browser.preferences.PreferenceUtils; |
| import org.chromium.components.url_formatter.UrlFormatter; |
| |
| import java.net.URI; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.Set; |
| |
| /** |
| * Shows the permissions and other settings for a particular website. |
| */ |
| public class SingleWebsitePreferences extends PreferenceFragment |
| implements OnPreferenceChangeListener, OnPreferenceClickListener { |
| // SingleWebsitePreferences expects either EXTRA_SITE (a Website) or |
| // EXTRA_SITE_ADDRESS (a WebsiteAddress) to be present (but not both). If |
| // EXTRA_SITE is present, the fragment will display the permissions in that |
| // Website object. If EXTRA_SITE_ADDRESS is present, the fragment will find all |
| // permissions for that website address and display those. |
| public static final String EXTRA_SITE = "org.chromium.chrome.preferences.site"; |
| public static final String EXTRA_SITE_ADDRESS = "org.chromium.chrome.preferences.site_address"; |
| public static final String EXTRA_USB_INFO = "org.chromium.chrome.preferences.usb_info"; |
| |
| // Preference keys, see single_website_preferences.xml |
| // Headings: |
| public static final String PREF_SITE_TITLE = "site_title"; |
| public static final String PREF_USAGE = "site_usage"; |
| public static final String PREF_PERMISSIONS = "site_permissions"; |
| public static final String PREF_OS_PERMISSIONS_WARNING = "os_permissions_warning"; |
| public static final String PREF_OS_PERMISSIONS_WARNING_EXTRA = "os_permissions_warning_extra"; |
| public static final String PREF_OS_PERMISSIONS_WARNING_DIVIDER = |
| "os_permissions_warning_divider"; |
| public static final String PREF_INTRUSIVE_ADS_INFO = "intrusive_ads_info"; |
| public static final String PREF_INTRUSIVE_ADS_INFO_DIVIDER = "intrusive_ads_info_divider"; |
| // Actions at the top (if adding new, see hasUsagePreferences below): |
| public static final String PREF_CLEAR_DATA = "clear_data"; |
| // Buttons: |
| public static final String PREF_RESET_SITE = "reset_site_button"; |
| // Website permissions (if adding new, see hasPermissionsPreferences and resetSite below): |
| public static final String PREF_ADS_PERMISSION = "ads_permission_list"; |
| public static final String PREF_AUTOPLAY_PERMISSION = "autoplay_permission_list"; |
| public static final String PREF_BACKGROUND_SYNC_PERMISSION = "background_sync_permission_list"; |
| public static final String PREF_CAMERA_CAPTURE_PERMISSION = "camera_permission_list"; |
| public static final String PREF_CLIPBOARD_PERMISSION = "clipboard_permission_list"; |
| public static final String PREF_COOKIES_PERMISSION = "cookies_permission_list"; |
| public static final String PREF_JAVASCRIPT_PERMISSION = "javascript_permission_list"; |
| public static final String PREF_LOCATION_ACCESS = "location_access_list"; |
| public static final String PREF_MIC_CAPTURE_PERMISSION = "microphone_permission_list"; |
| public static final String PREF_MIDI_SYSEX_PERMISSION = "midi_sysex_permission_list"; |
| public static final String PREF_NOTIFICATIONS_PERMISSION = "push_notifications_list"; |
| public static final String PREF_POPUP_PERMISSION = "popup_permission_list"; |
| public static final String PREF_PROTECTED_MEDIA_IDENTIFIER_PERMISSION = |
| "protected_media_identifier_permission_list"; |
| public static final String PREF_SOUND_PERMISSION = "sound_permission_list"; |
| |
| // All permissions from the permissions preference category must be listed here. |
| // TODO(mvanouwerkerk): Use this array in more places to reduce verbosity. |
| private static final String[] PERMISSION_PREFERENCE_KEYS = { |
| PREF_AUTOPLAY_PERMISSION, |
| PREF_BACKGROUND_SYNC_PERMISSION, |
| PREF_CAMERA_CAPTURE_PERMISSION, |
| PREF_CLIPBOARD_PERMISSION, |
| PREF_COOKIES_PERMISSION, |
| PREF_JAVASCRIPT_PERMISSION, |
| PREF_LOCATION_ACCESS, |
| PREF_MIC_CAPTURE_PERMISSION, |
| PREF_MIDI_SYSEX_PERMISSION, |
| PREF_NOTIFICATIONS_PERMISSION, |
| PREF_POPUP_PERMISSION, |
| PREF_PROTECTED_MEDIA_IDENTIFIER_PERMISSION, |
| PREF_ADS_PERMISSION, |
| PREF_SOUND_PERMISSION, |
| }; |
| |
| private static final int REQUEST_CODE_NOTIFICATION_CHANNEL_SETTINGS = 1; |
| |
| // The website this page is displaying details about. |
| private Website mSite; |
| |
| // The number of USB device permissions displayed. |
| private int mUsbPermissionCount; |
| |
| private class SingleWebsitePermissionsPopulator |
| implements WebsitePermissionsFetcher.WebsitePermissionsCallback { |
| private final WebsiteAddress mSiteAddress; |
| |
| public SingleWebsitePermissionsPopulator(WebsiteAddress siteAddress) { |
| mSiteAddress = siteAddress; |
| } |
| |
| @Override |
| public void onWebsitePermissionsAvailable(Collection<Website> sites) { |
| // This method may be called after the activity has been destroyed. |
| // In that case, bail out. |
| if (getActivity() == null) return; |
| |
| // TODO(mvanouwerkerk): Avoid modifying the outer class from this inner class. |
| mSite = mergePermissionInfoForTopLevelOrigin(mSiteAddress, sites); |
| |
| displaySitePermissions(); |
| } |
| } |
| |
| /** |
| * Creates a Bundle with the correct arguments for opening this fragment for |
| * the website with the given url. |
| * |
| * @param url The URL to open the fragment with. This is a complete url including scheme, |
| * domain, port, path, etc. |
| * @return The bundle to attach to the preferences intent. |
| */ |
| public static Bundle createFragmentArgsForSite(String url) { |
| Bundle fragmentArgs = new Bundle(); |
| // TODO(mvanouwerkerk): Define a pure getOrigin method in UrlUtilities that is the |
| // equivalent of the call below, because this is perfectly fine for non-display purposes. |
| String origin = |
| UrlFormatter.formatUrlForSecurityDisplay(URI.create(url), true /* showScheme */); |
| fragmentArgs.putSerializable(EXTRA_SITE_ADDRESS, WebsiteAddress.create(origin)); |
| return fragmentArgs; |
| } |
| |
| @Override |
| public void onActivityCreated(Bundle savedInstanceState) { |
| getActivity().setTitle(R.string.prefs_site_settings); |
| ListView listView = (ListView) getView().findViewById(android.R.id.list); |
| listView.setDivider(null); |
| |
| Object extraSite = getArguments().getSerializable(EXTRA_SITE); |
| Object extraSiteAddress = getArguments().getSerializable(EXTRA_SITE_ADDRESS); |
| |
| if (extraSite != null && extraSiteAddress == null) { |
| mSite = (Website) extraSite; |
| displaySitePermissions(); |
| } else if (extraSiteAddress != null && extraSite == null) { |
| WebsitePermissionsFetcher fetcher; |
| fetcher = new WebsitePermissionsFetcher( |
| new SingleWebsitePermissionsPopulator((WebsiteAddress) extraSiteAddress)); |
| fetcher.fetchAllPreferences(); |
| } else { |
| assert false : "Exactly one of EXTRA_SITE or EXTRA_SITE_ADDRESS must be provided."; |
| } |
| |
| super.onActivityCreated(savedInstanceState); |
| } |
| |
| /** |
| * Given an address and a list of sets of websites, returns a new site with the same origin |
| * as |address| which has merged into it the permissions of the matching input sites. If a |
| * permission is found more than once, the one found first is used and the latter are ignored. |
| * This should not drop any relevant data as there should not be duplicates like that in the |
| * first place. |
| * |
| * @param address The address to search for. |
| * @param websites The websites to search in. |
| * @return The merged website. |
| */ |
| private static Website mergePermissionInfoForTopLevelOrigin( |
| WebsiteAddress address, Collection<Website> websites) { |
| String origin = address.getOrigin(); |
| String host = Uri.parse(origin).getHost(); |
| Website merged = new Website(address, null); |
| // This loop looks expensive, but the amount of data is likely to be relatively small |
| // because most sites have very few permissions. |
| for (Website other : websites) { |
| if (merged.getAdsException() == null && other.getAdsException() != null |
| && other.compareByAddressTo(merged) == 0) { |
| merged.setAdsException(other.getAdsException()); |
| } |
| if (merged.getClipboardInfo() == null && other.getClipboardInfo() != null |
| && permissionInfoIsForTopLevelOrigin(other.getClipboardInfo(), origin)) { |
| merged.setClipboardInfo(other.getClipboardInfo()); |
| } |
| if (merged.getGeolocationInfo() == null && other.getGeolocationInfo() != null |
| && permissionInfoIsForTopLevelOrigin(other.getGeolocationInfo(), origin)) { |
| merged.setGeolocationInfo(other.getGeolocationInfo()); |
| } |
| if (merged.getMidiInfo() == null && other.getMidiInfo() != null |
| && permissionInfoIsForTopLevelOrigin(other.getMidiInfo(), origin)) { |
| merged.setMidiInfo(other.getMidiInfo()); |
| } |
| if (merged.getProtectedMediaIdentifierInfo() == null |
| && other.getProtectedMediaIdentifierInfo() != null |
| && permissionInfoIsForTopLevelOrigin( |
| other.getProtectedMediaIdentifierInfo(), origin)) { |
| merged.setProtectedMediaIdentifierInfo(other.getProtectedMediaIdentifierInfo()); |
| } |
| if (merged.getNotificationInfo() == null |
| && other.getNotificationInfo() != null |
| && permissionInfoIsForTopLevelOrigin( |
| other.getNotificationInfo(), origin)) { |
| merged.setNotificationInfo(other.getNotificationInfo()); |
| } |
| if (merged.getCameraInfo() == null && other.getCameraInfo() != null |
| && permissionInfoIsForTopLevelOrigin( |
| other.getCameraInfo(), origin)) { |
| merged.setCameraInfo(other.getCameraInfo()); |
| } |
| if (merged.getMicrophoneInfo() == null && other.getMicrophoneInfo() != null |
| && permissionInfoIsForTopLevelOrigin(other.getMicrophoneInfo(), origin)) { |
| merged.setMicrophoneInfo(other.getMicrophoneInfo()); |
| } |
| if (merged.getLocalStorageInfo() == null |
| && other.getLocalStorageInfo() != null |
| && origin.equals(other.getLocalStorageInfo().getOrigin())) { |
| merged.setLocalStorageInfo(other.getLocalStorageInfo()); |
| } |
| for (StorageInfo storageInfo : other.getStorageInfo()) { |
| if (host.equals(storageInfo.getHost())) { |
| merged.addStorageInfo(storageInfo); |
| } |
| } |
| for (UsbInfo usbInfo : other.getUsbInfo()) { |
| if (origin.equals(usbInfo.getOrigin()) |
| && (usbInfo.getEmbedder() == null || usbInfo.getEmbedder().equals("*"))) { |
| merged.addUsbInfo(usbInfo); |
| } |
| } |
| if (host.equals(other.getAddress().getHost())) { |
| if (merged.getJavaScriptException() == null |
| && other.getJavaScriptException() != null) { |
| merged.setJavaScriptException(other.getJavaScriptException()); |
| } |
| if (merged.getSoundException() == null && other.getSoundException() != null) { |
| merged.setSoundException(other.getSoundException()); |
| } |
| if (merged.getAutoplayException() == null && other.getAutoplayException() != null) { |
| merged.setAutoplayException(other.getAutoplayException()); |
| } |
| if (merged.getBackgroundSyncException() == null |
| && other.getBackgroundSyncException() != null) { |
| merged.setBackgroundSyncException(other.getBackgroundSyncException()); |
| } |
| if (merged.getPopupException() == null && other.getPopupException() != null) { |
| merged.setPopupException(other.getPopupException()); |
| } |
| } |
| |
| // TODO(crbug.com/763982): Deal with this TODO colony. |
| // TODO(mvanouwerkerk): Make the various info types share a common interface that |
| // supports reading the origin or host. |
| // TODO(lshang): Merge in CookieException? It will use patterns. |
| } |
| return merged; |
| } |
| |
| private static boolean permissionInfoIsForTopLevelOrigin(PermissionInfo info, String origin) { |
| // TODO(mvanouwerkerk): Find a more generic place for this method. |
| return origin.equals(info.getOrigin()) |
| && (origin.equals(info.getEmbedderSafe()) || "*".equals(info.getEmbedderSafe())); |
| } |
| |
| /** |
| * Updates the permissions displayed in the UI by fetching them from mSite. |
| * Must only be called once mSite is set. |
| */ |
| private void displaySitePermissions() { |
| PreferenceUtils.addPreferencesFromResource(this, R.xml.single_website_preferences); |
| |
| Set<String> permissionPreferenceKeys = |
| new HashSet<>(Arrays.asList(PERMISSION_PREFERENCE_KEYS)); |
| int maxPermissionOrder = 0; |
| PreferenceScreen preferenceScreen = getPreferenceScreen(); |
| ListAdapter preferences = preferenceScreen.getRootAdapter(); |
| for (int i = 0; i < preferences.getCount(); ++i) { |
| Preference preference = (Preference) preferences.getItem(i); |
| setUpPreference(preference); |
| // Keep track of the maximum 'order' value of permission preferences, to allow correct |
| // positioning of subsequent permission preferences. |
| if (permissionPreferenceKeys.contains(preference.getKey())) { |
| maxPermissionOrder = Math.max(maxPermissionOrder, preference.getOrder()); |
| } |
| } |
| setUpUsbPreferences(maxPermissionOrder); |
| setUpOsWarningPreferences(); |
| |
| setUpAdsInformationalBanner(); |
| |
| // Remove categories if no sub-items. |
| if (!hasUsagePreferences()) { |
| Preference heading = preferenceScreen.findPreference(PREF_USAGE); |
| preferenceScreen.removePreference(heading); |
| } |
| if (!hasPermissionsPreferences()) { |
| Preference heading = preferenceScreen.findPreference(PREF_PERMISSIONS); |
| preferenceScreen.removePreference(heading); |
| } |
| } |
| |
| private void setUpPreference(Preference preference) { |
| String key = preference.getKey(); |
| if (PREF_SITE_TITLE.equals(key)) { |
| preference.setTitle(mSite.getTitle()); |
| } else if (PREF_CLEAR_DATA.equals(key)) { |
| setUpClearDataPreference(preference); |
| } else if (PREF_RESET_SITE.equals(key)) { |
| preference.setOnPreferenceClickListener(this); |
| } else if (PREF_ADS_PERMISSION.equals(key)) { |
| setUpAdsPreference(preference); |
| } else if (PREF_AUTOPLAY_PERMISSION.equals(key)) { |
| setUpListPreference(preference, mSite.getAutoplayPermission()); |
| } else if (PREF_BACKGROUND_SYNC_PERMISSION.equals(key)) { |
| setUpListPreference(preference, mSite.getBackgroundSyncPermission()); |
| } else if (PREF_CAMERA_CAPTURE_PERMISSION.equals(key)) { |
| setUpListPreference(preference, mSite.getCameraPermission()); |
| } else if (PREF_CLIPBOARD_PERMISSION.equals(key)) { |
| setUpListPreference(preference, mSite.getClipboardPermission()); |
| } else if (PREF_COOKIES_PERMISSION.equals(key)) { |
| setUpListPreference(preference, mSite.getCookiePermission()); |
| } else if (PREF_JAVASCRIPT_PERMISSION.equals(key)) { |
| setUpListPreference(preference, mSite.getJavaScriptPermission()); |
| } else if (PREF_LOCATION_ACCESS.equals(key)) { |
| setUpLocationPreference(preference); |
| } else if (PREF_MIC_CAPTURE_PERMISSION.equals(key)) { |
| setUpListPreference(preference, mSite.getMicrophonePermission()); |
| } else if (PREF_MIDI_SYSEX_PERMISSION.equals(key)) { |
| setUpListPreference(preference, mSite.getMidiPermission()); |
| } else if (PREF_NOTIFICATIONS_PERMISSION.equals(key)) { |
| setUpNotificationsPreference(preference); |
| } else if (PREF_POPUP_PERMISSION.equals(key)) { |
| setUpListPreference(preference, mSite.getPopupPermission()); |
| } else if (PREF_PROTECTED_MEDIA_IDENTIFIER_PERMISSION.equals(key)) { |
| setUpListPreference(preference, mSite.getProtectedMediaIdentifierPermission()); |
| } else if (PREF_SOUND_PERMISSION.equals(key)) { |
| setUpListPreference(preference, mSite.getSoundPermission()); |
| } |
| } |
| |
| private void setUpClearDataPreference(Preference preference) { |
| long usage = mSite.getTotalUsage(); |
| if (usage > 0) { |
| Context context = preference.getContext(); |
| preference.setTitle( |
| String.format(context.getString(R.string.origin_settings_storage_usage_brief), |
| Formatter.formatShortFileSize(context, usage))); |
| ((ClearWebsiteStorage) preference) |
| .setConfirmationListener(new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| clearStoredData(); |
| } |
| }); |
| } else { |
| getPreferenceScreen().removePreference(preference); |
| } |
| } |
| |
| private void setUpNotificationsPreference(Preference preference) { |
| final ContentSetting value = mSite.getNotificationPermission(); |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O |
| && ChromeFeatureList.isEnabled(ChromeFeatureList.SITE_NOTIFICATION_CHANNELS)) { |
| if (!(value == ContentSetting.ALLOW || value == ContentSetting.BLOCK)) { |
| // TODO(crbug.com/735110): Figure out if this is the correct thing to do, for values |
| // that are non-null, but not ALLOW or BLOCK either. (In setupListPreference we |
| // treat non-ALLOW settings as BLOCK, but here we are simply removing them.) |
| getPreferenceScreen().removePreference(preference); |
| return; |
| } |
| // On Android O this preference is read-only, so we replace the existing pref with a |
| // regular Preference that takes users to OS settings on click. |
| Preference newPreference = new Preference(preference.getContext()); |
| newPreference.setKey(preference.getKey()); |
| setUpPreferenceCommon(newPreference); |
| |
| if (isPermissionControlledByDSE( |
| ContentSettingsType.CONTENT_SETTINGS_TYPE_NOTIFICATIONS)) { |
| newPreference.setSummary(getResources().getString(value == ContentSetting.ALLOW |
| ? R.string.website_settings_permissions_allow_dse |
| : R.string.website_settings_permissions_block_dse)); |
| } else { |
| newPreference.setSummary( |
| getResources().getString(ContentSettingsResources.getSiteSummary(value))); |
| } |
| |
| newPreference.setDefaultValue(value); |
| |
| // This preference is read-only so should not attempt to persist to shared prefs. |
| newPreference.setPersistent(false); |
| |
| newPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { |
| @Override |
| public boolean onPreferenceClick(Preference preference) { |
| // There is no guarantee that a channel has been initialized yet for sites |
| // that were granted permission before the channel-initialization-on-grant |
| // code was in place. However, getChannelIdForOrigin will fall back to the |
| // generic Sites channel if no specific channel has been created for the given |
| // origin, so it is safe to open the channel settings for whatever channel ID |
| // it returns. |
| String channelId = SiteChannelsManager.getInstance().getChannelIdForOrigin( |
| mSite.getAddress().getOrigin()); |
| launchOsChannelSettings(preference.getContext(), channelId); |
| return true; |
| } |
| }); |
| newPreference.setOrder(preference.getOrder()); |
| getPreferenceScreen().removePreference(preference); |
| getPreferenceScreen().addPreference(newPreference); |
| } else { |
| setUpListPreference(preference, value); |
| if (isPermissionControlledByDSE(ContentSettingsType.CONTENT_SETTINGS_TYPE_NOTIFICATIONS) |
| && value != null) { |
| updatePreferenceForDSESetting(preference); |
| } |
| } |
| } |
| |
| private void launchOsChannelSettings(Context context, String channelId) { |
| Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS); |
| intent.putExtra(Settings.EXTRA_CHANNEL_ID, channelId); |
| intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName()); |
| startActivityForResult(intent, REQUEST_CODE_NOTIFICATION_CHANNEL_SETTINGS); |
| } |
| |
| /** |
| * If we are returning to Site Settings from another activity, the preferences displayed may be |
| * out of date. Here we refresh any we suspect may have changed. |
| */ |
| @Override |
| public void onActivityResult(int requestCode, int resultCode, Intent data) { |
| super.onActivityResult(requestCode, resultCode, data); |
| // The preference screen and mSite may be null if this activity was killed in the |
| // background, and the tasks scheduled from onActivityCreated haven't completed yet. Those |
| // tasks will take care of reinitializing everything afresh so there is no work to do here. |
| if (getPreferenceScreen() == null || mSite == null) { |
| return; |
| } |
| if (requestCode == REQUEST_CODE_NOTIFICATION_CHANNEL_SETTINGS) { |
| // User has navigated back from system channel settings on O+. Ensure notification |
| // preference is up to date, since they might have toggled it from channel settings. |
| Preference notificationsPreference = |
| getPreferenceScreen().findPreference(PREF_NOTIFICATIONS_PERMISSION); |
| if (notificationsPreference != null) { |
| setUpNotificationsPreference(notificationsPreference); |
| } |
| } |
| } |
| |
| private void setUpUsbPreferences(int maxPermissionOrder) { |
| for (UsbInfo info : mSite.getUsbInfo()) { |
| Preference preference = new Preference(getActivity()); |
| preference.getExtras().putSerializable(EXTRA_USB_INFO, info); |
| preference.setIcon(R.drawable.settings_usb); |
| preference.setOnPreferenceClickListener(this); |
| preference.setOrder(maxPermissionOrder); |
| preference.setTitle(info.getName()); |
| preference.setWidgetLayoutResource(R.layout.usb_permission); |
| getPreferenceScreen().addPreference(preference); |
| mUsbPermissionCount++; |
| } |
| } |
| |
| private void setUpOsWarningPreferences() { |
| PreferenceScreen preferenceScreen = getPreferenceScreen(); |
| SiteSettingsCategory categoryWithWarning = getWarningCategory(); |
| // Remove the 'permission is off in Android' message if not needed. |
| if (categoryWithWarning == null) { |
| preferenceScreen.removePreference( |
| preferenceScreen.findPreference(PREF_OS_PERMISSIONS_WARNING)); |
| preferenceScreen.removePreference( |
| preferenceScreen.findPreference(PREF_OS_PERMISSIONS_WARNING_EXTRA)); |
| preferenceScreen.removePreference( |
| preferenceScreen.findPreference(PREF_OS_PERMISSIONS_WARNING_DIVIDER)); |
| } else { |
| Preference osWarning = preferenceScreen.findPreference(PREF_OS_PERMISSIONS_WARNING); |
| Preference osWarningExtra = |
| preferenceScreen.findPreference(PREF_OS_PERMISSIONS_WARNING_EXTRA); |
| categoryWithWarning.configurePermissionIsOffPreferences( |
| osWarning, osWarningExtra, getActivity(), false); |
| if (osWarning.getTitle() == null) { |
| preferenceScreen.removePreference( |
| preferenceScreen.findPreference(PREF_OS_PERMISSIONS_WARNING)); |
| } else if (osWarningExtra.getTitle() == null) { |
| preferenceScreen.removePreference( |
| preferenceScreen.findPreference(PREF_OS_PERMISSIONS_WARNING_EXTRA)); |
| } |
| } |
| } |
| |
| private void setUpAdsInformationalBanner() { |
| // Add the informational banner which shows at the top of the UI if ad blocking is |
| // activated on this site. |
| PreferenceScreen preferenceScreen = getPreferenceScreen(); |
| boolean adBlockingActivated = SiteSettingsCategory.adsCategoryEnabled() |
| && WebsitePreferenceBridge.getAdBlockingActivated(mSite.getAddress().getOrigin()) |
| && preferenceScreen.findPreference(PREF_ADS_PERMISSION) != null; |
| |
| if (!adBlockingActivated) { |
| Preference intrusiveAdsInfo = preferenceScreen.findPreference(PREF_INTRUSIVE_ADS_INFO); |
| Preference intrusiveAdsInfoDivider = |
| preferenceScreen.findPreference(PREF_INTRUSIVE_ADS_INFO_DIVIDER); |
| |
| preferenceScreen.removePreference(intrusiveAdsInfo); |
| preferenceScreen.removePreference(intrusiveAdsInfoDivider); |
| } |
| } |
| |
| private SiteSettingsCategory getWarningCategory() { |
| // If more than one per-app permission is disabled in Android, we can pick any category to |
| // show the warning, because they will all show the same warning and all take the user to |
| // the user to the same location. It is preferrable, however, that we give Geolocation some |
| // priority because that category is the only one that potentially shows an additional |
| // warning (when Location is turned off globally). |
| if (showWarningFor(ContentSettingsType.CONTENT_SETTINGS_TYPE_GEOLOCATION)) { |
| return SiteSettingsCategory.fromContentSettingsType( |
| ContentSettingsType.CONTENT_SETTINGS_TYPE_GEOLOCATION); |
| } |
| if (showWarningFor(ContentSettingsType.CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA)) { |
| return SiteSettingsCategory.fromContentSettingsType( |
| ContentSettingsType.CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA); |
| } |
| if (showWarningFor(ContentSettingsType.CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC)) { |
| return SiteSettingsCategory.fromContentSettingsType( |
| ContentSettingsType.CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC); |
| } |
| return null; |
| } |
| |
| private boolean showWarningFor(int type) { |
| ContentSetting setting = null; |
| if (type == ContentSettingsType.CONTENT_SETTINGS_TYPE_GEOLOCATION) { |
| setting = mSite.getGeolocationPermission(); |
| } else if (type == ContentSettingsType.CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA) { |
| setting = mSite.getCameraPermission(); |
| } else if (type == ContentSettingsType.CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC) { |
| setting = mSite.getMicrophonePermission(); |
| } |
| |
| if (setting == null) { |
| return false; |
| } |
| |
| SiteSettingsCategory category = SiteSettingsCategory.fromContentSettingsType(type); |
| return category.showPermissionBlockedMessage(getActivity()); |
| } |
| |
| private boolean hasUsagePreferences() { |
| // New actions under the Usage preference category must be listed here so that the category |
| // heading can be removed when no actions are shown. |
| return getPreferenceScreen().findPreference(PREF_CLEAR_DATA) != null; |
| } |
| |
| private boolean hasPermissionsPreferences() { |
| if (mUsbPermissionCount > 0) return true; |
| PreferenceScreen screen = getPreferenceScreen(); |
| for (String key : PERMISSION_PREFERENCE_KEYS) { |
| if (screen.findPreference(key) != null) return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Initialize a ListPreference with a certain value. |
| * @param preference The ListPreference to initialize. |
| * @param value The value to initialize it to. |
| */ |
| private void setUpListPreference(Preference preference, ContentSetting value) { |
| if (value == null) { |
| getPreferenceScreen().removePreference(preference); |
| return; |
| } |
| setUpPreferenceCommon(preference); |
| ListPreference listPreference = (ListPreference) preference; |
| |
| CharSequence[] keys = new String[2]; |
| CharSequence[] descriptions = new String[2]; |
| keys[0] = ContentSetting.ALLOW.toString(); |
| keys[1] = ContentSetting.BLOCK.toString(); |
| descriptions[0] = getResources().getString( |
| ContentSettingsResources.getSiteSummary(ContentSetting.ALLOW)); |
| descriptions[1] = getResources().getString( |
| ContentSettingsResources.getSiteSummary(ContentSetting.BLOCK)); |
| listPreference.setEntryValues(keys); |
| listPreference.setEntries(descriptions); |
| // TODO(crbug.com/735110): Figure out if this is the correct thing to do - here we are |
| // effectively treating non-ALLOW values as BLOCK. |
| int index = (value == ContentSetting.ALLOW ? 0 : 1); |
| listPreference.setValueIndex(index); |
| listPreference.setOnPreferenceChangeListener(this); |
| listPreference.setSummary("%s"); |
| } |
| |
| /** |
| * Sets some properties that apply to both regular Preferences and ListPreferences, i.e. |
| * preference title, enabled-state, and icon, based on the preference's key. |
| */ |
| private void setUpPreferenceCommon(Preference preference) { |
| int contentType = getContentSettingsTypeFromPreferenceKey(preference.getKey()); |
| int explanationResourceId = ContentSettingsResources.getExplanation(contentType); |
| if (explanationResourceId != 0) { |
| preference.setTitle(explanationResourceId); |
| } |
| if (preference.isEnabled()) { |
| SiteSettingsCategory category = |
| SiteSettingsCategory.fromContentSettingsType(contentType); |
| if (category != null && !category.enabledInAndroid(getActivity())) { |
| preference.setIcon(category.getDisabledInAndroidIcon(getActivity())); |
| preference.setEnabled(false); |
| } else { |
| preference.setIcon( |
| ContentSettingsResources.getTintedIcon(contentType, getResources())); |
| } |
| } else { |
| preference.setIcon( |
| ContentSettingsResources.getDisabledIcon(contentType, getResources())); |
| } |
| } |
| |
| private void setUpLocationPreference(Preference preference) { |
| ContentSetting permission = mSite.getGeolocationPermission(); |
| setUpListPreference(preference, permission); |
| if (isPermissionControlledByDSE(ContentSettingsType.CONTENT_SETTINGS_TYPE_GEOLOCATION) |
| && permission != null) { |
| updatePreferenceForDSESetting(preference); |
| } |
| } |
| |
| /** |
| * Updates the ads list preference based on whether the site is a candidate for blocking. This |
| * has some custom behavior. |
| * 1. If the site is a candidate and has activation, the permission should show up even if it |
| * is set as the default (e.g. |preference| is null). |
| * 2. The BLOCK string is custom. |
| */ |
| private void setUpAdsPreference(Preference preference) { |
| // Do not show the setting if the category is not enabled. |
| if (!SiteSettingsCategory.adsCategoryEnabled()) { |
| setUpListPreference(preference, null); |
| return; |
| } |
| // If the ad blocker is activated, then this site will have ads blocked unless there is an |
| // explicit permission disallowing the blocking. |
| boolean activated = |
| WebsitePreferenceBridge.getAdBlockingActivated(mSite.getAddress().getOrigin()); |
| ContentSetting permission = mSite.getAdsPermission(); |
| |
| // If |permission| is null, there is no explicit (non-default) permission set for this site. |
| // If the site is not considered a candidate for blocking, do the standard thing and remove |
| // the preference. |
| if (permission == null && !activated) { |
| setUpListPreference(preference, null); |
| return; |
| } |
| |
| // However, if the blocking is activated, we still want to show the permission, even if it |
| // is in the default state. |
| if (permission == null) { |
| ContentSetting defaultPermission = PrefServiceBridge.getInstance().adsEnabled() |
| ? ContentSetting.ALLOW |
| : ContentSetting.BLOCK; |
| permission = defaultPermission; |
| } |
| setUpListPreference(preference, permission); |
| |
| // The subresource filter permission has a custom BLOCK string. |
| ListPreference listPreference = (ListPreference) preference; |
| Resources res = getResources(); |
| listPreference.setEntries( |
| new String[] {res.getString(R.string.website_settings_permissions_allow), |
| res.getString(R.string.website_settings_permissions_ads_block)}); |
| listPreference.setValueIndex(permission == ContentSetting.ALLOW ? 0 : 1); |
| } |
| |
| /** |
| * Returns true if the DSE (default search engine) geolocation and notifications permissions |
| * are configured for the DSE. |
| */ |
| private boolean isPermissionControlledByDSE(@ContentSettingsType int contentSettingsType) { |
| return WebsitePreferenceBridge.isPermissionControlledByDSE( |
| contentSettingsType, mSite.getAddress().getOrigin(), false); |
| } |
| |
| /** |
| * Updates the location preference to indicate that the site has access to location (via X-Geo) |
| * for searches that happen from the omnibox. |
| * @param preference The Location preference to modify. |
| */ |
| private void updatePreferenceForDSESetting(Preference preference) { |
| ListPreference listPreference = (ListPreference) preference; |
| Resources res = getResources(); |
| listPreference.setEntries(new String[] { |
| res.getString(R.string.website_settings_permissions_allow_dse), |
| res.getString(R.string.website_settings_permissions_block_dse), |
| }); |
| } |
| |
| private int getContentSettingsTypeFromPreferenceKey(String preferenceKey) { |
| switch (preferenceKey) { |
| case PREF_ADS_PERMISSION: |
| return ContentSettingsType.CONTENT_SETTINGS_TYPE_ADS; |
| case PREF_AUTOPLAY_PERMISSION: |
| return ContentSettingsType.CONTENT_SETTINGS_TYPE_AUTOPLAY; |
| case PREF_BACKGROUND_SYNC_PERMISSION: |
| return ContentSettingsType.CONTENT_SETTINGS_TYPE_BACKGROUND_SYNC; |
| case PREF_CAMERA_CAPTURE_PERMISSION: |
| return ContentSettingsType.CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA; |
| case PREF_CLIPBOARD_PERMISSION: |
| return ContentSettingsType.CONTENT_SETTINGS_TYPE_CLIPBOARD_READ; |
| case PREF_COOKIES_PERMISSION: |
| return ContentSettingsType.CONTENT_SETTINGS_TYPE_COOKIES; |
| case PREF_JAVASCRIPT_PERMISSION: |
| return ContentSettingsType.CONTENT_SETTINGS_TYPE_JAVASCRIPT; |
| case PREF_LOCATION_ACCESS: |
| return ContentSettingsType.CONTENT_SETTINGS_TYPE_GEOLOCATION; |
| case PREF_MIC_CAPTURE_PERMISSION: |
| return ContentSettingsType.CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC; |
| case PREF_MIDI_SYSEX_PERMISSION: |
| return ContentSettingsType.CONTENT_SETTINGS_TYPE_MIDI_SYSEX; |
| case PREF_NOTIFICATIONS_PERMISSION: |
| return ContentSettingsType.CONTENT_SETTINGS_TYPE_NOTIFICATIONS; |
| case PREF_POPUP_PERMISSION: |
| return ContentSettingsType.CONTENT_SETTINGS_TYPE_POPUPS; |
| case PREF_PROTECTED_MEDIA_IDENTIFIER_PERMISSION: |
| return ContentSettingsType.CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER; |
| case PREF_SOUND_PERMISSION: |
| return ContentSettingsType.CONTENT_SETTINGS_TYPE_SOUND; |
| default: |
| return 0; |
| } |
| } |
| |
| private void clearStoredData() { |
| mSite.clearAllStoredData( |
| new Website.StoredDataClearedCallback() { |
| @Override |
| public void onStoredDataCleared() { |
| PreferenceScreen preferenceScreen = getPreferenceScreen(); |
| preferenceScreen.removePreference( |
| preferenceScreen.findPreference(PREF_CLEAR_DATA)); |
| if (!hasUsagePreferences()) { |
| preferenceScreen.removePreference( |
| preferenceScreen.findPreference(PREF_USAGE)); |
| } |
| popBackIfNoSettings(); |
| } |
| }); |
| } |
| |
| private void popBackIfNoSettings() { |
| if (!hasPermissionsPreferences() && !hasUsagePreferences()) { |
| if (getActivity() != null) { |
| getActivity().finish(); |
| } |
| } |
| } |
| |
| @Override |
| public boolean onPreferenceChange(Preference preference, Object newValue) { |
| ContentSetting permission = ContentSetting.fromString((String) newValue); |
| if (PREF_ADS_PERMISSION.equals(preference.getKey())) { |
| mSite.setAdsPermission(permission); |
| } else if (PREF_AUTOPLAY_PERMISSION.equals(preference.getKey())) { |
| mSite.setAutoplayPermission(permission); |
| } else if (PREF_BACKGROUND_SYNC_PERMISSION.equals(preference.getKey())) { |
| mSite.setBackgroundSyncPermission(permission); |
| } else if (PREF_CAMERA_CAPTURE_PERMISSION.equals(preference.getKey())) { |
| mSite.setCameraPermission(permission); |
| } else if (PREF_CLIPBOARD_PERMISSION.equals(preference.getKey())) { |
| mSite.setClipboardPermission(permission); |
| } else if (PREF_COOKIES_PERMISSION.equals(preference.getKey())) { |
| mSite.setCookiePermission(permission); |
| } else if (PREF_JAVASCRIPT_PERMISSION.equals(preference.getKey())) { |
| mSite.setJavaScriptPermission(permission); |
| } else if (PREF_LOCATION_ACCESS.equals(preference.getKey())) { |
| mSite.setGeolocationPermission(permission); |
| } else if (PREF_MIC_CAPTURE_PERMISSION.equals(preference.getKey())) { |
| mSite.setMicrophonePermission(permission); |
| } else if (PREF_MIDI_SYSEX_PERMISSION.equals(preference.getKey())) { |
| mSite.setMidiPermission(permission); |
| } else if (PREF_NOTIFICATIONS_PERMISSION.equals(preference.getKey())) { |
| mSite.setNotificationPermission(permission); |
| } else if (PREF_POPUP_PERMISSION.equals(preference.getKey())) { |
| mSite.setPopupPermission(permission); |
| } else if (PREF_PROTECTED_MEDIA_IDENTIFIER_PERMISSION.equals(preference.getKey())) { |
| mSite.setProtectedMediaIdentifierPermission(permission); |
| } else if (PREF_SOUND_PERMISSION.equals(preference.getKey())) { |
| mSite.setSoundPermission(permission); |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public boolean onPreferenceClick(Preference preference) { |
| Bundle extras = preference.peekExtras(); |
| if (extras != null) { |
| UsbInfo usbInfo = (UsbInfo) extras.getSerializable(EXTRA_USB_INFO); |
| if (usbInfo != null) { |
| usbInfo.revoke(); |
| |
| PreferenceScreen preferenceScreen = getPreferenceScreen(); |
| preferenceScreen.removePreference(preference); |
| mUsbPermissionCount--; |
| if (!hasPermissionsPreferences()) { |
| Preference heading = preferenceScreen.findPreference(PREF_PERMISSIONS); |
| preferenceScreen.removePreference(heading); |
| } |
| return true; |
| } |
| } |
| |
| // Handle the Clear & Reset preference click by showing a confirmation. |
| new AlertDialog.Builder(getActivity(), R.style.AlertDialogTheme) |
| .setTitle(R.string.website_reset) |
| .setMessage(R.string.website_reset_confirmation) |
| .setPositiveButton(R.string.website_reset, new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| resetSite(); |
| } |
| }) |
| .setNegativeButton(R.string.cancel, null) |
| .show(); |
| return true; |
| } |
| |
| /** |
| * Resets the current site, clearing all permissions and storage used (inc. cookies). |
| */ |
| @VisibleForTesting |
| protected void resetSite() { |
| if (getActivity() == null) return; |
| // Clear the screen. |
| // TODO(mvanouwerkerk): Refactor this class so that it does not depend on the screen state |
| // for its logic. This class should maintain its own data model, and only update the screen |
| // after a change is made. |
| PreferenceScreen screen = getPreferenceScreen(); |
| for (String key : PERMISSION_PREFERENCE_KEYS) { |
| Preference preference = screen.findPreference(key); |
| if (preference != null) screen.removePreference(preference); |
| } |
| |
| String origin = mSite.getAddress().getOrigin(); |
| WebsitePreferenceBridge.nativeClearCookieData(origin); |
| WebsitePreferenceBridge.nativeClearBannerData(origin); |
| |
| // Clear the permissions. |
| mSite.setAdsPermission(ContentSetting.DEFAULT); |
| mSite.setAutoplayPermission(ContentSetting.DEFAULT); |
| mSite.setBackgroundSyncPermission(ContentSetting.DEFAULT); |
| mSite.setCameraPermission(ContentSetting.DEFAULT); |
| mSite.setClipboardPermission(ContentSetting.DEFAULT); |
| mSite.setCookiePermission(ContentSetting.DEFAULT); |
| mSite.setGeolocationPermission(ContentSetting.DEFAULT); |
| mSite.setJavaScriptPermission(ContentSetting.DEFAULT); |
| mSite.setMicrophonePermission(ContentSetting.DEFAULT); |
| mSite.setMidiPermission(ContentSetting.DEFAULT); |
| mSite.setNotificationPermission(ContentSetting.DEFAULT); |
| mSite.setPopupPermission(ContentSetting.DEFAULT); |
| mSite.setProtectedMediaIdentifierPermission(ContentSetting.DEFAULT); |
| mSite.setSoundPermission(ContentSetting.DEFAULT); |
| |
| for (UsbInfo info : mSite.getUsbInfo()) info.revoke(); |
| |
| // Clear the storage and finish the activity if necessary. |
| if (mSite.getTotalUsage() > 0) { |
| clearStoredData(); |
| } else { |
| // Clearing stored data implies popping back to parent menu if there |
| // is nothing left to show. Therefore, we only need to explicitly |
| // close the activity if there's no stored data to begin with. |
| getActivity().finish(); |
| } |
| } |
| } |