| // Copyright 2016 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.app.Activity; |
| import android.app.ActivityManager; |
| import android.app.Dialog; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.content.res.Resources; |
| import android.graphics.Bitmap; |
| import android.graphics.drawable.BitmapDrawable; |
| import android.graphics.drawable.Drawable; |
| import android.os.Bundle; |
| import android.support.v4.app.DialogFragment; |
| import android.support.v7.app.AlertDialog; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.AdapterView; |
| import android.widget.ArrayAdapter; |
| import android.widget.CheckBox; |
| import android.widget.ImageView; |
| import android.widget.ListView; |
| |
| import org.chromium.base.CollectionUtil; |
| import org.chromium.base.ContextUtils; |
| import org.chromium.base.VisibleForTesting; |
| import org.chromium.chrome.R; |
| import org.chromium.chrome.browser.favicon.FaviconUtils; |
| import org.chromium.chrome.browser.favicon.IconType; |
| import org.chromium.chrome.browser.favicon.LargeIconBridge; |
| import org.chromium.chrome.browser.favicon.LargeIconBridge.LargeIconCallback; |
| import org.chromium.chrome.browser.profiles.Profile; |
| import org.chromium.chrome.browser.ui.widget.RoundedIconGenerator; |
| import org.chromium.chrome.browser.util.ConversionUtils; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| |
| /** |
| * Modal dialog that shows a list of important domains to the user which they can uncheck. Used to |
| * allow the user to exclude domains from being cleared by the clear browsing data function. |
| * We use proper bundle construction (through the {@link #newInstance(String[], int[], String[])} |
| * method) and onActivityResult return conventions. |
| */ |
| public class ConfirmImportantSitesDialogFragment extends DialogFragment { |
| private class ClearBrowsingDataAdapter extends ArrayAdapter<String> |
| implements AdapterView.OnItemClickListener { |
| private final String[] mDomains; |
| private final int mFaviconSize; |
| private RoundedIconGenerator mIconGenerator; |
| |
| private ClearBrowsingDataAdapter( |
| String[] domains, String[] faviconURLs, Resources resources) { |
| super(getActivity(), R.layout.confirm_important_sites_list_row, domains); |
| mDomains = domains; |
| mFaviconURLs = faviconURLs; |
| mFaviconSize = resources.getDimensionPixelSize(R.dimen.default_favicon_size); |
| mIconGenerator = FaviconUtils.createRoundedRectangleIconGenerator(getResources()); |
| } |
| |
| @Override |
| public boolean hasStableIds() { |
| return true; |
| } |
| |
| @Override |
| public View getView(int position, View convertView, ViewGroup parent) { |
| View childView = convertView; |
| if (childView == null) { |
| LayoutInflater inflater = LayoutInflater.from(getActivity()); |
| childView = |
| inflater.inflate(R.layout.confirm_important_sites_list_row, parent, false); |
| |
| ViewAndFaviconHolder viewHolder = new ViewAndFaviconHolder(); |
| viewHolder.checkboxView = (CheckBox) childView.findViewById(R.id.icon_row_checkbox); |
| viewHolder.imageView = (ImageView) childView.findViewById(R.id.icon_row_image); |
| childView.setTag(viewHolder); |
| } |
| ViewAndFaviconHolder viewHolder = (ViewAndFaviconHolder) childView.getTag(); |
| configureChildView(position, viewHolder); |
| return childView; |
| } |
| |
| private void configureChildView(int position, ViewAndFaviconHolder viewHolder) { |
| String domain = mDomains[position]; |
| viewHolder.checkboxView.setChecked(mCheckedState.get(domain)); |
| viewHolder.checkboxView.setText(domain); |
| loadFavicon(viewHolder, mFaviconURLs[position]); |
| } |
| |
| /** |
| * Called when a list item is clicked. We toggle the checkbox and update our selected |
| * domains list. |
| */ |
| @Override |
| public void onItemClick(AdapterView<?> parent, View view, int position, long id) { |
| String domain = mDomains[position]; |
| ViewAndFaviconHolder viewHolder = (ViewAndFaviconHolder) view.getTag(); |
| boolean isChecked = mCheckedState.get(domain); |
| mCheckedState.put(domain, !isChecked); |
| viewHolder.checkboxView.setChecked(!isChecked); |
| } |
| |
| private void loadFavicon(final ViewAndFaviconHolder viewHolder, final String url) { |
| viewHolder.imageCallback = new LargeIconCallback() { |
| @Override |
| public void onLargeIconAvailable(Bitmap icon, int fallbackColor, |
| boolean isFallbackColorDefault, @IconType int iconType) { |
| if (this != viewHolder.imageCallback) return; |
| Drawable image = getFaviconDrawable(icon, fallbackColor, url); |
| viewHolder.imageView.setImageDrawable(image); |
| } |
| }; |
| mLargeIconBridge.getLargeIconForUrl(url, mFaviconSize, viewHolder.imageCallback); |
| } |
| |
| private Drawable getFaviconDrawable(Bitmap icon, int fallbackColor, String url) { |
| if (icon == null) { |
| mIconGenerator.setBackgroundColor(fallbackColor); |
| icon = mIconGenerator.generateIconForUrl(url); |
| return new BitmapDrawable(getResources(), icon); |
| } else { |
| return FaviconUtils.createRoundedBitmapDrawable(getResources(), |
| Bitmap.createScaledBitmap(icon, mFaviconSize, mFaviconSize, false)); |
| } |
| } |
| } |
| |
| /** |
| * ViewHolder class optimizes looking up table row fields. findViewById is only called once |
| * per row view initialization, and the references are cached here. Also stores a reference to |
| * the favicon image callback so that we can make sure we load the correct favicon. |
| */ |
| private static class ViewAndFaviconHolder { |
| public CheckBox checkboxView; |
| public ImageView imageView; |
| public LargeIconCallback imageCallback; |
| } |
| |
| /** |
| * Constructs a new instance of the important sites dialog fragment. |
| * @param importantDomains The list of important domains to display. |
| * @param importantDomainReasons The reasons for choosing each important domain. |
| * @param faviconURLs The list of favicon urls that correspond to each importantDomains. |
| * @return An instance of ConfirmImportantSitesDialogFragment with the bundle arguments set. |
| */ |
| public static ConfirmImportantSitesDialogFragment newInstance( |
| String[] importantDomains, int[] importantDomainReasons, String[] faviconURLs) { |
| ConfirmImportantSitesDialogFragment dialogFragment = |
| new ConfirmImportantSitesDialogFragment(); |
| Bundle bundle = new Bundle(); |
| bundle.putStringArray(IMPORTANT_DOMAINS_TAG, importantDomains); |
| bundle.putIntArray(IMPORTANT_DOMAIN_REASONS_TAG, importantDomainReasons); |
| bundle.putStringArray(FAVICON_URLS_TAG, faviconURLs); |
| dialogFragment.setArguments(bundle); |
| return dialogFragment; |
| } |
| |
| private static final int FAVICON_MAX_CACHE_SIZE_BYTES = |
| 100 * ConversionUtils.BYTES_PER_KILOBYTE; // 100KB |
| |
| /** The tag used when showing the clear browsing fragment. */ |
| public static final String FRAGMENT_TAG = "ConfirmImportantSitesDialogFragment"; |
| |
| /** The tag for the string array of deselected domains. These are meant to NOT be cleared. */ |
| public static final String DESELECTED_DOMAINS_TAG = "DeselectedDomains"; |
| /** The tag for the int array of reasons the deselected domains were important. */ |
| public static final String DESELECTED_DOMAIN_REASONS_TAG = "DeselectedDomainReasons"; |
| /** The tag for the string array of ignored domains, which whill be cleared. */ |
| public static final String IGNORED_DOMAINS_TAG = "IgnoredDomains"; |
| /** The tag for the int array of reasons the ignored domains were important. */ |
| public static final String IGNORED_DOMAIN_REASONS_TAG = "IgnoredDomainReasons"; |
| |
| /** The tag used for logging. */ |
| public static final String TAG = "ConfirmImportantSitesDialogFragment"; |
| |
| /** The tag used to store the important domains in the bundle. */ |
| private static final String IMPORTANT_DOMAINS_TAG = "ImportantDomains"; |
| /** The tag used to store the important domain reasons in the bundle. */ |
| private static final String IMPORTANT_DOMAIN_REASONS_TAG = "ImportantDomainReasons"; |
| |
| /** The tag used to store the favicon urls corresponding to each important domain. */ |
| private static final String FAVICON_URLS_TAG = "FaviconURLs"; |
| |
| /** Array of important registerable domains we're showing to the user. */ |
| private String[] mImportantDomains; |
| /** Map of the reasons the above important domains were chosen. */ |
| private Map<String, Integer> mImportantDomainsReasons; |
| /** Array of favicon urls to use for each important domain above. */ |
| private String[] mFaviconURLs; |
| /** The map of domains to the checked state, where true is checked. */ |
| private Map<String, Boolean> mCheckedState; |
| /** The alert dialog shown to the user. */ |
| private AlertDialog mDialog; |
| /** Our adapter that we use with the list view in the dialog. */ |
| private ClearBrowsingDataAdapter mAdapter; |
| |
| private LargeIconBridge mLargeIconBridge; |
| |
| private Profile mProfile; |
| |
| /** We store the custom list view for testing */ |
| private ListView mSitesListView; |
| |
| public ConfirmImportantSitesDialogFragment() { |
| mImportantDomainsReasons = new HashMap<>(); |
| mCheckedState = new HashMap<>(); |
| } |
| |
| @Override |
| public void setArguments(Bundle args) { |
| super.setArguments(args); |
| mImportantDomains = args.getStringArray(IMPORTANT_DOMAINS_TAG); |
| mFaviconURLs = args.getStringArray(FAVICON_URLS_TAG); |
| int[] importantDomainReasons = args.getIntArray(IMPORTANT_DOMAIN_REASONS_TAG); |
| for (int i = 0; i < mImportantDomains.length; ++i) { |
| mImportantDomainsReasons.put(mImportantDomains[i], importantDomainReasons[i]); |
| mCheckedState.put(mImportantDomains[i], true); |
| } |
| } |
| |
| @VisibleForTesting |
| public Set<String> getDeselectedDomains() { |
| HashSet<String> deselected = new HashSet<>(); |
| for (Entry<String, Boolean> entry : mCheckedState.entrySet()) { |
| if (!entry.getValue()) deselected.add(entry.getKey()); |
| } |
| return deselected; |
| } |
| |
| @VisibleForTesting |
| public ListView getSitesList() { |
| return mSitesListView; |
| } |
| |
| @Override |
| public void onDismiss(DialogInterface dialog) { |
| super.onDismiss(dialog); |
| if (mLargeIconBridge != null) { |
| mLargeIconBridge.destroy(); |
| } |
| } |
| |
| @Override |
| public Dialog onCreateDialog(Bundle savedInstanceState) { |
| // We check the domains and urls as well due to crbug.com/622879. |
| if (savedInstanceState != null) { |
| // The important domains and favicon URLs aren't currently saved, so if this dialog |
| // is recreated from a saved instance they will be null. This method must return a |
| // valid dialog, so these two array's are initialized, then the dialog is dismissed. |
| // TODO(dmurph): save mImportantDomains and mFaviconURLs so that they can be restored |
| // from a savedInstanceState and the dialog can be properly recreated rather than |
| // dismissed. |
| mImportantDomains = new String[0]; |
| mFaviconURLs = new String[0]; |
| dismiss(); |
| } |
| mProfile = Profile.getLastUsedProfile().getOriginalProfile(); |
| mLargeIconBridge = new LargeIconBridge(mProfile); |
| ActivityManager activityManager = |
| ((ActivityManager) ContextUtils.getApplicationContext().getSystemService( |
| Context.ACTIVITY_SERVICE)); |
| int maxSize = Math.min( |
| activityManager.getMemoryClass() / 16 * 25 * ConversionUtils.BYTES_PER_KILOBYTE, |
| FAVICON_MAX_CACHE_SIZE_BYTES); |
| mLargeIconBridge.createCache(maxSize); |
| |
| mAdapter = new ClearBrowsingDataAdapter(mImportantDomains, mFaviconURLs, getResources()); |
| DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| if (which == AlertDialog.BUTTON_POSITIVE) { |
| Intent data = new Intent(); |
| List<String> deselectedDomains = new ArrayList<>(); |
| List<Integer> deselectedDomainReasons = new ArrayList<>(); |
| List<String> ignoredDomains = new ArrayList<>(); |
| List<Integer> ignoredDomainReasons = new ArrayList<>(); |
| for (Entry<String, Boolean> entry : mCheckedState.entrySet()) { |
| Integer reason = mImportantDomainsReasons.get(entry.getKey()); |
| if (entry.getValue()) { |
| ignoredDomains.add(entry.getKey()); |
| ignoredDomainReasons.add(reason); |
| } else { |
| deselectedDomains.add(entry.getKey()); |
| deselectedDomainReasons.add(reason); |
| } |
| } |
| data.putExtra(DESELECTED_DOMAINS_TAG, deselectedDomains.toArray(new String[0])); |
| data.putExtra(DESELECTED_DOMAIN_REASONS_TAG, |
| CollectionUtil.integerListToIntArray(deselectedDomainReasons)); |
| data.putExtra(IGNORED_DOMAINS_TAG, ignoredDomains.toArray(new String[0])); |
| data.putExtra(IGNORED_DOMAIN_REASONS_TAG, |
| CollectionUtil.integerListToIntArray(ignoredDomainReasons)); |
| getTargetFragment().onActivityResult( |
| getTargetRequestCode(), Activity.RESULT_OK, data); |
| } else { |
| getTargetFragment().onActivityResult(getTargetRequestCode(), |
| Activity.RESULT_CANCELED, getActivity().getIntent()); |
| } |
| } |
| }; |
| // We create our own ListView, as AlertDialog doesn't let us set a message and a list |
| // adapter at the same time. |
| View messageAndListView = getActivity().getLayoutInflater().inflate( |
| R.layout.clear_browsing_important_dialog_listview, null); |
| mSitesListView = (ListView) messageAndListView.findViewById(R.id.select_dialog_listview); |
| mSitesListView.setAdapter(mAdapter); |
| mSitesListView.setOnItemClickListener(mAdapter); |
| final AlertDialog.Builder builder = |
| new AlertDialog.Builder(getActivity(), R.style.Theme_Chromium_AlertDialog) |
| .setTitle(R.string.storage_clear_site_storage_title) |
| .setPositiveButton( |
| R.string.clear_browsing_data_important_dialog_button, listener) |
| .setNegativeButton(R.string.cancel, listener) |
| .setView(messageAndListView); |
| mDialog = builder.create(); |
| |
| return mDialog; |
| } |
| } |