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.MetricConsts; 50 import com.android.documentsui.R; 51 import com.android.documentsui.base.DocumentInfo; 52 import com.android.documentsui.base.DocumentStack; 53 import com.android.documentsui.base.EventHandler; 54 import com.android.documentsui.base.RootInfo; 55 import com.android.documentsui.queries.SearchViewManager.SearchManagerListener; 56 import com.android.documentsui.testing.TestEventHandler; 57 import com.android.documentsui.testing.TestHandler; 58 import com.android.documentsui.testing.TestMenu; 59 import com.android.documentsui.testing.TestMenuItem; 60 import com.android.documentsui.testing.TestTimer; 61 62 import org.junit.Before; 63 import org.junit.Test; 64 import org.junit.runner.RunWith; 65 66 import java.time.LocalDate; 67 import java.time.ZoneId; 68 import java.util.HashSet; 69 import java.util.Set; 70 import java.util.Timer; 71 import java.util.TimerTask; 72 73 @RunWith(AndroidJUnit4.class) 74 @SmallTest 75 public final class SearchViewManagerTest { 76 77 private TestEventHandler<String> mTestEventHandler; 78 private TestTimer mTestTimer; 79 private TestHandler mTestHandler; 80 private TestMenu mTestMenu; 81 private TestMenuItem mSearchMenuItem; 82 private TestableSearchViewManager mSearchViewManager; 83 private SearchChipViewManager mSearchChipViewManager; 84 85 private boolean mListenerOnSearchChangedCalled; 86 87 @Before setUp()88 public void setUp() { 89 mTestEventHandler = new TestEventHandler<>(); 90 mTestTimer = new TestTimer(); 91 mTestHandler = new TestHandler(); 92 93 final SearchManagerListener searchListener = new SearchManagerListener() { 94 @Override 95 public void onSearchChanged(@Nullable String query) { 96 mListenerOnSearchChangedCalled = true; 97 } 98 99 @Override 100 public void onSearchFinished() { 101 } 102 103 @Override 104 public void onSearchViewChanged(boolean opened) { 105 } 106 107 @Override 108 public void onSearchChipStateChanged(View v) { 109 } 110 111 @Override 112 public void onSearchViewFocusChanged(boolean hasFocus) { 113 } 114 115 @Override 116 public void onSearchViewClearClicked() { 117 } 118 }; 119 120 ViewGroup chipGroup = mock(ViewGroup.class); 121 mSearchChipViewManager = spy(new SearchChipViewManager(chipGroup)); 122 mSearchViewManager = new TestableSearchViewManager(searchListener, mTestEventHandler, 123 mSearchChipViewManager, null /* savedState */, mTestTimer, mTestHandler); 124 125 mTestMenu = TestMenu.create(); 126 mSearchMenuItem = mTestMenu.findItem(R.id.option_menu_search); 127 mSearchViewManager.install(mTestMenu, true, false); 128 } 129 130 private static class TestableSearchViewManager extends SearchViewManager { 131 132 private String mHistoryRecorded; 133 private boolean mIsHistoryRecorded; 134 TestableSearchViewManager( SearchManagerListener listener, EventHandler<String> commandProcessor, SearchChipViewManager chipViewManager, @Nullable Bundle savedState, Timer timer, Handler handler)135 public TestableSearchViewManager( 136 SearchManagerListener listener, 137 EventHandler<String> commandProcessor, 138 SearchChipViewManager chipViewManager, 139 @Nullable Bundle savedState, 140 Timer timer, 141 Handler handler) { 142 super(listener, commandProcessor, chipViewManager, savedState, timer, handler); 143 } 144 145 @Override createSearchTask(String newText)146 public TimerTask createSearchTask(String newText) { 147 TimerTask task = super.createSearchTask(newText); 148 TestTimer.Task testTask = new TestTimer.Task(task); 149 return testTask; 150 } 151 152 @Override recordHistoryInternal()153 protected void recordHistoryInternal() { 154 mHistoryRecorded = getCurrentSearch(); 155 mIsHistoryRecorded = true; 156 } 157 getRecordedHistory()158 public String getRecordedHistory() { 159 return mHistoryRecorded; 160 } 161 isHistoryRecorded()162 public boolean isHistoryRecorded() { 163 return mIsHistoryRecorded; 164 } 165 } 166 fastForwardTo(long timeMs)167 private void fastForwardTo(long timeMs) { 168 mTestTimer.fastForwardTo(timeMs); 169 mTestHandler.dispatchAllMessages(); 170 } 171 172 173 @Test testParseQueryContent_ActionIsNotMatched_NotParseQueryContent()174 public void testParseQueryContent_ActionIsNotMatched_NotParseQueryContent() { 175 final String queryString = "query"; 176 Intent intent = new Intent(); 177 intent.putExtra(Intent.EXTRA_CONTENT_QUERY, queryString); 178 179 mSearchViewManager.parseQueryContentFromIntent(intent, -1); 180 assertTrue(mSearchViewManager.getQueryContentFromIntent() == null); 181 } 182 183 @Test testParseQueryContent_queryContentIsMatched()184 public void testParseQueryContent_queryContentIsMatched() { 185 final String queryString = "query"; 186 Intent intent = new Intent(); 187 intent.putExtra(Intent.EXTRA_CONTENT_QUERY, queryString); 188 189 mSearchViewManager.parseQueryContentFromIntent(intent, ACTION_GET_CONTENT); 190 assertEquals(queryString, mSearchViewManager.getQueryContentFromIntent()); 191 } 192 193 @Test testIsExpanded_ExpandsOnClick()194 public void testIsExpanded_ExpandsOnClick() { 195 mSearchViewManager.onClick(null); 196 assertTrue(mSearchViewManager.isExpanded()); 197 } 198 199 @Test testIsExpanded_CollapsesOnMenuItemActionCollapse()200 public void testIsExpanded_CollapsesOnMenuItemActionCollapse() { 201 mSearchViewManager.onClick(null); 202 mSearchViewManager.onMenuItemActionCollapse(null); 203 assertFalse(mSearchViewManager.isExpanded()); 204 } 205 206 @Test testIsSearching_TrueHasCheckedChip()207 public void testIsSearching_TrueHasCheckedChip() throws Exception { 208 mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList(); 209 assertTrue(mSearchViewManager.isSearching()); 210 } 211 212 @Test testIsSearching_FalseOnClick()213 public void testIsSearching_FalseOnClick() throws Exception { 214 mSearchViewManager.onClick(null); 215 assertFalse(mSearchViewManager.isSearching()); 216 } 217 218 @Test testIsSearching_TrueOnQueryTextSubmit()219 public void testIsSearching_TrueOnQueryTextSubmit() throws Exception { 220 mSearchViewManager.onClick(null); 221 mSearchViewManager.onQueryTextSubmit("query"); 222 assertTrue(mSearchViewManager.isSearching()); 223 } 224 225 @Test testIsSearching_FalseImmediatelyAfterOnQueryTextChange()226 public void testIsSearching_FalseImmediatelyAfterOnQueryTextChange() throws Exception { 227 mSearchViewManager.onClick(null); 228 mSearchViewManager.onQueryTextChange("q"); 229 assertFalse(mSearchViewManager.isSearching()); 230 } 231 232 @Test testIsSearching_TrueAfterOnQueryTextChangeAndWait()233 public void testIsSearching_TrueAfterOnQueryTextChangeAndWait() throws Exception { 234 mSearchViewManager.onClick(null); 235 mSearchViewManager.onQueryTextChange("q"); 236 fastForwardTo(SearchViewManager.SEARCH_DELAY_MS); 237 assertTrue(mSearchViewManager.isSearching()); 238 } 239 240 @Test testIsSearching_FalseWhenSecondOnQueryTextChangeResetsTimer()241 public void testIsSearching_FalseWhenSecondOnQueryTextChangeResetsTimer() throws Exception { 242 mSearchViewManager.onClick(null); 243 mSearchViewManager.onQueryTextChange("q"); 244 fastForwardTo(SearchViewManager.SEARCH_DELAY_MS - 1); 245 mSearchViewManager.onQueryTextChange("qu"); 246 fastForwardTo(SearchViewManager.SEARCH_DELAY_MS); 247 assertFalse(mSearchViewManager.isSearching()); 248 } 249 250 @Test testIsSearching_TrueAfterSecondOnQueryTextChangeResetsTimer()251 public void testIsSearching_TrueAfterSecondOnQueryTextChangeResetsTimer() throws Exception { 252 mSearchViewManager.onClick(null); 253 mSearchViewManager.onQueryTextChange("q"); 254 fastForwardTo(SearchViewManager.SEARCH_DELAY_MS - 1); 255 mSearchViewManager.onQueryTextChange("qu"); 256 fastForwardTo(SearchViewManager.SEARCH_DELAY_MS * 2); 257 assertTrue(mSearchViewManager.isSearching()); 258 } 259 260 @Test testIsSearching_FalseIfSearchCanceled()261 public void testIsSearching_FalseIfSearchCanceled() throws Exception { 262 mSearchViewManager.onClick(null); 263 mSearchViewManager.onQueryTextChange("q"); 264 mSearchViewManager.cancelSearch(); 265 fastForwardTo(SearchViewManager.SEARCH_DELAY_MS); 266 assertFalse(mSearchViewManager.isSearching()); 267 } 268 269 @Test testOnSearchChanged_CalledAfterOnQueryTextSubmit()270 public void testOnSearchChanged_CalledAfterOnQueryTextSubmit() throws Exception { 271 mSearchViewManager.onClick(null); 272 mSearchViewManager.onQueryTextSubmit("q"); 273 assertTrue(mListenerOnSearchChangedCalled); 274 } 275 276 @Test testOnSearchChanged_NotCalledImmediatelyAfterOnQueryTextChanged()277 public void testOnSearchChanged_NotCalledImmediatelyAfterOnQueryTextChanged() throws Exception { 278 mSearchViewManager.onClick(null); 279 mSearchViewManager.onQueryTextChange("q"); 280 assertFalse(mListenerOnSearchChangedCalled); 281 } 282 283 @Test testOnSearchChanged_CalledAfterOnQueryTextChangedAndWait()284 public void testOnSearchChanged_CalledAfterOnQueryTextChangedAndWait() throws Exception { 285 mSearchViewManager.onClick(null); 286 mSearchViewManager.onQueryTextChange("q"); 287 fastForwardTo(SearchViewManager.SEARCH_DELAY_MS); 288 assertTrue(mListenerOnSearchChangedCalled); 289 } 290 291 @Test testOnSearchChanged_CalledOnlyOnceAfterOnQueryTextSubmit()292 public void testOnSearchChanged_CalledOnlyOnceAfterOnQueryTextSubmit() throws Exception { 293 mSearchViewManager.onClick(null); 294 mSearchViewManager.onQueryTextChange("q"); 295 mSearchViewManager.onQueryTextSubmit("q"); 296 297 // Clear the flag to check if it gets set again. 298 mListenerOnSearchChangedCalled = false; 299 fastForwardTo(SearchViewManager.SEARCH_DELAY_MS); 300 assertFalse(mListenerOnSearchChangedCalled); 301 } 302 303 @Test testOnSearchChanged_NotCalledForOnQueryTextSubmitIfSearchAlreadyFinished()304 public void testOnSearchChanged_NotCalledForOnQueryTextSubmitIfSearchAlreadyFinished() 305 throws Exception { 306 mSearchViewManager.onClick(null); 307 mSearchViewManager.onQueryTextChange("q"); 308 fastForwardTo(SearchViewManager.SEARCH_DELAY_MS); 309 // Clear the flag to check if it gets set again. 310 mListenerOnSearchChangedCalled = false; 311 mSearchViewManager.onQueryTextSubmit("q"); 312 assertFalse(mListenerOnSearchChangedCalled); 313 } 314 315 @Test testHistoryRecorded_recordOnQueryTextSubmit()316 public void testHistoryRecorded_recordOnQueryTextSubmit() { 317 mSearchViewManager.onClick(null); 318 mSearchViewManager.onQueryTextSubmit("q"); 319 320 assertEquals(mSearchViewManager.getCurrentSearch(), 321 mSearchViewManager.getRecordedHistory()); 322 } 323 324 @Test testHistoryRecorded_skipWhenNoSearchString()325 public void testHistoryRecorded_skipWhenNoSearchString() { 326 mSearchViewManager.recordHistory(); 327 328 assertFalse(mSearchViewManager.isHistoryRecorded()); 329 } 330 331 @Test testCheckedChipItems_IsEmptyIfSearchCanceled()332 public void testCheckedChipItems_IsEmptyIfSearchCanceled() throws Exception { 333 mSearchViewManager.onClick(null); 334 mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList(); 335 mSearchViewManager.cancelSearch(); 336 fastForwardTo(SearchViewManager.SEARCH_DELAY_MS); 337 assertTrue(!mSearchChipViewManager.hasCheckedItems()); 338 } 339 340 @Test testBuildQueryArgs_hasSearchString()341 public void testBuildQueryArgs_hasSearchString() throws Exception { 342 final String query = "q"; 343 mSearchViewManager.onClick(null); 344 mSearchViewManager.onQueryTextChange("q"); 345 fastForwardTo(SearchViewManager.SEARCH_DELAY_MS); 346 347 final Bundle queryArgs = mSearchViewManager.buildQueryArgs(); 348 assertFalse(queryArgs.isEmpty()); 349 350 final String queryString = queryArgs.getString(DocumentsContract.QUERY_ARG_DISPLAY_NAME); 351 assertEquals(query, queryString); 352 } 353 354 @Test testBuildQueryArgs_emptySearchString_expandedSearchWithChips_hasEmptyButNotMissingSearchString()355 public void testBuildQueryArgs_emptySearchString_expandedSearchWithChips_hasEmptyButNotMissingSearchString() 356 throws Exception { 357 mSearchViewManager.onClick(null); 358 mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList(); 359 fastForwardTo(SearchViewManager.SEARCH_DELAY_MS); 360 361 final String queryString = 362 mSearchViewManager.buildQueryArgs() 363 .getString(DocumentsContract.QUERY_ARG_DISPLAY_NAME); 364 assertEquals("", queryString); 365 } 366 367 @Test testBuildQueryArgs_emptySearchString_withChipsWithoutExpandedSearch_hasNoSearchString()368 public void testBuildQueryArgs_emptySearchString_withChipsWithoutExpandedSearch_hasNoSearchString() 369 throws Exception { 370 mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList(); 371 fastForwardTo(SearchViewManager.SEARCH_DELAY_MS); 372 373 assertFalse(mSearchViewManager.buildQueryArgs().containsKey(QUERY_ARG_DISPLAY_NAME)); 374 } 375 376 @Test testBuildQueryArgs_emptySearchString_expandedSearchWithNoChips_hasNoSearchString()377 public void testBuildQueryArgs_emptySearchString_expandedSearchWithNoChips_hasNoSearchString() 378 throws Exception { 379 mSearchViewManager.onClick(null); 380 fastForwardTo(SearchViewManager.SEARCH_DELAY_MS); 381 382 assertFalse(mSearchViewManager.buildQueryArgs().containsKey(QUERY_ARG_DISPLAY_NAME)); 383 } 384 385 @Test testBuildQueryArgs_hasMimeType()386 public void testBuildQueryArgs_hasMimeType() throws Exception { 387 mSearchViewManager.onClick(null); 388 mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList(); 389 390 final Bundle queryArgs = mSearchViewManager.buildQueryArgs(); 391 assertFalse(queryArgs.isEmpty()); 392 393 final String[] mimeTypes = queryArgs.getStringArray(QUERY_ARG_MIME_TYPES); 394 assertTrue(mimeTypes.length > 0); 395 assertEquals("image/*", mimeTypes[0]); 396 } 397 398 @Test testBuildQueryArgs_hasLargeFilesSize()399 public void testBuildQueryArgs_hasLargeFilesSize() throws Exception { 400 mSearchViewManager.onClick(null); 401 mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList(); 402 403 final Bundle queryArgs = mSearchViewManager.buildQueryArgs(); 404 assertFalse(queryArgs.isEmpty()); 405 406 final long largeFilesSize = queryArgs.getLong(QUERY_ARG_FILE_SIZE_OVER); 407 assertEquals(10000000L, largeFilesSize); 408 } 409 410 @Test testBuildQueryArgs_hasWeekAgoTime()411 public void testBuildQueryArgs_hasWeekAgoTime() throws Exception { 412 mSearchViewManager.onClick(null); 413 mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList(); 414 415 final long startTime = LocalDate.now().minusDays(7).atStartOfDay(ZoneId.systemDefault()) 416 .toInstant().toEpochMilli(); 417 418 final Bundle queryArgs = mSearchViewManager.buildQueryArgs(); 419 assertFalse(queryArgs.isEmpty()); 420 421 final long endTime = LocalDate.now().minusDays(7).atStartOfDay(ZoneId.systemDefault()) 422 .toInstant().toEpochMilli(); 423 final long weekAgoTime = queryArgs.getLong(QUERY_ARG_LAST_MODIFIED_AFTER); 424 assertTrue(weekAgoTime == endTime || weekAgoTime == startTime); 425 } 426 427 @Test testSupportsMimeTypesSearch_showChips()428 public void testSupportsMimeTypesSearch_showChips() throws Exception { 429 RootInfo root = spy(new RootInfo()); 430 when(root.isRecents()).thenReturn(false); 431 root.flags = FLAG_SUPPORTS_SEARCH; 432 root.queryArgs = QUERY_ARG_MIME_TYPES; 433 DocumentStack stack = new DocumentStack(root, new DocumentInfo()); 434 435 mSearchViewManager.showMenu(stack); 436 437 verify(mSearchChipViewManager, times(1)).setChipsRowVisible(true); 438 } 439 440 @Test testNotSupportsMimeTypesSearch_notShowChips()441 public void testNotSupportsMimeTypesSearch_notShowChips() throws Exception { 442 RootInfo root = spy(new RootInfo()); 443 when(root.isRecents()).thenReturn(false); 444 root.flags = FLAG_SUPPORTS_SEARCH; 445 root.queryArgs = TextUtils.join("\n", 446 new String[]{QUERY_ARG_DISPLAY_NAME, QUERY_ARG_FILE_SIZE_OVER, 447 QUERY_ARG_LAST_MODIFIED_AFTER}); 448 DocumentStack stack = new DocumentStack(root, new DocumentInfo()); 449 450 mSearchViewManager.showMenu(stack); 451 452 verify(mSearchChipViewManager, times(1)).setChipsRowVisible(false); 453 } 454 455 @Test testSupportsSearch_showMenu()456 public void testSupportsSearch_showMenu() throws Exception { 457 RootInfo root = spy(new RootInfo()); 458 when(root.isRecents()).thenReturn(false); 459 root.flags = FLAG_SUPPORTS_SEARCH; 460 DocumentStack stack = new DocumentStack(root, new DocumentInfo()); 461 462 mSearchViewManager.showMenu(stack); 463 464 assertTrue(mSearchMenuItem.isVisible()); 465 } 466 467 @Test testNotSupportsSearch_notShowMenuAndChips()468 public void testNotSupportsSearch_notShowMenuAndChips() throws Exception { 469 RootInfo root = spy(new RootInfo()); 470 when(root.isRecents()).thenReturn(false); 471 root.queryArgs = QUERY_ARG_MIME_TYPES; 472 DocumentStack stack = new DocumentStack(root, new DocumentInfo()); 473 474 mSearchViewManager.install(mTestMenu, true, false); 475 mSearchViewManager.showMenu(stack); 476 477 assertFalse(mSearchMenuItem.isVisible()); 478 verify(mSearchChipViewManager, times(1)).setChipsRowVisible(false); 479 } 480 getFakeSearchChipDataList()481 private static Set<SearchChipData> getFakeSearchChipDataList() { 482 final Set<SearchChipData> chipDataList = new HashSet<>(); 483 chipDataList.add(new SearchChipData(MetricConsts.TYPE_CHIP_IMAGES, 484 0 /* titleRes */, new String[]{"image/*"})); 485 chipDataList.add(new SearchChipData(MetricConsts.TYPE_CHIP_LARGE_FILES, 486 0 /* titleRes */, new String[]{""})); 487 chipDataList.add(new SearchChipData(MetricConsts.TYPE_CHIP_FROM_THIS_WEEK, 488 0 /* titleRes */, new String[]{""})); 489 return chipDataList; 490 } 491 } 492