SearchFragmentTest.java revision 5035f8c5590a8eea7bf372a325a4aa0ee1e96149
1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18package com.android.settings.search;
19
20import android.app.LoaderManager;
21import android.content.Context;
22import android.content.Intent;
23import android.content.Loader;
24import android.os.Bundle;
25import android.util.Pair;
26import android.view.View;
27
28import com.android.internal.logging.nano.MetricsProto;
29import com.android.settings.R;
30import com.android.settings.SettingsActivity;
31import com.android.settings.testutils.SettingsRobolectricTestRunner;
32import com.android.settings.TestConfig;
33import com.android.settings.testutils.DatabaseTestUtils;
34import com.android.settings.testutils.FakeFeatureFactory;
35import com.android.settings.testutils.shadow.SettingsShadowResources;
36
37import org.junit.After;
38import org.junit.Before;
39import org.junit.Test;
40import org.junit.runner.RunWith;
41import org.mockito.Answers;
42import org.mockito.ArgumentCaptor;
43import org.mockito.ArgumentMatcher;
44import org.mockito.Captor;
45import org.mockito.Mock;
46import org.mockito.MockitoAnnotations;
47import org.robolectric.Robolectric;
48import org.robolectric.RuntimeEnvironment;
49import org.robolectric.annotation.Config;
50import org.robolectric.util.ActivityController;
51import org.robolectric.util.ReflectionHelpers;
52
53import java.util.Set;
54
55import static com.google.common.truth.Truth.assertThat;
56import static org.mockito.ArgumentMatchers.nullable;
57import static org.mockito.Matchers.any;
58import static org.mockito.Matchers.anyInt;
59import static org.mockito.Matchers.anyString;
60import static org.mockito.Matchers.argThat;
61import static org.mockito.Matchers.eq;
62import static org.mockito.Mockito.mock;
63import static org.mockito.Mockito.never;
64import static org.mockito.Mockito.spy;
65import static org.mockito.Mockito.times;
66import static org.mockito.Mockito.verify;
67import static org.mockito.Mockito.when;
68
69@RunWith(SettingsRobolectricTestRunner.class)
70@Config(manifest = TestConfig.MANIFEST_PATH,
71        sdk = TestConfig.SDK_VERSION,
72        shadows = {
73                SettingsShadowResources.class,
74                SettingsShadowResources.SettingsShadowTheme.class,
75        })
76public class SearchFragmentTest {
77
78    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
79    private Context mContext;
80    @Mock
81    private DatabaseResultLoader mDatabaseResultLoader;
82    @Mock
83    private InstalledAppResultLoader mInstalledAppResultLoader;
84    @Mock
85    private AccessibilityServiceResultLoader mAccessibilityServiceResultLoader;
86
87    @Mock
88    private SavedQueryLoader mSavedQueryLoader;
89    @Mock
90    private SavedQueryController mSavedQueryController;
91    @Mock
92    private SearchResultsAdapter mSearchResultsAdapter;
93    @Captor
94    private ArgumentCaptor<String> mQueryCaptor = ArgumentCaptor.forClass(String.class);
95
96    private FakeFeatureFactory mFeatureFactory;
97
98    @Before
99    public void setUp() {
100        MockitoAnnotations.initMocks(this);
101
102        mFeatureFactory = FakeFeatureFactory.setupForTest(mContext);
103    }
104
105    @After
106    public void tearDown() {
107        DatabaseTestUtils.clearDb(RuntimeEnvironment.application);
108    }
109
110    @Test
111    public void screenRotate_shouldPersistQuery() {
112        when(mFeatureFactory.searchFeatureProvider
113                .getDatabaseSearchLoader(any(Context.class), anyString()))
114                .thenReturn(mDatabaseResultLoader);
115        when(mFeatureFactory.searchFeatureProvider
116                .getInstalledAppSearchLoader(any(Context.class), anyString()))
117                .thenReturn(mInstalledAppResultLoader);
118        when(mFeatureFactory.searchFeatureProvider
119                .getAccessibilityServiceResultLoader(any(Context.class), anyString()))
120                .thenReturn(mAccessibilityServiceResultLoader);
121        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
122                .thenReturn(mSavedQueryLoader);
123
124        final Bundle bundle = new Bundle();
125        final String testQuery = "test";
126        ActivityController<SearchActivity> activityController =
127                Robolectric.buildActivity(SearchActivity.class);
128        activityController.setup();
129        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
130                .findFragmentById(R.id.main_content);
131
132        ReflectionHelpers.setField(fragment, "mShowingSavedQuery", false);
133        fragment.mQuery = testQuery;
134
135        activityController.saveInstanceState(bundle).pause().stop().destroy();
136
137        activityController = Robolectric.buildActivity(SearchActivity.class);
138        activityController.setup(bundle);
139
140        assertThat(fragment.mQuery).isEqualTo(testQuery);
141    }
142
143    @Test
144    public void screenRotateEmptyString_ShouldNotCrash() {
145        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
146                .thenReturn(mSavedQueryLoader);
147
148        final Bundle bundle = new Bundle();
149        ActivityController<SearchActivity> activityController =
150                Robolectric.buildActivity(SearchActivity.class);
151        activityController.setup();
152        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
153                .findFragmentById(R.id.main_content);
154        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
155                .thenReturn(true);
156
157        fragment.mQuery = "";
158
159        activityController.saveInstanceState(bundle).pause().stop().destroy();
160
161        activityController = Robolectric.buildActivity(SearchActivity.class);
162        activityController.setup(bundle);
163
164        verify(mFeatureFactory.searchFeatureProvider, never())
165                .getDatabaseSearchLoader(any(Context.class), anyString());
166        verify(mFeatureFactory.searchFeatureProvider, never())
167                .getInstalledAppSearchLoader(any(Context.class), anyString());
168    }
169
170    @Test
171    public void queryTextChange_shouldTriggerLoaderAndInitializeSearch() {
172        when(mFeatureFactory.searchFeatureProvider
173                .getDatabaseSearchLoader(any(Context.class), anyString()))
174                .thenReturn(mDatabaseResultLoader);
175        when(mFeatureFactory.searchFeatureProvider
176                .getInstalledAppSearchLoader(any(Context.class), anyString()))
177                .thenReturn(mInstalledAppResultLoader);
178        when(mFeatureFactory.searchFeatureProvider
179                .getAccessibilityServiceResultLoader(any(Context.class), anyString()))
180                .thenReturn(mAccessibilityServiceResultLoader);
181        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
182                .thenReturn(mSavedQueryLoader);
183
184        final String testQuery = "test";
185        ActivityController<SearchActivity> activityController =
186                Robolectric.buildActivity(SearchActivity.class);
187        activityController.setup();
188        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
189                .findFragmentById(R.id.main_content);
190        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
191                .thenReturn(true);
192
193        ReflectionHelpers.setField(fragment, "mSearchAdapter", mSearchResultsAdapter);
194        fragment.onQueryTextChange(testQuery);
195        activityController.get().onBackPressed();
196
197        activityController.pause().stop().destroy();
198
199        verify(mFeatureFactory.metricsFeatureProvider, never()).action(
200                any(Context.class),
201                eq(MetricsProto.MetricsEvent.ACTION_LEAVE_SEARCH_RESULT_WITHOUT_QUERY));
202        verify(mFeatureFactory.metricsFeatureProvider).histogram(
203                any(Context.class), eq(SearchFragment.RESULT_CLICK_COUNT), eq(0));
204        verify(mFeatureFactory.searchFeatureProvider)
205                .getDatabaseSearchLoader(any(Context.class), anyString());
206        verify(mFeatureFactory.searchFeatureProvider)
207                .getInstalledAppSearchLoader(any(Context.class), anyString());
208        verify(mSearchResultsAdapter).initializeSearch(mQueryCaptor.capture());
209        assertThat(mQueryCaptor.getValue()).isEqualTo(testQuery);
210    }
211
212    @Test
213    public void onSearchResultsDisplayed_noResult_shouldShowNoResultView() {
214        ActivityController<SearchActivity> activityController =
215                Robolectric.buildActivity(SearchActivity.class);
216        activityController.setup();
217        SearchFragment fragment = spy((SearchFragment) activityController.get().getFragmentManager()
218                .findFragmentById(R.id.main_content));
219        fragment.onSearchResultsDisplayed(0 /* count */);
220
221        assertThat(fragment.mNoResultsView.getVisibility()).isEqualTo(View.VISIBLE);
222        verify(mFeatureFactory.metricsFeatureProvider).visible(
223                any(Context.class),
224                anyInt(),
225                eq(MetricsProto.MetricsEvent.SETTINGS_SEARCH_NO_RESULT));
226    }
227
228    @Test
229    public void queryTextChangeToEmpty_shouldLoadSavedQueryAndNotInitializeSearch() {
230        when(mFeatureFactory.searchFeatureProvider
231                .getDatabaseSearchLoader(any(Context.class), anyString()))
232                .thenReturn(mDatabaseResultLoader);
233        when(mFeatureFactory.searchFeatureProvider
234                .getInstalledAppSearchLoader(any(Context.class), anyString()))
235                .thenReturn(mInstalledAppResultLoader);
236        when(mFeatureFactory.searchFeatureProvider
237                .getAccessibilityServiceResultLoader(any(Context.class), anyString()))
238                .thenReturn(mAccessibilityServiceResultLoader);
239        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
240                .thenReturn(mSavedQueryLoader);
241        ActivityController<SearchActivity> activityController =
242                Robolectric.buildActivity(SearchActivity.class);
243        activityController.setup();
244        SearchFragment fragment = spy((SearchFragment) activityController.get().getFragmentManager()
245                .findFragmentById(R.id.main_content));
246        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
247                .thenReturn(true);
248        ReflectionHelpers.setField(fragment, "mSavedQueryController", mSavedQueryController);
249        ReflectionHelpers.setField(fragment, "mSearchAdapter", mSearchResultsAdapter);
250        fragment.mQuery = "123";
251
252        fragment.onQueryTextChange("");
253
254        verify(mFeatureFactory.searchFeatureProvider, never())
255                .getDatabaseSearchLoader(any(Context.class), anyString());
256        verify(mFeatureFactory.searchFeatureProvider, never())
257                .getInstalledAppSearchLoader(any(Context.class), anyString());
258        verify(mSavedQueryController).loadSavedQueries();
259        verify(mSearchResultsAdapter, never()).initializeSearch(anyString());
260    }
261
262    @Test
263    public void updateIndex_TriggerOnCreate() {
264        when(mFeatureFactory.searchFeatureProvider
265                .getDatabaseSearchLoader(any(Context.class), anyString()))
266                .thenReturn(mDatabaseResultLoader);
267        when(mFeatureFactory.searchFeatureProvider
268                .getInstalledAppSearchLoader(any(Context.class), anyString()))
269                .thenReturn(mInstalledAppResultLoader);
270        when(mFeatureFactory.searchFeatureProvider
271                .getAccessibilityServiceResultLoader(any(Context.class), anyString()))
272                .thenReturn(mAccessibilityServiceResultLoader);
273        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
274                .thenReturn(mSavedQueryLoader);
275
276        ActivityController<SearchActivity> activityController =
277                Robolectric.buildActivity(SearchActivity.class);
278        activityController.setup();
279        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
280                .findFragmentById(R.id.main_content);
281        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
282                .thenReturn(true);
283
284        fragment.onAttach(null);
285        verify(mFeatureFactory.searchFeatureProvider).updateIndexAsync(any(Context.class),
286                any(IndexingCallback.class));
287    }
288
289    @Test
290    public void syncLoaders_MergeWhenAllLoadersDone() {
291        when(mFeatureFactory.searchFeatureProvider
292                .getDatabaseSearchLoader(any(Context.class), anyString()))
293                .thenReturn(new MockDBLoader(RuntimeEnvironment.application));
294        when(mFeatureFactory.searchFeatureProvider
295                .getInstalledAppSearchLoader(any(Context.class), anyString()))
296                .thenReturn(new MockAppLoader(RuntimeEnvironment.application));
297        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
298                .thenReturn(mSavedQueryLoader);
299
300        ActivityController<SearchActivity> activityController =
301                Robolectric.buildActivity(SearchActivity.class);
302        activityController.setup();
303
304        SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager()
305                .findFragmentById(R.id.main_content));
306        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
307                .thenReturn(true);
308
309        fragment.onQueryTextChange("non-empty");
310
311        Robolectric.flushForegroundThreadScheduler();
312
313        verify(fragment, times(2)).onLoadFinished(any(Loader.class), any(Set.class));
314    }
315
316    @Test
317    public void whenNoQuery_HideFeedbackIsCalled() {
318        when(mFeatureFactory.searchFeatureProvider
319                .getDatabaseSearchLoader(any(Context.class), anyString()))
320                .thenReturn(new MockDBLoader(RuntimeEnvironment.application));
321        when(mFeatureFactory.searchFeatureProvider
322                .getInstalledAppSearchLoader(any(Context.class), anyString()))
323                .thenReturn(new MockAppLoader(RuntimeEnvironment.application));
324        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
325                .thenReturn(mSavedQueryLoader);
326
327        ActivityController<SearchActivity> activityController =
328                Robolectric.buildActivity(SearchActivity.class);
329        activityController.setup();
330        SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager()
331                .findFragmentById(R.id.main_content));
332        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
333                .thenReturn(true);
334        when(fragment.getLoaderManager()).thenReturn(mock(LoaderManager.class));
335
336        fragment.onQueryTextChange("");
337        Robolectric.flushForegroundThreadScheduler();
338
339        verify(mFeatureFactory.searchFeatureProvider).hideFeedbackButton();
340    }
341
342    @Test
343    public void onLoadFinished_ShowsFeedback() {
344        when(mFeatureFactory.searchFeatureProvider
345                .getDatabaseSearchLoader(any(Context.class), anyString()))
346                .thenReturn(new MockDBLoader(RuntimeEnvironment.application));
347        when(mFeatureFactory.searchFeatureProvider
348                .getInstalledAppSearchLoader(any(Context.class), anyString()))
349                .thenReturn(new MockAppLoader(RuntimeEnvironment.application));
350        when(mFeatureFactory.searchFeatureProvider
351                .getAccessibilityServiceResultLoader(any(Context.class), anyString()))
352                .thenReturn(new MockAccessiblityLoader(RuntimeEnvironment.application));
353        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
354                .thenReturn(mSavedQueryLoader);
355        ActivityController<SearchActivity> activityController =
356                Robolectric.buildActivity(SearchActivity.class);
357        activityController.setup();
358        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
359                .findFragmentById(R.id.main_content);
360        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
361                .thenReturn(true);
362
363        fragment.onQueryTextChange("non-empty");
364        Robolectric.flushForegroundThreadScheduler();
365
366        verify(mFeatureFactory.searchFeatureProvider).showFeedbackButton(any(SearchFragment.class),
367                any(View.class));
368    }
369
370    @Test
371    public void preIndexingFinished_isIndexingFinishedFlag_isFalse() {
372        ActivityController<SearchActivity> activityController =
373                Robolectric.buildActivity(SearchActivity.class);
374        activityController.setup();
375        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
376                .findFragmentById(R.id.main_content);
377
378        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
379                .thenReturn(false);
380    }
381
382    @Test
383    public void onIndexingFinished_notShowingSavedQuery_initLoaders() {
384        ActivityController<SearchActivity> activityController =
385                Robolectric.buildActivity(SearchActivity.class);
386        activityController.setup();
387        SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager()
388                .findFragmentById(R.id.main_content));
389        final LoaderManager loaderManager = mock(LoaderManager.class);
390        when(fragment.getLoaderManager()).thenReturn(loaderManager);
391        fragment.mShowingSavedQuery = false;
392        fragment.mQuery = null;
393
394        fragment.onIndexingFinished();
395
396        verify(loaderManager).initLoader(eq(SearchFragment.LOADER_ID_DATABASE),
397                eq(null), any(LoaderManager.LoaderCallbacks.class));
398        verify(loaderManager).initLoader(eq(SearchFragment.LOADER_ID_INSTALLED_APPS),
399                eq(null), any(LoaderManager.LoaderCallbacks.class));
400    }
401
402    @Test
403    public void onIndexingFinished_showingSavedQuery_loadsSavedQueries() {
404        ActivityController<SearchActivity> activityController =
405                Robolectric.buildActivity(SearchActivity.class);
406        activityController.setup();
407        SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager()
408                .findFragmentById(R.id.main_content));
409        fragment.mShowingSavedQuery = true;
410        ReflectionHelpers.setField(fragment, "mSavedQueryController", mSavedQueryController);
411
412        fragment.onIndexingFinished();
413
414        verify(fragment.mSavedQueryController).loadSavedQueries();
415    }
416
417    @Test
418    public void onIndexingFinished_noActivity_shouldNotCrash() {
419        ActivityController<SearchActivity> activityController =
420                Robolectric.buildActivity(SearchActivity.class);
421        activityController.setup();
422        SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager()
423                .findFragmentById(R.id.main_content));
424        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
425                .thenReturn(true);
426        fragment.mQuery = "bright";
427        ReflectionHelpers.setField(fragment, "mLoaderManager", null);
428        ReflectionHelpers.setField(fragment, "mHost", null);
429
430        fragment.onIndexingFinished();
431        // no crash
432    }
433
434    @Test
435    public void onSearchResultClicked_shouldLogResultMeta() {
436        SearchFragment fragment = new SearchFragment();
437        ReflectionHelpers.setField(fragment, "mMetricsFeatureProvider",
438                mFeatureFactory.metricsFeatureProvider);
439        ReflectionHelpers.setField(fragment, "mSearchFeatureProvider",
440                mFeatureFactory.searchFeatureProvider);
441        ReflectionHelpers.setField(fragment, "mSearchAdapter", mock(SearchResultsAdapter.class));
442        fragment.mSavedQueryController = mock(SavedQueryController.class);
443
444        // Should log result name, result count, clicked rank, etc.
445        final SearchViewHolder resultViewHolder = mock(SearchViewHolder.class);
446        when(resultViewHolder.getClickActionMetricName())
447                .thenReturn(MetricsProto.MetricsEvent.ACTION_CLICK_SETTINGS_SEARCH_RESULT);
448        ResultPayload payLoad = new ResultPayload(
449                (new Intent()).putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, "test_setting"));
450        SearchResult searchResult = new SearchResult.Builder()
451                .setStableId(payLoad.hashCode())
452                .setPayload(payLoad)
453                .setTitle("setting_title")
454                .build();
455        fragment.onSearchResultClicked(resultViewHolder, searchResult);
456
457        verify(mFeatureFactory.metricsFeatureProvider).action(
458                nullable(Context.class),
459                eq(MetricsProto.MetricsEvent.ACTION_CLICK_SETTINGS_SEARCH_RESULT),
460                eq("test_setting"),
461                argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SEARCH_RESULT_COUNT)),
462                argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SEARCH_RESULT_RANK)),
463                argThat(pairMatches(MetricsProto.MetricsEvent
464                                .FIELD_SETTINGS_SEARCH_RESULT_ASYNC_RANKING_STATE)),
465                argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SEARCH_QUERY_LENGTH)));
466
467        verify(mFeatureFactory.searchFeatureProvider).searchResultClicked(nullable(Context.class),
468                nullable(String.class), eq(searchResult));
469    }
470
471    @Test
472    public void onResume_shouldCallSearchRankingWarmupIfSmartSearchRankingEnabled(){
473        when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(any(Context.class)))
474                .thenReturn(true);
475
476        ActivityController<SearchActivity> activityController =
477                Robolectric.buildActivity(SearchActivity.class);
478        activityController.setup();
479        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
480                .findFragmentById(R.id.main_content);
481
482        verify(mFeatureFactory.searchFeatureProvider)
483                .searchRankingWarmup(any(Context.class));
484    }
485
486    @Test
487    public void onResume_shouldNotCallSearchRankingWarmupIfSmartSearchRankingDisabled(){
488        when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(any(Context.class)))
489                .thenReturn(false);
490
491        ActivityController<SearchActivity> activityController =
492                Robolectric.buildActivity(SearchActivity.class);
493        activityController.setup();
494        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
495                .findFragmentById(R.id.main_content);
496
497        verify(mFeatureFactory.searchFeatureProvider, never())
498                .searchRankingWarmup(any(Context.class));
499    }
500
501    private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag) {
502        return pair -> pair.first == tag;
503    }
504}
505