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