Skip to content

Commit

Permalink
Add search bar to Android password settings
Browse files Browse the repository at this point in the history
By enabling the feature PasswordSearch, a search icon will appear in the
action bar in Chrome > Settings > Save Passwords.

Clicking the icon will trigger a search box that hides non-password
views.
Every newly typed letter will instantly filter passwords which
don't contain the query. Ignores case.

Update: instead of adding a new white icon, the ic_search is recolored.
Update: merged with WIP crrev/c/868213

Bug: 794108
Change-Id: I9b4e3c7754bb5b0cc56e3156a746bcbf44aa5bd3
Reviewed-on: https://chromium-review.googlesource.com/866830
Commit-Queue: Friedrich Horschig <fhorschig@chromium.org>
Reviewed-by: Maxim Kolosovskiy <kolos@chromium.org>
Reviewed-by: Theresa <twellington@chromium.org>
Cr-Commit-Position: refs/heads/master@{#531891}
  • Loading branch information
FHorschig authored and Commit Bot committed Jan 25, 2018
1 parent 63fe706 commit 03c9198
Show file tree
Hide file tree
Showing 11 changed files with 567 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:chrome="http://schemas.android.com/apk/res-auto" >

<item
android:id="@+id/menu_id_search"
android:icon="@drawable/ic_search"
android:title="@string/search"
chrome:showAsAction="ifRoom|collapseActionView"
chrome:actionViewClass="android.support.v7.widget.SearchView" />

<item
android:id="@id/menu_id_general_help"
android:icon="@drawable/ic_help_and_feedback"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ public static boolean getFieldTrialParamByFeatureAsBoolean(
"OfflinePagesDescriptivePendingStatus";
public static final String OMNIBOX_SPARE_RENDERER = "OmniboxSpareRenderer";
public static final String PAY_WITH_GOOGLE_V1 = "PayWithGoogleV1";
public static final String PASSWORD_SEARCH = "PasswordSearchMobile";
public static final String PROGRESS_BAR_THROTTLE = "ProgressBarThrottle";
public static final String PWA_PERSISTENT_NOTIFICATION = "PwaPersistentNotification";
public static final String READER_MODE_IN_CCT = "ReaderModeInCCT";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
Expand All @@ -17,17 +20,20 @@
import android.preference.PreferenceScreen;
import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.SearchView;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.inputmethod.EditorInfo;

import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.Callback;
import org.chromium.base.ContentUriUtils;
import org.chromium.base.ContextUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.preferences.ChromeBaseCheckBoxPreference;
Expand All @@ -47,6 +53,7 @@
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.util.Locale;

/**
* The "Save passwords" screen in Settings, which allows the user to enable or disable password
Expand Down Expand Up @@ -108,21 +115,21 @@ public class SavePasswordsPreferences
@Nullable
private Uri mExportFileUri;

private String mSearchQuery;
private Preference mLinkPref;
private ChromeSwitchPreference mSavePasswordsSwitch;
private ChromeBaseCheckBoxPreference mAutoSignInSwitch;
private TextMessagePreference mEmptyView;
private Menu mMenuForTesting;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActivity().setTitle(R.string.prefs_saved_passwords);
setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getActivity()));
PasswordManagerHandlerProvider.getInstance().addObserver(this);
if (ChromeFeatureList.isEnabled(EXPORT_PASSWORDS)
&& ReauthenticationManager.isReauthenticationApiAvailable()) {
setHasOptionsMenu(true);
}

setHasOptionsMenu(providesPasswordExport() || providesPasswordSearch());

if (savedInstanceState == null) return;

Expand All @@ -143,8 +150,51 @@ public void onCreate(Bundle savedInstanceState) {
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.clear();
mMenuForTesting = menu;
inflater.inflate(R.menu.save_password_preferences_action_bar_menu, menu);
menu.findItem(R.id.export_passwords).setVisible(providesPasswordExport());
menu.findItem(R.id.export_passwords).setEnabled(false);
MenuItem searchItem = menu.findItem(R.id.menu_id_search);
searchItem.setVisible(providesPasswordSearch());
if (providesPasswordSearch()) {
setUpSearchAction(searchItem);
}
}

/**
* Prepares the searchItem's icon and searchView. Sets up listeners to clicks and interactions
* with the searchItem or its searchView.
* @param searchItem the item containing the SearchView. Must not be null.
*/
private void setUpSearchAction(MenuItem searchItem) {
SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setImeOptions(EditorInfo.IME_FLAG_NO_FULLSCREEN);
searchItem.setIcon(convertToPlainWhite(searchItem.getIcon()));
searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem menuItem) {
filterPasswords(""); // Hide other menu elements.
return true; // Continue expanding.
}

@Override
public boolean onMenuItemActionCollapse(MenuItem menuItem) {
filterPasswords(null); // Reset filter to bring back all preferences.
return true; // Continue collapsing.
}
});
searchView.setOnSearchClickListener(view -> filterPasswords(""));
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return true; // Continue with default action - nothing.
}

