Commit 4937cc62 authored by Nate Fischer's avatar Nate Fischer Committed by Commit Bot

[AW][Dev-UI] add "clear text" button to search bar

This adds a "clear text" icon to the flag UI's search bar. The "clear
text" icon is hidden by default and only appears if the user has typed
some text.

This imports the "clear text" material icon for the close button.

Bug: 1052345
Test: run_webview_instrumentation_test_apk -f FlagsFragmentTest.*
Change-Id: Ia694ca5f13a38ce25107dade3921221670251b1d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2340651
Commit-Queue: Nate Fischer <ntfschr@chromium.org>
Reviewed-by: default avatarHazem Ashmawy <hazems@chromium.org>
Cr-Commit-Position: refs/heads/master@{#795724}
parent 7f782752
......@@ -15,13 +15,19 @@ import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.not;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
import android.support.test.rule.ActivityTestRule;
import android.view.MotionEvent;
import android.view.View;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import androidx.annotation.IntDef;
import androidx.test.filters.MediumTest;
import org.hamcrest.Description;
......@@ -39,6 +45,7 @@ import org.chromium.android_webview.devui.R;
import org.chromium.android_webview.test.AwJUnit4ClassRunner;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.Feature;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
/**
* UI tests for {@link FlagsFragment}.
......@@ -108,12 +115,71 @@ public class FlagsFragmentTest {
return withCount(is(totalNumFlags));
}
@IntDef({CompoundDrawable.START, CompoundDrawable.TOP, CompoundDrawable.END,
CompoundDrawable.BOTTOM})
private @interface CompoundDrawable {
int START = 0;
int TOP = 1;
int END = 2;
int BOTTOM = 3;
}
private static Matcher<View> compoundDrawableVisible(@CompoundDrawable int position) {
return new TypeSafeMatcher<View>() {
@Override
public boolean matchesSafely(View view) {
if (!(view instanceof TextView)) {
return false;
}
Drawable[] compoundDrawables = ((TextView) view).getCompoundDrawablesRelative();
Drawable endDrawable = compoundDrawables[position];
return endDrawable != null;
}
@Override
public void describeTo(Description description) {
description.appendText("with drawable in position " + position);
}
};
}
// Click a TextView at the start/end/top/bottom. Does not check if any CompoundDrawable drawable
// is in that position, it just sends a touch event for those coordinates.
private static void tapCompoundDrawableOnUiThread(
TextView view, @CompoundDrawable int position) {
TestThreadUtils.runOnUiThreadBlocking(() -> {
long downTime = SystemClock.uptimeMillis();
long eventTime = downTime + 50;
float x = view.getWidth() / 2.0f;
float y = view.getHeight() / 2.0f;
if (position == CompoundDrawable.START) {
x = 0.0f;
} else if (position == CompoundDrawable.END) {
x = view.getWidth();
} else if (position == CompoundDrawable.TOP) {
y = 0.0f;
} else if (position == CompoundDrawable.BOTTOM) {
y = view.getHeight();
}
int metaState = 0; // no modifier keys (ex. alt/control), this is just a touch event
view.dispatchTouchEvent(MotionEvent.obtain(
downTime, eventTime, MotionEvent.ACTION_UP, x, y, metaState));
});
}
@Test
@MediumTest
@Feature({"AndroidWebView"})
public void testSearchEmptyByDefault() throws Throwable {
onView(withId(R.id.flag_search_bar)).check(matches(withText("")));
onView(withId(R.id.flag_search_bar)).check(matches(withHintText("Search flags")));
// Magnifier should always be visible, "clear text" icon should be hidden
onView(withId(R.id.flag_search_bar))
.check(matches(compoundDrawableVisible(CompoundDrawable.START)));
onView(withId(R.id.flag_search_bar))
.check(matches(not(compoundDrawableVisible(CompoundDrawable.END))));
}
@Test
......@@ -197,4 +263,78 @@ public class FlagsFragmentTest {
helper.waitForCallback(searchBarChangeCount, 2);
onView(withId(R.id.flags_list)).check(matches(withCount(totalNumFlags)));
}
@Test
@MediumTest
@Feature({"AndroidWebView"})
public void testTappingClearButtonClearsText() throws Throwable {
CallbackHelper helper = getFlagUiSearchBarListener();
int searchBarChangeCount = helper.getCallCount();
onView(withId(R.id.flag_search_bar)).perform(replaceText("logging"));
helper.waitForCallback(searchBarChangeCount, 1);
// "x" icon should visible if there's some text
onView(withId(R.id.flag_search_bar))
.check(matches(compoundDrawableVisible(CompoundDrawable.END)));
EditText searchBar = mRule.getActivity().findViewById(R.id.flag_search_bar);
tapCompoundDrawableOnUiThread(searchBar, CompoundDrawable.END);
// "x" icon disappears when text is cleared
onView(withId(R.id.flag_search_bar)).check(matches(withText("")));
onView(withId(R.id.flag_search_bar))
.check(matches(not(compoundDrawableVisible(CompoundDrawable.END))));
// Magnifier icon should still be visible
onView(withId(R.id.flag_search_bar))
.check(matches(compoundDrawableVisible(CompoundDrawable.START)));
}
@Test
@MediumTest
@Feature({"AndroidWebView"})
public void testDeletingTextHidesClearTextButton() throws Throwable {
CallbackHelper helper = getFlagUiSearchBarListener();
int searchBarChangeCount = helper.getCallCount();
onView(withId(R.id.flag_search_bar)).perform(replaceText("logging"));
helper.waitForCallback(searchBarChangeCount, 1);
// "x" icon should visible if there's some text
onView(withId(R.id.flag_search_bar))
.check(matches(compoundDrawableVisible(CompoundDrawable.END)));
onView(withId(R.id.flag_search_bar)).perform(replaceText(""));
// "x" icon disappears when text is cleared
onView(withId(R.id.flag_search_bar)).check(matches(withText("")));
onView(withId(R.id.flag_search_bar))
.check(matches(not(compoundDrawableVisible(CompoundDrawable.END))));
// Magnifier icon should still be visible
onView(withId(R.id.flag_search_bar))
.check(matches(compoundDrawableVisible(CompoundDrawable.START)));
}
@Test
@MediumTest
@Feature({"AndroidWebView"})
public void testElsewhereOnSearchBarDoesNotClearText() throws Throwable {
CallbackHelper helper = getFlagUiSearchBarListener();
int searchBarChangeCount = helper.getCallCount();
onView(withId(R.id.flag_search_bar)).perform(replaceText("logging"));
helper.waitForCallback(searchBarChangeCount, 1);
EditText searchBar = mRule.getActivity().findViewById(R.id.flag_search_bar);
tapCompoundDrawableOnUiThread(searchBar, CompoundDrawable.TOP);
// EditText should not be cleared
onView(withId(R.id.flag_search_bar)).check(matches(withText("logging")));
// "x" icon is still visible
onView(withId(R.id.flag_search_bar))
.check(matches(compoundDrawableVisible(CompoundDrawable.END)));
}
}
......@@ -135,6 +135,7 @@ android_resources("devui_resources") {
"java/res_devui/drawable/blue_circle.xml",
"java/res_devui/drawable/ic_action_home.xml",
"java/res_devui/drawable/ic_alert_error.xml",
"java/res_devui/drawable/ic_clear_text.xml",
"java/res_devui/drawable/ic_flag.xml",
"java/res_devui/drawable/ic_search.xml",
"java/res_devui/layout/activity_main.xml",
......
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M0 0h24v24H0V0z" />
<path
android:fillColor="#000000"
android:pathData="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
</vector>
......@@ -10,12 +10,16 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
......@@ -110,9 +114,17 @@ public class FlagsFragment extends DevUiBaseFragment {
EditText searchBar = view.findViewById(R.id.flag_search_bar);
searchBar.addTextChangedListener(new TextWatcher() {
private boolean mPreviouslyHadText;
@Override
public void onTextChanged(CharSequence cs, int start, int before, int count) {
mListAdapter.getFilter().filter(cs);
boolean currentlyHasText = !cs.toString().isEmpty();
// As an optimization, only change the clear text button if the search bar just now
// became empty or non-empty.
if (mPreviouslyHadText != currentlyHasText) {
setClearTextButtonEnabled(searchBar, currentlyHasText);
}
mPreviouslyHadText = currentlyHasText;
}
@Override
......@@ -133,6 +145,40 @@ public class FlagsFragment extends DevUiBaseFragment {
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
private void setClearTextButtonEnabled(EditText editText, boolean enabled) {
int iconColor = getResources().getColor(R.color.navigation_unselected);
Drawable clearTextIcon = getResources().getDrawable(R.drawable.ic_clear_text);
clearTextIcon.mutate();
clearTextIcon.setColorFilter(new PorterDuffColorFilter(iconColor, PorterDuff.Mode.SRC_IN));
// Overwrite only the end drawable (index = 2), since there's already a drawable at the
// start.
Drawable[] compoundDrawables = editText.getCompoundDrawablesRelative();
compoundDrawables[2] = enabled ? clearTextIcon : null;
editText.setCompoundDrawablesRelativeWithIntrinsicBounds(compoundDrawables[0],
compoundDrawables[1], compoundDrawables[2], compoundDrawables[3]);
// Set (or remove) the onTouchListener
if (enabled) {
editText.setOnTouchListener((View v, MotionEvent event) -> {
int x = (int) event.getX();
int iconStart = editText.getWidth() - clearTextIcon.getIntrinsicWidth();
int iconEnd = editText.getWidth();
boolean didTapIcon = x >= iconStart && x <= iconEnd;
if (didTapIcon) {
if (event.getAction() == MotionEvent.ACTION_UP) {
editText.setText("");
}
return true;
}
return false;
});
} else {
editText.setOnTouchListener(null);
}
}
/**
* Notifies the caller when ListView filtering is complete, in response to modifying the text in
* {@code R.id.flag_search_bar}.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment