Merge "SearchView keyboard opens and closes appropriately"

This commit is contained in:
TreeHugger Robot
2017-02-18 01:41:35 +00:00
committed by Android (Google) Code Review
3 changed files with 87 additions and 13 deletions

View File

@@ -30,6 +30,7 @@ import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.LinearLayout.LayoutParams; import android.widget.LinearLayout.LayoutParams;
import android.widget.SearchView; import android.widget.SearchView;
@@ -48,6 +49,9 @@ public class SearchFragment extends InstrumentedFragment implements
{ {
private static final String TAG = "SearchFragment"; private static final String TAG = "SearchFragment";
@VisibleForTesting
static final int SEARCH_TAG = "SearchViewTag".hashCode();
// State values // State values
private static final String STATE_QUERY = "state_query"; private static final String STATE_QUERY = "state_query";
private static final String STATE_NEVER_ENTERED_QUERY = "state_never_entered_query"; private static final String STATE_NEVER_ENTERED_QUERY = "state_never_entered_query";
@@ -80,12 +84,23 @@ public class SearchFragment extends InstrumentedFragment implements
@VisibleForTesting (otherwise = VisibleForTesting.PRIVATE) @VisibleForTesting (otherwise = VisibleForTesting.PRIVATE)
SearchFeatureProvider mSearchFeatureProvider; SearchFeatureProvider mSearchFeatureProvider;
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) private SearchResultsAdapter mSearchAdapter;
SearchResultsAdapter mSearchAdapter;
private RecyclerView mResultsRecyclerView; @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
RecyclerView mResultsRecyclerView;
private SearchView mSearchView; private SearchView mSearchView;
@VisibleForTesting
final RecyclerView.OnScrollListener mScrollListener =
new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (dy != 0) {
hideKeyboard();
}
}
};
@Override @Override
public int getMetricsCategory() { public int getMetricsCategory() {
return MetricsProto.MetricsEvent.DASHBOARD_SEARCH_RESULTS; return MetricsProto.MetricsEvent.DASHBOARD_SEARCH_RESULTS;
@@ -122,6 +137,7 @@ public class SearchFragment extends InstrumentedFragment implements
actionBar.setCustomView(mSearchView); actionBar.setCustomView(mSearchView);
actionBar.setDisplayShowCustomEnabled(true); actionBar.setDisplayShowCustomEnabled(true);
actionBar.setDisplayShowTitleEnabled(false); actionBar.setDisplayShowTitleEnabled(false);
mSearchView.requestFocus();
// Run the Index update only if we have some space // Run the Index update only if we have some space
if (!Utils.isLowStorage(activity)) { if (!Utils.isLowStorage(activity)) {
@@ -138,6 +154,7 @@ public class SearchFragment extends InstrumentedFragment implements
mResultsRecyclerView = (RecyclerView) view.findViewById(R.id.list_results); mResultsRecyclerView = (RecyclerView) view.findViewById(R.id.list_results);
mResultsRecyclerView.setAdapter(mSearchAdapter); mResultsRecyclerView.setAdapter(mSearchAdapter);
mResultsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); mResultsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
mResultsRecyclerView.addOnScrollListener(mScrollListener);
return view; return view;
} }
@@ -189,7 +206,7 @@ public class SearchFragment extends InstrumentedFragment implements
// Save submitted query. // Save submitted query.
getLoaderManager().restartLoader(SaveQueryRecorderCallback.LOADER_ID_SAVE_QUERY_TASK, null, getLoaderManager().restartLoader(SaveQueryRecorderCallback.LOADER_ID_SAVE_QUERY_TASK, null,
mSaveQueryRecorderCallback); mSaveQueryRecorderCallback);
hideKeyboard();
return true; return true;
} }
@@ -240,17 +257,33 @@ public class SearchFragment extends InstrumentedFragment implements
loaderManager.restartLoader(LOADER_ID_INSTALLED_APPS, null /* args */, this /* callback */); loaderManager.restartLoader(LOADER_ID_INSTALLED_APPS, null /* args */, this /* callback */);
} }
private SearchView makeSearchView(ActionBar actionBar, String query) { @VisibleForTesting (otherwise = VisibleForTesting.PRIVATE)
SearchView makeSearchView(ActionBar actionBar, String query) {
final SearchView searchView = new SearchView(actionBar.getThemedContext()); final SearchView searchView = new SearchView(actionBar.getThemedContext());
searchView.setIconifiedByDefault(false); searchView.setIconifiedByDefault(false);
searchView.setQuery(query, false /* submitQuery */); searchView.setQuery(query, false /* submitQuery */);
searchView.setOnQueryTextListener(this); searchView.setOnQueryTextListener(this);
searchView.setTag(SEARCH_TAG, searchView);
final LayoutParams lp = final LayoutParams lp =
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
searchView.setLayoutParams(lp); searchView.setLayoutParams(lp);
return searchView; return searchView;
} }
private void hideKeyboard() {
final Activity activity = getActivity();
if (activity != null) {
View view = activity.getCurrentFocus();
InputMethodManager imm = (InputMethodManager)
activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
if (mResultsRecyclerView != null) {
mResultsRecyclerView.requestFocus();
}
}
private class SaveQueryRecorderCallback implements LoaderManager.LoaderCallbacks<Void> { private class SaveQueryRecorderCallback implements LoaderManager.LoaderCallbacks<Void> {
// TODO: make a generic background task manager to handle one-off tasks like this one. // TODO: make a generic background task manager to handle one-off tasks like this one.

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.android.settings.search2;
import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.widget.SearchView;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.matcher.ViewMatchers.hasFocus;
import static android.support.test.espresso.matcher.ViewMatchers.withClassName;
import static android.support.test.espresso.matcher.ViewMatchers.withTagKey;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.core.AllOf.allOf;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class SearchFragmentEspressoTest {
@Rule
public ActivityTestRule<SearchActivity> mActivityRule =
new ActivityTestRule<>(SearchActivity.class, true, true);
@Test
public void test_OpenKeyboardOnSearchLaunch() {
onView(allOf(hasFocus(), withTagKey(SearchFragment.SEARCH_TAG)))
.check(matches(withClassName(containsString(SearchView.class.getName()))));
}
}

View File

@@ -20,16 +20,12 @@ import android.content.Context;
import android.content.Loader; import android.content.Loader;
import android.os.Bundle; import android.os.Bundle;
import android.os.UserManager;
import com.android.internal.logging.nano.MetricsProto; import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig; import com.android.settings.TestConfig;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.testutils.DatabaseTestUtils;
import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -41,14 +37,11 @@ import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import org.robolectric.util.ActivityController; import org.robolectric.util.ActivityController;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
@@ -248,5 +241,4 @@ public class SearchFragmentTest {
verify(fragment, times(2)).onLoadFinished(any(Loader.class), any(List.class)); verify(fragment, times(2)).onLoadFinished(any(Loader.class), any(List.class));
} }
} }