@Override
public boolean onQueryTextChange(String query) {
return filterPasswords(query);
}
});
}

@Override
Expand Down Expand Up @@ -341,6 +391,12 @@ private void sendExportIntent() {
mExportFileUri = null;
}

private boolean filterPasswords(String query) {
mSearchQuery = query;
rebuildPasswordLists();
return false; // Query has been handled. Don't trigger default action of SearchView.
}

/**
* Empty screen message when no passwords or exceptions are stored.
*/
Expand Down Expand Up @@ -414,10 +470,13 @@ public void passwordListAvailable(int count) {
SavedPasswordEntry saved = PasswordManagerHandlerProvider.getInstance()
.getPasswordManagerHandler()
.getSavedPasswordEntry(i);
PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(getActivity());
String url = saved.getUrl();
String name = saved.getUserName();
String password = saved.getPassword();
if (shouldBeFiltered(url, name)) {
continue; // The current password won't show with the active filter, try the next.
}
PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(getActivity());
screen.setTitle(url);
screen.setOnPreferenceClickListener(this);
screen.setSummary(name);
Expand All @@ -428,10 +487,32 @@ public void passwordListAvailable(int count) {
args.putInt(PASSWORD_LIST_ID, i);
profileCategory.addPreference(screen);
}
mNoPasswords = profileCategory.getPreferenceCount() == 0;
if (mNoPasswords) {
displayManageAccountLink(); // Maybe the password is just not on the device.
displayEmptyScreenMessage();
}
}

/**
* Returns true if there is a search query that requires the exclusion of an entry based on
* the passed url or name.
* @param url the visible URL of the entry to check. May be empty but must not be null.
* @param name the visible user name of the entry to check. May be empty but must not be null.
* @return Returns whether the entry with the passed url and name should be filtered.
*/
private boolean shouldBeFiltered(final String url, final String name) {
if (mSearchQuery == null) {
return false;
}
return !url.toLowerCase(Locale.ENGLISH).contains(mSearchQuery.toLowerCase(Locale.ENGLISH))
&& !name.toLowerCase(Locale.getDefault())
.contains(mSearchQuery.toLowerCase(Locale.getDefault()));
}

@Override
public void passwordExceptionListAvailable(int count) {
if (mSearchQuery != null) return; // Don't show exceptions if a search is ongoing.
resetList(PREF_KEY_CATEGORY_EXCEPTIONS);
resetNoEntriesTextMessage();

Expand Down Expand Up @@ -515,7 +596,23 @@ public boolean onPreferenceClick(Preference preference) {
return true;
}

/**
* Convert a given icon to a plain white version by applying the MATRIX_TRANSFORM_TO_WHITE color
* filter. The resulting drawable will be brighter than a usual grayscale conversion.
*
* For grayscale conversion, use the function ColorMatrix#setSaturation(0) instead.
* @param icon The drawable to be converted.
* @return Returns the bright white version of the passed drawable.
*/
private static Drawable convertToPlainWhite(Drawable icon) {
icon.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP);
return icon;
}

