blob: 22682eae06e68fd4cdde4384d4cc2613731b039e [file] [log] [blame]
// 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.website;
import android.content.DialogInterface;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import android.support.graphics.drawable.VectorDrawableCompat;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.SearchView;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.ListView;
import org.chromium.base.annotations.RemovableInRelease;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.help.HelpAndFeedback;
import org.chromium.chrome.browser.preferences.ChromeImageViewPreference;
import org.chromium.chrome.browser.preferences.ManagedPreferenceDelegate;
import org.chromium.chrome.browser.preferences.ManagedPreferencesUtils;
import org.chromium.chrome.browser.preferences.PreferenceUtils;
import org.chromium.chrome.browser.profiles.Profile;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Locale;
/**
* Shows a particular chosen object (e.g. a USB device) and the list of sites that have been
* granted access to it by the user.
*/
public class ChosenObjectPreferences extends PreferenceFragment {
public static final String EXTRA_OBJECT_INFOS = "org.chromium.chrome.preferences.object_infos";
public static final String EXTRA_SITES = "org.chromium.chrome.preferences.site_set";
public static final String EXTRA_CATEGORY =
"org.chromium.chrome.preferences.content_settings_type";
// The site settings category we are showing.
private SiteSettingsCategory mCategory;
// The set of object permissions being examined.
private ArrayList<ChosenObjectInfo> mObjectInfos;
// The set of sites to display.
private ArrayList<Website> mSites;
// The view for searching the list of items.
private SearchView mSearchView;
// If not blank, represents a substring to use to search for site names.
private String mSearch = "";
@Override
@SuppressWarnings("unchecked")
public void onActivityCreated(Bundle savedInstanceState) {
PreferenceUtils.addPreferencesFromResource(this, R.xml.blank_preference_fragment_screen);
ListView listView = (ListView) getView().findViewById(android.R.id.list);
listView.setDivider(null);
int contentSettingsType = getArguments().getInt(EXTRA_CATEGORY);
mCategory = SiteSettingsCategory.createFromContentSettingsType(contentSettingsType);
mObjectInfos =
(ArrayList<ChosenObjectInfo>) getArguments().getSerializable(EXTRA_OBJECT_INFOS);
checkObjectConsistency();
mSites = (ArrayList<Website>) getArguments().getSerializable(EXTRA_SITES);
String title = getArguments().getString(SingleCategoryPreferences.EXTRA_TITLE);
if (title != null) getActivity().setTitle(title);
setHasOptionsMenu(true);
super.onActivityCreated(savedInstanceState);
}
/**
* Checks the consistency of |mObjectInfos|.
*
* This method asserts that for all the entries in |mObjectInfos| the getObject() method
* returns the same value. This must be true because this activity is displaying permissions
* for a single object. Each instance varies only in which site it represents.
*/
@RemovableInRelease
private void checkObjectConsistency() {
String exampleObject = mObjectInfos.get(0).getObject();
for (ChosenObjectInfo info : mObjectInfos) {
assert info.getObject().equals(exampleObject);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.clear();
inflater.inflate(R.menu.website_preferences_menu, menu);
MenuItem searchItem = menu.findItem(R.id.search);
mSearchView = (SearchView) MenuItemCompat.getActionView(searchItem);
mSearchView.setImeOptions(EditorInfo.IME_FLAG_NO_FULLSCREEN);
SearchView.OnQueryTextListener queryTextListener = new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return true;
}
@Override
public boolean onQueryTextChange(String query) {
// Make search case-insensitive.
query = query.toLowerCase(Locale.getDefault());
if (query.equals(mSearch)) return true;
mSearch = query;
getInfo();
return true;
}
};
mSearchView.setOnQueryTextListener(queryTextListener);
MenuItem help =
menu.add(Menu.NONE, R.id.menu_id_targeted_help, Menu.NONE, R.string.menu_help);
help.setIcon(VectorDrawableCompat.create(
getResources(), R.drawable.ic_help_and_feedback, getActivity().getTheme()));
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.menu_id_targeted_help) {
HelpAndFeedback.getInstance(getActivity())
.show(getActivity(), getString(R.string.help_context_settings),
Profile.getLastUsedProfile(), null);
return true;
}
return false;
}
@Override
public void onResume() {
super.onResume();
if (mSites == null) {
getInfo();
} else {
resetList();
}
}
/**
* Iterates through |mObjectInfos| to revoke the object permissions. If the list of objects
* contains one that is managed, a toast will display to explain that the managed objects cannot
* be reset, otherwise all of the objects are reset so the activity is finished.
*/
public void revokeObjectPermissions() {
boolean hasManagedObject = false;
for (ChosenObjectInfo info : mObjectInfos) {
if (info.isManaged())
hasManagedObject = true;
else
info.revoke();
}
// Managed objects cannot be revoked, so finish the activity only if the list did not
// contain managed objects.
if (hasManagedObject) {
ManagedPreferencesUtils.showManagedSettingsCannotBeResetToast(getActivity());
} else {
getActivity().finish();
}
}
private class ResultsPopulator implements WebsitePermissionsFetcher.WebsitePermissionsCallback {
@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;
// Remember the object being examined in this view so that we can filter the results
// to only include sites with permission for this particular object.
String exampleObject = mObjectInfos.get(0).getObject();
mObjectInfos.clear();
mSites = new ArrayList<Website>();
for (Website site : sites) {
for (ChosenObjectInfo info : site.getChosenObjectInfo()) {
if (info.getObject().equals(exampleObject)) {
mObjectInfos.add(info);
if (mSearch.isEmpty()
|| site.getTitle()
.toLowerCase(Locale.getDefault())
.contains(mSearch)) {
mSites.add(site);
}
}
}
}
// After revoking a site's permission to access an object the user may end up back at
// this activity. It is awkward to display this empty list because there's no action
// that can be taken from it. In this case we dismiss this activity as well, taking
// them back to SingleCategoryPreferences which will now no longer offer the option to
// examine the permissions for this object.
if (mObjectInfos.isEmpty()) {
getActivity().finish();
} else {
resetList();
}
}
}
/**
* Refreshes the list of sites with access to the object being examined.
*
* resetList() is called to refresh the view when the data is ready.
*/
private void getInfo() {
WebsitePermissionsFetcher fetcher = new WebsitePermissionsFetcher();
fetcher.fetchPreferencesForCategory(mCategory, new ResultsPopulator());
}
/**
* Configures two Preferences that make up the header portion of this fragment. The first
* Preference displays the chosen object name and provides a button to reset all of the site
* permissions for the object. The second Preference is a horizontal divider line.
*/
private void createHeader() {
PreferenceScreen preferenceScreen = getPreferenceScreen();
ChromeImageViewPreference header = new ChromeImageViewPreference(getActivity());
String titleText = mObjectInfos.get(0).getName();
String dialogMsg =
String.format(getView().getContext().getString(
R.string.chosen_object_website_reset_confirmation_for),
titleText);
header.setTitle(titleText);
header.setImageView(R.drawable.ic_delete_white_24dp,
R.string.website_settings_revoke_all_permissions_for_device, (View view) -> {
new AlertDialog.Builder(getActivity(), R.style.Theme_Chromium_AlertDialog)
.setTitle(R.string.reset)
.setMessage(dialogMsg)
.setPositiveButton(R.string.reset,
(DialogInterface dialog, int which) -> {
revokeObjectPermissions();
getInfo();
})
.setNegativeButton(R.string.cancel, null)
.show();
});
preferenceScreen.addPreference(header);
Preference divider = new Preference(getActivity());
divider.setLayoutResource(R.layout.divider_preference);
preferenceScreen.addPreference(divider);
}
/**
* Refreshes the preference list by recreating the heading and the sites allowed to access the
* chosen object preference for this fragment.
*/
private void resetList() {
PreferenceScreen preferenceScreen = getPreferenceScreen();
preferenceScreen.removeAll();
createHeader();
// Each item |i| in |mSites| and |mObjectInfos| correspond to each other.
// See SingleCategoryPreferences.addChosenObjects().
for (int i = 0; i < mSites.size() && i < mObjectInfos.size(); ++i) {
Website site = mSites.get(i);
ChosenObjectInfo info = mObjectInfos.get(i);
WebsitePreference preference = new WebsitePreference(getActivity(), site, mCategory);
preference.getExtras().putSerializable(SingleWebsitePreferences.EXTRA_SITE, site);
preference.setFragment(SingleWebsitePreferences.class.getCanonicalName());
preference.setImageView(R.drawable.ic_delete_white_24dp,
R.string.website_settings_revoke_device_permission, (View view) -> {
info.revoke();
getInfo();
});
preference.setManagedPreferenceDelegate(new ManagedPreferenceDelegate() {
@Override
public boolean isPreferenceControlledByPolicy(Preference preference) {
return info.isManaged();
}
@Override
public boolean isPreferenceControlledByCustodian(Preference preference) {
return false;
}
@Override
public boolean isPreferenceClickDisabledByPolicy(Preference preference) {
return false;
}
});
preferenceScreen.addPreference(preference);
}
// Force this list to be reloaded if the activity is resumed.
mSites = null;
}
}