1 /*
2  * Copyright (C) 2016 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 package com.android.documentsui.queries;
18 
19 import static android.provider.DocumentsContract.QUERY_ARG_DISPLAY_NAME;
20 import static android.provider.DocumentsContract.QUERY_ARG_FILE_SIZE_OVER;
21 import static android.provider.DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER;
22 import static android.provider.DocumentsContract.QUERY_ARG_MIME_TYPES;
23 import static android.provider.DocumentsContract.Root.FLAG_SUPPORTS_SEARCH;
24 
25 import static com.android.documentsui.base.State.ACTION_GET_CONTENT;
26 
27 import static junit.framework.Assert.assertEquals;
28 import static junit.framework.Assert.assertFalse;
29 import static junit.framework.Assert.assertTrue;
30 
31 import static org.mockito.Mockito.mock;
32 import static org.mockito.Mockito.spy;
33 import static org.mockito.Mockito.times;
34 import static org.mockito.Mockito.verify;
35 import static org.mockito.Mockito.when;
36 
37 import android.content.Intent;
38 import android.os.Bundle;
39 import android.os.Handler;
40 import android.provider.DocumentsContract;
41 import android.text.TextUtils;
42 import android.view.View;
43 import android.view.ViewGroup;
44 
45 import androidx.annotation.Nullable;
46 import androidx.test.filters.SmallTest;
47 import androidx.test.runner.AndroidJUnit4;
48 
49 import com.android.documentsui.R;
50 import com.android.documentsui.base.DocumentInfo;
51 import com.android.documentsui.base.DocumentStack;
52 import com.android.documentsui.base.EventHandler;
53 import com.android.documentsui.base.RootInfo;
54 import com.android.documentsui.queries.SearchViewManager.SearchManagerListener;
55 import com.android.documentsui.testing.TestEventHandler;
56 import com.android.documentsui.testing.TestHandler;
57 import com.android.documentsui.testing.TestMenu;
58 import com.android.documentsui.testing.TestMenuItem;
59 import com.android.documentsui.testing.TestTimer;
60 
61 import org.junit.Before;
62 import org.junit.Test;
63 import org.junit.runner.RunWith;
64 
65 import java.util.HashSet;
66 import java.util.Set;
67 import java.util.Timer;
68 import java.util.TimerTask;
69 
70 @RunWith(AndroidJUnit4.class)
71 @SmallTest
72 public final class SearchViewManagerTest {
73 
74     private TestEventHandler<String> mTestEventHandler;
75     private TestTimer mTestTimer;
76     private TestHandler mTestHandler;
77     private TestMenu mTestMenu;
78     private TestMenuItem mSearchMenuItem;
79     private TestableSearchViewManager mSearchViewManager;
80     private SearchChipViewManager mSearchChipViewManager;
81 
82     private boolean mListenerOnSearchChangedCalled;
83 
84     @Before
setUp()85     public void setUp() {
86         mTestEventHandler = new TestEventHandler<>();
87         mTestTimer = new TestTimer();
88         mTestHandler = new TestHandler();
89 
90         final SearchManagerListener searchListener = new SearchManagerListener() {
91             @Override
92             public void onSearchChanged(@Nullable String query) {
93                 mListenerOnSearchChangedCalled = true;
94             }
95 
96             @Override
97             public void onSearchFinished() {
98             }
99 
100             @Override
101             public void onSearchViewChanged(boolean opened) {
102             }
103 
104             @Override
105             public void onSearchChipStateChanged(View v) {
106             }
107 
108             @Override
109             public void onSearchViewFocusChanged(boolean hasFocus) {
110             }
111 
112             @Override
113             public void onSearchViewClearClicked() {
114             }
115         };
116 
117         ViewGroup chipGroup = mock(ViewGroup.class);
118         mSearchChipViewManager = spy(new SearchChipViewManager(chipGroup));
119         mSearchViewManager = new TestableSearchViewManager(searchListener, mTestEventHandler,
120                 mSearchChipViewManager, null /* savedState */, mTestTimer, mTestHandler);
121 
122         mTestMenu = TestMenu.create();
123         mSearchMenuItem = mTestMenu.findItem(R.id.option_menu_search);
124         mSearchViewManager.install(mTestMenu, true, false);
125     }
126 
127     private static class TestableSearchViewManager extends SearchViewManager {
128 
129         private String mHistoryRecorded;
130 
TestableSearchViewManager( SearchManagerListener listener, EventHandler<String> commandProcessor, SearchChipViewManager chipViewManager, @Nullable Bundle savedState, Timer timer, Handler handler)131         public TestableSearchViewManager(
132                 SearchManagerListener listener,
133                 EventHandler<String> commandProcessor,
134                 SearchChipViewManager chipViewManager,
135                 @Nullable Bundle savedState,
136                 Timer timer,
137                 Handler handler) {
138             super(listener, commandProcessor, chipViewManager, savedState, timer, handler);
139         }
140 
141         @Override
createSearchTask(String newText)142         public TimerTask createSearchTask(String newText) {
143             TimerTask task = super.createSearchTask(newText);
144             TestTimer.Task testTask = new TestTimer.Task(task);
145             return testTask;
146         }
147 
148         @Override
recordHistory()149         public void recordHistory() {
150             mHistoryRecorded = getCurrentSearch();
151         }
152 
getRecordedHistory()153         public String getRecordedHistory() {
154             return mHistoryRecorded;
155         }
156     }
157 
fastForwardTo(long timeMs)158     private void fastForwardTo(long timeMs) {
159         mTestTimer.fastForwardTo(timeMs);
160         mTestHandler.dispatchAllMessages();
161     }
162 
163 
164     @Test
testParseQueryContent_ActionIsNotMatched_NotParseQueryContent()165     public void testParseQueryContent_ActionIsNotMatched_NotParseQueryContent() {
166         final String queryString = "query";
167         Intent intent = new Intent();
168         intent.putExtra(Intent.EXTRA_CONTENT_QUERY, queryString);
169 
170         mSearchViewManager.parseQueryContentFromIntent(intent, -1);
171         assertTrue(mSearchViewManager.getQueryContentFromIntent() == null);
172     }
173 
174     @Test
testParseQueryContent_queryContentIsMatched()175     public void testParseQueryContent_queryContentIsMatched() {
176         final String queryString = "query";
177         Intent intent = new Intent();
178         intent.putExtra(Intent.EXTRA_CONTENT_QUERY, queryString);
179 
180         mSearchViewManager.parseQueryContentFromIntent(intent, ACTION_GET_CONTENT);
181         assertEquals(queryString, mSearchViewManager.getQueryContentFromIntent());
182     }
183 
184     @Test
testIsExpanded_ExpandsOnClick()185     public void testIsExpanded_ExpandsOnClick() {
186         mSearchViewManager.onClick(null);
187         assertTrue(mSearchViewManager.isExpanded());
188     }
189 
190     @Test
testIsExpanded_CollapsesOnMenuItemActionCollapse()191     public void testIsExpanded_CollapsesOnMenuItemActionCollapse() {
192         mSearchViewManager.onClick(null);
193         mSearchViewManager.onMenuItemActionCollapse(null);
194         assertFalse(mSearchViewManager.isExpanded());
195     }
196 
197     @Test
testIsSearching_TrueHasCheckedChip()198     public void testIsSearching_TrueHasCheckedChip() throws Exception {
199         mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList();
200         assertTrue(mSearchViewManager.isSearching());
201     }
202 
203     @Test
testIsSearching_FalseOnClick()204     public void testIsSearching_FalseOnClick() throws Exception {
205         mSearchViewManager.onClick(null);
206         assertFalse(mSearchViewManager.isSearching());
207     }
208 
209     @Test
testIsSearching_TrueOnQueryTextSubmit()210     public void testIsSearching_TrueOnQueryTextSubmit() throws Exception {
211         mSearchViewManager.onClick(null);
212         mSearchViewManager.onQueryTextSubmit("query");
213         assertTrue(mSearchViewManager.isSearching());
214     }
215 
216     @Test
testIsSearching_FalseImmediatelyAfterOnQueryTextChange()217     public void testIsSearching_FalseImmediatelyAfterOnQueryTextChange() throws Exception {
218         mSearchViewManager.onClick(null);
219         mSearchViewManager.onQueryTextChange("q");
220         assertFalse(mSearchViewManager.isSearching());
221     }
222 
223     @Test
testIsSearching_TrueAfterOnQueryTextChangeAndWait()224     public void testIsSearching_TrueAfterOnQueryTextChangeAndWait() throws Exception {
225         mSearchViewManager.onClick(null);
226         mSearchViewManager.onQueryTextChange("q");
227         fastForwardTo(SearchViewManager.SEARCH_DELAY_MS);
228         assertTrue(mSearchViewManager.isSearching());
229     }
230 
231     @Test
testIsSearching_FalseWhenSecondOnQueryTextChangeResetsTimer()232     public void testIsSearching_FalseWhenSecondOnQueryTextChangeResetsTimer() throws Exception {
233         mSearchViewManager.onClick(null);
234         mSearchViewManager.onQueryTextChange("q");
235         fastForwardTo(SearchViewManager.SEARCH_DELAY_MS - 1);
236         mSearchViewManager.onQueryTextChange("qu");
237         fastForwardTo(SearchViewManager.SEARCH_DELAY_MS);
238         assertFalse(mSearchViewManager.isSearching());
239     }
240 
241     @Test
testIsSearching_TrueAfterSecondOnQueryTextChangeResetsTimer()242     public void testIsSearching_TrueAfterSecondOnQueryTextChangeResetsTimer() throws Exception {
243         mSearchViewManager.onClick(null);
244         mSearchViewManager.onQueryTextChange("q");
245         fastForwardTo(SearchViewManager.SEARCH_DELAY_MS - 1);
246         mSearchViewManager.onQueryTextChange("qu");
247         fastForwardTo(SearchViewManager.SEARCH_DELAY_MS * 2);
248         assertTrue(mSearchViewManager.isSearching());
249     }
250 
251     @Test
testIsSearching_FalseIfSearchCanceled()252     public void testIsSearching_FalseIfSearchCanceled() throws Exception {
253         mSearchViewManager.onClick(null);
254         mSearchViewManager.onQueryTextChange("q");
255         mSearchViewManager.cancelSearch();
256         fastForwardTo(SearchViewManager.SEARCH_DELAY_MS);
257         assertFalse(mSearchViewManager.isSearching());
258     }
259 
260     @Test
testOnSearchChanged_CalledAfterOnQueryTextSubmit()261     public void testOnSearchChanged_CalledAfterOnQueryTextSubmit() throws Exception {
262         mSearchViewManager.onClick(null);
263         mSearchViewManager.onQueryTextSubmit("q");
264         assertTrue(mListenerOnSearchChangedCalled);
265     }
266 
267     @Test
testOnSearchChanged_NotCalledImmediatelyAfterOnQueryTextChanged()268     public void testOnSearchChanged_NotCalledImmediatelyAfterOnQueryTextChanged() throws Exception {
269         mSearchViewManager.onClick(null);
270         mSearchViewManager.onQueryTextChange("q");
271         assertFalse(mListenerOnSearchChangedCalled);
272     }
273 
274     @Test
testOnSearchChanged_CalledAfterOnQueryTextChangedAndWait()275     public void testOnSearchChanged_CalledAfterOnQueryTextChangedAndWait() throws Exception {
276         mSearchViewManager.onClick(null);
277         mSearchViewManager.onQueryTextChange("q");
278         fastForwardTo(SearchViewManager.SEARCH_DELAY_MS);
279         assertTrue(mListenerOnSearchChangedCalled);
280     }
281 
282     @Test
testOnSearchChanged_CalledOnlyOnceAfterOnQueryTextSubmit()283     public void testOnSearchChanged_CalledOnlyOnceAfterOnQueryTextSubmit() throws Exception {
284         mSearchViewManager.onClick(null);
285         mSearchViewManager.onQueryTextChange("q");
286         mSearchViewManager.onQueryTextSubmit("q");
287 
288         // Clear the flag to check if it gets set again.
289         mListenerOnSearchChangedCalled = false;
290         fastForwardTo(SearchViewManager.SEARCH_DELAY_MS);
291         assertFalse(mListenerOnSearchChangedCalled);
292     }
293 
294     @Test
testOnSearchChanged_NotCalledForOnQueryTextSubmitIfSearchAlreadyFinished()295     public void testOnSearchChanged_NotCalledForOnQueryTextSubmitIfSearchAlreadyFinished()
296             throws Exception {
297         mSearchViewManager.onClick(null);
298         mSearchViewManager.onQueryTextChange("q");
299         fastForwardTo(SearchViewManager.SEARCH_DELAY_MS);
300         // Clear the flag to check if it gets set again.
301         mListenerOnSearchChangedCalled = false;
302         mSearchViewManager.onQueryTextSubmit("q");
303         assertFalse(mListenerOnSearchChangedCalled);
304     }
305 
306     @Test
testHistoryRecorded_recordOnQueryTextSubmit()307     public void testHistoryRecorded_recordOnQueryTextSubmit() {
308         mSearchViewManager.onClick(null);
309         mSearchViewManager.onQueryTextSubmit("q");
310 
311         assertEquals(mSearchViewManager.getCurrentSearch(),
312                 mSearchViewManager.getRecordedHistory());
313     }
314 
315     @Test
testCheckedChipItems_IsEmptyIfSearchCanceled()316     public void testCheckedChipItems_IsEmptyIfSearchCanceled() throws Exception {
317         mSearchViewManager.onClick(null);
318         mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList();
319         mSearchViewManager.cancelSearch();
320         fastForwardTo(SearchViewManager.SEARCH_DELAY_MS);
321         assertTrue(!mSearchChipViewManager.hasCheckedItems());
322     }
323 
324     @Test
testBuildQueryArgs_hasSearchString()325     public void testBuildQueryArgs_hasSearchString() throws Exception {
326         final String query = "q";
327         mSearchViewManager.onClick(null);
328         mSearchViewManager.onQueryTextChange("q");
329         fastForwardTo(SearchViewManager.SEARCH_DELAY_MS);
330 
331         final Bundle queryArgs = mSearchViewManager.buildQueryArgs();
332         assertFalse(queryArgs.isEmpty());
333 
334         final String queryString = queryArgs.getString(DocumentsContract.QUERY_ARG_DISPLAY_NAME);
335         assertEquals(query, queryString);
336     }
337 
338     @Test
testBuildQueryArgs_hasMimeType()339     public void testBuildQueryArgs_hasMimeType() throws Exception {
340         mSearchViewManager.onClick(null);
341         mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList();
342 
343         final Bundle queryArgs = mSearchViewManager.buildQueryArgs();
344         assertFalse(queryArgs.isEmpty());
345 
346         final String[] mimeTypes = queryArgs.getStringArray(QUERY_ARG_MIME_TYPES);
347         assertTrue(mimeTypes.length > 0);
348         assertEquals("image/*", mimeTypes[0]);
349     }
350 
351     @Test
testSupportsMimeTypesSearch_showChips()352     public void testSupportsMimeTypesSearch_showChips() throws Exception {
353         RootInfo root = spy(new RootInfo());
354         when(root.isRecents()).thenReturn(false);
355         root.flags = FLAG_SUPPORTS_SEARCH;
356         root.queryArgs = QUERY_ARG_MIME_TYPES;
357         DocumentStack stack = new DocumentStack(root, new DocumentInfo());
358 
359         mSearchViewManager.showMenu(stack);
360 
361         verify(mSearchChipViewManager, times(1)).setChipsRowVisible(true);
362     }
363 
364     @Test
testNotSupportsMimeTypesSearch_notShowChips()365     public void testNotSupportsMimeTypesSearch_notShowChips() throws Exception {
366         RootInfo root = spy(new RootInfo());
367         when(root.isRecents()).thenReturn(false);
368         root.flags = FLAG_SUPPORTS_SEARCH;
369         root.queryArgs = TextUtils.join("\n",
370                 new String[]{QUERY_ARG_DISPLAY_NAME, QUERY_ARG_FILE_SIZE_OVER,
371                         QUERY_ARG_LAST_MODIFIED_AFTER});
372         DocumentStack stack = new DocumentStack(root, new DocumentInfo());
373 
374         mSearchViewManager.showMenu(stack);
375 
376         verify(mSearchChipViewManager, times(1)).setChipsRowVisible(false);
377     }
378 
379     @Test
testSupportsSearch_showMenu()380     public void testSupportsSearch_showMenu() throws Exception {
381         RootInfo root = spy(new RootInfo());
382         when(root.isRecents()).thenReturn(false);
383         root.flags = FLAG_SUPPORTS_SEARCH;
384         DocumentStack stack = new DocumentStack(root, new DocumentInfo());
385 
386         mSearchViewManager.showMenu(stack);
387 
388         assertTrue(mSearchMenuItem.isVisible());
389     }
390 
391     @Test
testNotSupportsSearch_notShowMenuAndChips()392     public void testNotSupportsSearch_notShowMenuAndChips() throws Exception {
393         RootInfo root = spy(new RootInfo());
394         when(root.isRecents()).thenReturn(false);
395         root.queryArgs = QUERY_ARG_MIME_TYPES;
396         DocumentStack stack = new DocumentStack(root, new DocumentInfo());
397 
398         mSearchViewManager.install(mTestMenu, true, false);
399         mSearchViewManager.showMenu(stack);
400 
401         assertFalse(mSearchMenuItem.isVisible());
402         verify(mSearchChipViewManager, times(1)).setChipsRowVisible(false);
403     }
404 
getFakeSearchChipDataList()405     private static Set<SearchChipData> getFakeSearchChipDataList() {
406         final Set<SearchChipData> chipDataList = new HashSet<>();
407         chipDataList.add(new SearchChipData(0 /* chipType */, 0, new String[]{"image/*"}));
408         return chipDataList;
409     }
410 }
411