private void createSavePasswordsSwitch() {
if (mSearchQuery != null) {
return; // Don't create this option when the preferences are filtered for passwords.
}
mSavePasswordsSwitch = new ChromeSwitchPreference(getActivity(), null);
mSavePasswordsSwitch.setKey(PREF_SAVE_PASSWORDS_SWITCH);
mSavePasswordsSwitch.setTitle(R.string.prefs_saved_passwords);
Expand Down Expand Up @@ -546,6 +643,9 @@ public boolean isPreferenceControlledByPolicy(Preference preference) {
}

private void createAutoSignInCheckbox() {
if (mSearchQuery != null) {
return; // Don't create this option when the preferences are filtered for passwords.
}
mAutoSignInSwitch = new ChromeBaseCheckBoxPreference(getActivity(), null);
mAutoSignInSwitch.setKey(PREF_AUTOSIGNIN_SWITCH);
mAutoSignInSwitch.setTitle(R.string.passwords_auto_signin_title);
Expand All @@ -571,20 +671,48 @@ public boolean isPreferenceControlledByPolicy(Preference preference) {
}

private void displayManageAccountLink() {
if (getPreferenceScreen().findPreference(PREF_KEY_MANAGE_ACCOUNT_LINK) == null) {
if (mLinkPref == null) {
ForegroundColorSpan colorSpan = new ForegroundColorSpan(
ApiCompatibilityUtils.getColor(getResources(), R.color.google_blue_700));
SpannableString title = SpanApplier.applySpans(
getString(R.string.manage_passwords_text),
new SpanApplier.SpanInfo("<link>", "</link>", colorSpan));
mLinkPref = new ChromeBasePreference(getActivity());
mLinkPref.setKey(PREF_KEY_MANAGE_ACCOUNT_LINK);
mLinkPref.setTitle(title);
mLinkPref.setOnPreferenceClickListener(this);
mLinkPref.setOrder(ORDER_MANAGE_ACCOUNT_LINK);
}
if (mSearchQuery != null && !mNoPasswords) {
return; // Don't add the Manage Account link if there is a search going on.
}
if (getPreferenceScreen().findPreference(PREF_KEY_MANAGE_ACCOUNT_LINK) != null) {
return; // Don't add the Manage Account link if it's present.
}
if (mLinkPref != null) {
// If we created the link before, reuse it.
getPreferenceScreen().addPreference(mLinkPref);
return;
}
ForegroundColorSpan colorSpan = new ForegroundColorSpan(
ApiCompatibilityUtils.getColor(getResources(), R.color.google_blue_700));
SpannableString title = SpanApplier.applySpans(getString(R.string.manage_passwords_text),
new SpanApplier.SpanInfo("<link>", "</link>", colorSpan));
mLinkPref = new ChromeBasePreference(getActivity());
mLinkPref.setKey(PREF_KEY_MANAGE_ACCOUNT_LINK);
mLinkPref.setTitle(title);
mLinkPref.setOnPreferenceClickListener(this);
mLinkPref.setOrder(ORDER_MANAGE_ACCOUNT_LINK);
getPreferenceScreen().addPreference(mLinkPref);
}

/**
* Returns whether the password export feature is ready to use.
* @return Returns true if the flag is set and the Reauthentication Api is available.
*/
private boolean providesPasswordExport() {
return ChromeFeatureList.isEnabled(EXPORT_PASSWORDS)
&& ReauthenticationManager.isReauthenticationApiAvailable();
}

/**
* Returns whether the password search feature is ready to use.
* @return Returns true if the flag is set.
*/
private boolean providesPasswordSearch() {
return ChromeFeatureList.isEnabled(ChromeFeatureList.PASSWORD_SEARCH);
}

@VisibleForTesting
Menu getMenuForTesting() {
return mMenuForTesting;
}
}
Loading

0 comments on commit 03c9198

Please sign in to comment.