Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 592e4d73 authored by Matthew Fritze's avatar Matthew Fritze
Browse files

SearchView keyboard opens and closes appropriately

- Keyboard opens when Search View opens
- Keyboard closes on query text submit
- Keyboard closes when user scrolls through results

Change-Id: I7cef383a2799eeb456fbaa07b8577c88cd35a997
Fixes: 34723251, 35164545
Test: SearchFragmentEspressoTest, manual test requested from QA
parent 007d120c
Loading
Loading
Loading
Loading
+38 −5
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.LinearLayout.LayoutParams;
import android.widget.SearchView;

@@ -48,6 +49,9 @@ public class SearchFragment extends InstrumentedFragment implements
{
    private static final String TAG = "SearchFragment";

    @VisibleForTesting
    static final int SEARCH_TAG = "SearchViewTag".hashCode();

    // State values
    private static final String STATE_QUERY = "state_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)
    SearchFeatureProvider mSearchFeatureProvider;

    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
    SearchResultsAdapter mSearchAdapter;
    private SearchResultsAdapter mSearchAdapter;

    private RecyclerView mResultsRecyclerView;
    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
    RecyclerView mResultsRecyclerView;
    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
    public int getMetricsCategory() {
        return MetricsProto.MetricsEvent.DASHBOARD_SEARCH_RESULTS;
@@ -122,6 +137,7 @@ public class SearchFragment extends InstrumentedFragment implements
        actionBar.setCustomView(mSearchView);
        actionBar.setDisplayShowCustomEnabled(true);
        actionBar.setDisplayShowTitleEnabled(false);
        mSearchView.requestFocus();

        // Run the Index update only if we have some space
        if (!Utils.isLowStorage(activity)) {
@@ -138,6 +154,7 @@ public class SearchFragment extends InstrumentedFragment implements
        mResultsRecyclerView = (RecyclerView) view.findViewById(R.id.list_results);
        mResultsRecyclerView.setAdapter(mSearchAdapter);
        mResultsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
        mResultsRecyclerView.addOnScrollListener(mScrollListener);
        return view;
    }

@@ -189,7 +206,7 @@ public class SearchFragment extends InstrumentedFragment implements
        // Save submitted query.
        getLoaderManager().restartLoader(SaveQueryRecorderCallback.LOADER_ID_SAVE_QUERY_TASK, null,
                mSaveQueryRecorderCallback);

        hideKeyboard();
        return true;
    }

@@ -240,17 +257,33 @@ public class SearchFragment extends InstrumentedFragment implements
        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());
        searchView.setIconifiedByDefault(false);
        searchView.setQuery(query, false /* submitQuery */);
        searchView.setOnQueryTextListener(this);
        searchView.setTag(SEARCH_TAG, searchView);
        final LayoutParams lp =
                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        searchView.setLayoutParams(lp);
        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> {
        // TODO: make a generic background task manager to handle one-off tasks like this one.

+49 −0
Original line number Diff line number Diff line
/*
 * 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()))));
    }
}
+0 −8
Original line number Diff line number Diff line
@@ -20,16 +20,12 @@ import android.content.Context;
import android.content.Loader;
import android.os.Bundle;

import android.os.UserManager;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.testutils.DatabaseTestUtils;
import com.android.settings.testutils.FakeFeatureFactory;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -41,14 +37,11 @@ import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.util.ActivityController;


import java.util.ArrayList;
import java.util.List;

import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -248,5 +241,4 @@ public class SearchFragmentTest {

        verify(fragment, times(2)).onLoadFinished(any(Loader.class), any(List.class));
    }

}