/* * Copyright (C) 2016 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.documentsui.queries; import static android.provider.DocumentsContract.QUERY_ARG_DISPLAY_NAME; import static android.provider.DocumentsContract.QUERY_ARG_FILE_SIZE_OVER; import static android.provider.DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER; import static android.provider.DocumentsContract.QUERY_ARG_MIME_TYPES; import static android.provider.DocumentsContract.Root.FLAG_SUPPORTS_SEARCH; import static com.android.documentsui.base.State.ACTION_GET_CONTENT; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.provider.DocumentsContract; import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.documentsui.MetricConsts; import com.android.documentsui.R; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.DocumentStack; import com.android.documentsui.base.EventHandler; import com.android.documentsui.base.RootInfo; import com.android.documentsui.queries.SearchViewManager.SearchManagerListener; import com.android.documentsui.testing.TestEventHandler; import com.android.documentsui.testing.TestHandler; import com.android.documentsui.testing.TestMenu; import com.android.documentsui.testing.TestMenuItem; import com.android.documentsui.testing.TestTimer; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.time.LocalDate; import java.time.ZoneId; import java.util.HashSet; import java.util.Set; import java.util.Timer; import java.util.TimerTask; @RunWith(AndroidJUnit4.class) @SmallTest public final class SearchViewManagerTest { private TestEventHandler mTestEventHandler; private TestTimer mTestTimer; private TestHandler mTestHandler; private TestMenu mTestMenu; private TestMenuItem mSearchMenuItem; private TestableSearchViewManager mSearchViewManager; private SearchChipViewManager mSearchChipViewManager; private boolean mListenerOnSearchChangedCalled; @Before public void setUp() { mTestEventHandler = new TestEventHandler<>(); mTestTimer = new TestTimer(); mTestHandler = new TestHandler(); final SearchManagerListener searchListener = new SearchManagerListener() { @Override public void onSearchChanged(@Nullable String query) { mListenerOnSearchChangedCalled = true; } @Override public void onSearchFinished() { } @Override public void onSearchViewChanged(boolean opened) { } @Override public void onSearchChipStateChanged(View v) { } @Override public void onSearchViewFocusChanged(boolean hasFocus) { } @Override public void onSearchViewClearClicked() { } }; ViewGroup chipGroup = mock(ViewGroup.class); mSearchChipViewManager = spy(new SearchChipViewManager(chipGroup)); mSearchViewManager = new TestableSearchViewManager(searchListener, mTestEventHandler, mSearchChipViewManager, null /* savedState */, mTestTimer, mTestHandler); mTestMenu = TestMenu.create(); mSearchMenuItem = mTestMenu.findItem(R.id.option_menu_search); mSearchViewManager.install(mTestMenu, true, false); } private static class TestableSearchViewManager extends SearchViewManager { private String mHistoryRecorded; private boolean mIsHistoryRecorded; public TestableSearchViewManager( SearchManagerListener listener, EventHandler commandProcessor, SearchChipViewManager chipViewManager, @Nullable Bundle savedState, Timer timer, Handler handler) { super(listener, commandProcessor, chipViewManager, savedState, timer, handler); } @Override public TimerTask createSearchTask(String newText) { TimerTask task = super.createSearchTask(newText); TestTimer.Task testTask = new TestTimer.Task(task); return testTask; } @Override protected void recordHistoryInternal() { mHistoryRecorded = getCurrentSearch(); mIsHistoryRecorded = true; } public String getRecordedHistory() { return mHistoryRecorded; } public boolean isHistoryRecorded() { return mIsHistoryRecorded; } } private void fastForwardTo(long timeMs) { mTestTimer.fastForwardTo(timeMs); mTestHandler.dispatchAllMessages(); } @Test public void testParseQueryContent_ActionIsNotMatched_NotParseQueryContent() { final String queryString = "query"; Intent intent = new Intent(); intent.putExtra(Intent.EXTRA_CONTENT_QUERY, queryString); mSearchViewManager.parseQueryContentFromIntent(intent, -1); assertTrue(mSearchViewManager.getQueryContentFromIntent() == null); } @Test public void testParseQueryContent_queryContentIsMatched() { final String queryString = "query"; Intent intent = new Intent(); intent.putExtra(Intent.EXTRA_CONTENT_QUERY, queryString); mSearchViewManager.parseQueryContentFromIntent(intent, ACTION_GET_CONTENT); assertEquals(queryString, mSearchViewManager.getQueryContentFromIntent()); } @Test public void testIsExpanded_ExpandsOnClick() { mSearchViewManager.onClick(null); assertTrue(mSearchViewManager.isExpanded()); } @Test public void testIsExpanded_CollapsesOnMenuItemActionCollapse() { mSearchViewManager.onClick(null); mSearchViewManager.onMenuItemActionCollapse(null); assertFalse(mSearchViewManager.isExpanded()); } @Test public void testIsSearching_TrueHasCheckedChip() throws Exception { mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList(); assertTrue(mSearchViewManager.isSearching()); } @Test public void testIsSearching_FalseOnClick() throws Exception { mSearchViewManager.onClick(null); assertFalse(mSearchViewManager.isSearching()); } @Test public void testIsSearching_TrueOnQueryTextSubmit() throws Exception { mSearchViewManager.onClick(null); mSearchViewManager.onQueryTextSubmit("query"); assertTrue(mSearchViewManager.isSearching()); } @Test public void testIsSearching_FalseImmediatelyAfterOnQueryTextChange() throws Exception { mSearchViewManager.onClick(null); mSearchViewManager.onQueryTextChange("q"); assertFalse(mSearchViewManager.isSearching()); } @Test public void testIsSearching_TrueAfterOnQueryTextChangeAndWait() throws Exception { mSearchViewManager.onClick(null); mSearchViewManager.onQueryTextChange("q"); fastForwardTo(SearchViewManager.SEARCH_DELAY_MS); assertTrue(mSearchViewManager.isSearching()); } @Test public void testIsSearching_FalseWhenSecondOnQueryTextChangeResetsTimer() throws Exception { mSearchViewManager.onClick(null); mSearchViewManager.onQueryTextChange("q"); fastForwardTo(SearchViewManager.SEARCH_DELAY_MS - 1); mSearchViewManager.onQueryTextChange("qu"); fastForwardTo(SearchViewManager.SEARCH_DELAY_MS); assertFalse(mSearchViewManager.isSearching()); } @Test public void testIsSearching_TrueAfterSecondOnQueryTextChangeResetsTimer() throws Exception { mSearchViewManager.onClick(null); mSearchViewManager.onQueryTextChange("q"); fastForwardTo(SearchViewManager.SEARCH_DELAY_MS - 1); mSearchViewManager.onQueryTextChange("qu"); fastForwardTo(SearchViewManager.SEARCH_DELAY_MS * 2); assertTrue(mSearchViewManager.isSearching()); } @Test public void testIsSearching_FalseIfSearchCanceled() throws Exception { mSearchViewManager.onClick(null); mSearchViewManager.onQueryTextChange("q"); mSearchViewManager.cancelSearch(); fastForwardTo(SearchViewManager.SEARCH_DELAY_MS); assertFalse(mSearchViewManager.isSearching()); } @Test public void testOnSearchChanged_CalledAfterOnQueryTextSubmit() throws Exception { mSearchViewManager.onClick(null); mSearchViewManager.onQueryTextSubmit("q"); assertTrue(mListenerOnSearchChangedCalled); } @Test public void testOnSearchChanged_NotCalledImmediatelyAfterOnQueryTextChanged() throws Exception { mSearchViewManager.onClick(null); mSearchViewManager.onQueryTextChange("q"); assertFalse(mListenerOnSearchChangedCalled); } @Test public void testOnSearchChanged_CalledAfterOnQueryTextChangedAndWait() throws Exception { mSearchViewManager.onClick(null); mSearchViewManager.onQueryTextChange("q"); fastForwardTo(SearchViewManager.SEARCH_DELAY_MS); assertTrue(mListenerOnSearchChangedCalled); } @Test public void testOnSearchChanged_CalledOnlyOnceAfterOnQueryTextSubmit() throws Exception { mSearchViewManager.onClick(null); mSearchViewManager.onQueryTextChange("q"); mSearchViewManager.onQueryTextSubmit("q"); // Clear the flag to check if it gets set again. mListenerOnSearchChangedCalled = false; fastForwardTo(SearchViewManager.SEARCH_DELAY_MS); assertFalse(mListenerOnSearchChangedCalled); } @Test public void testOnSearchChanged_NotCalledForOnQueryTextSubmitIfSearchAlreadyFinished() throws Exception { mSearchViewManager.onClick(null); mSearchViewManager.onQueryTextChange("q"); fastForwardTo(SearchViewManager.SEARCH_DELAY_MS); // Clear the flag to check if it gets set again. mListenerOnSearchChangedCalled = false; mSearchViewManager.onQueryTextSubmit("q"); assertFalse(mListenerOnSearchChangedCalled); } @Test public void testHistoryRecorded_recordOnQueryTextSubmit() { mSearchViewManager.onClick(null); mSearchViewManager.onQueryTextSubmit("q"); assertEquals(mSearchViewManager.getCurrentSearch(), mSearchViewManager.getRecordedHistory()); } @Test public void testHistoryRecorded_skipWhenNoSearchString() { mSearchViewManager.recordHistory(); assertFalse(mSearchViewManager.isHistoryRecorded()); } @Test public void testCheckedChipItems_IsEmptyIfSearchCanceled() throws Exception { mSearchViewManager.onClick(null); mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList(); mSearchViewManager.cancelSearch(); fastForwardTo(SearchViewManager.SEARCH_DELAY_MS); assertTrue(!mSearchChipViewManager.hasCheckedItems()); } @Test public void testBuildQueryArgs_hasSearchString() throws Exception { final String query = "q"; mSearchViewManager.onClick(null); mSearchViewManager.onQueryTextChange("q"); fastForwardTo(SearchViewManager.SEARCH_DELAY_MS); final Bundle queryArgs = mSearchViewManager.buildQueryArgs(); assertFalse(queryArgs.isEmpty()); final String queryString = queryArgs.getString(DocumentsContract.QUERY_ARG_DISPLAY_NAME); assertEquals(query, queryString); } @Test public void testBuildQueryArgs_emptySearchString_expandedSearchWithChips_hasEmptyButNotMissingSearchString() throws Exception { mSearchViewManager.onClick(null); mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList(); fastForwardTo(SearchViewManager.SEARCH_DELAY_MS); final String queryString = mSearchViewManager.buildQueryArgs() .getString(DocumentsContract.QUERY_ARG_DISPLAY_NAME); assertEquals("", queryString); } @Test public void testBuildQueryArgs_emptySearchString_withChipsWithoutExpandedSearch_hasNoSearchString() throws Exception { mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList(); fastForwardTo(SearchViewManager.SEARCH_DELAY_MS); assertFalse(mSearchViewManager.buildQueryArgs().containsKey(QUERY_ARG_DISPLAY_NAME)); } @Test public void testBuildQueryArgs_emptySearchString_expandedSearchWithNoChips_hasNoSearchString() throws Exception { mSearchViewManager.onClick(null); fastForwardTo(SearchViewManager.SEARCH_DELAY_MS); assertFalse(mSearchViewManager.buildQueryArgs().containsKey(QUERY_ARG_DISPLAY_NAME)); } @Test public void testBuildQueryArgs_hasMimeType() throws Exception { mSearchViewManager.onClick(null); mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList(); final Bundle queryArgs = mSearchViewManager.buildQueryArgs(); assertFalse(queryArgs.isEmpty()); final String[] mimeTypes = queryArgs.getStringArray(QUERY_ARG_MIME_TYPES); assertTrue(mimeTypes.length > 0); assertEquals("image/*", mimeTypes[0]); } @Test public void testBuildQueryArgs_hasLargeFilesSize() throws Exception { mSearchViewManager.onClick(null); mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList(); final Bundle queryArgs = mSearchViewManager.buildQueryArgs(); assertFalse(queryArgs.isEmpty()); final long largeFilesSize = queryArgs.getLong(QUERY_ARG_FILE_SIZE_OVER); assertEquals(10000000L, largeFilesSize); } @Test public void testBuildQueryArgs_hasWeekAgoTime() throws Exception { mSearchViewManager.onClick(null); mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList(); final long startTime = LocalDate.now().minusDays(7).atStartOfDay(ZoneId.systemDefault()) .toInstant().toEpochMilli(); final Bundle queryArgs = mSearchViewManager.buildQueryArgs(); assertFalse(queryArgs.isEmpty()); final long endTime = LocalDate.now().minusDays(7).atStartOfDay(ZoneId.systemDefault()) .toInstant().toEpochMilli(); final long weekAgoTime = queryArgs.getLong(QUERY_ARG_LAST_MODIFIED_AFTER); assertTrue(weekAgoTime == endTime || weekAgoTime == startTime); } @Test public void testSupportsMimeTypesSearch_showChips() throws Exception { RootInfo root = spy(new RootInfo()); when(root.isRecents()).thenReturn(false); root.flags = FLAG_SUPPORTS_SEARCH; root.queryArgs = QUERY_ARG_MIME_TYPES; DocumentStack stack = new DocumentStack(root, new DocumentInfo()); mSearchViewManager.showMenu(stack); verify(mSearchChipViewManager, times(1)).setChipsRowVisible(true); } @Test public void testNotSupportsMimeTypesSearch_notShowChips() throws Exception { RootInfo root = spy(new RootInfo()); when(root.isRecents()).thenReturn(false); root.flags = FLAG_SUPPORTS_SEARCH; root.queryArgs = TextUtils.join("\n", new String[]{QUERY_ARG_DISPLAY_NAME, QUERY_ARG_FILE_SIZE_OVER, QUERY_ARG_LAST_MODIFIED_AFTER}); DocumentStack stack = new DocumentStack(root, new DocumentInfo()); mSearchViewManager.showMenu(stack); verify(mSearchChipViewManager, times(1)).setChipsRowVisible(false); } @Test public void testSupportsSearch_showMenu() throws Exception { RootInfo root = spy(new RootInfo()); when(root.isRecents()).thenReturn(false); root.flags = FLAG_SUPPORTS_SEARCH; DocumentStack stack = new DocumentStack(root, new DocumentInfo()); mSearchViewManager.showMenu(stack); assertTrue(mSearchMenuItem.isVisible()); } @Test public void testNotSupportsSearch_notShowMenuAndChips() throws Exception { RootInfo root = spy(new RootInfo()); when(root.isRecents()).thenReturn(false); root.queryArgs = QUERY_ARG_MIME_TYPES; DocumentStack stack = new DocumentStack(root, new DocumentInfo()); mSearchViewManager.install(mTestMenu, true, false); mSearchViewManager.showMenu(stack); assertFalse(mSearchMenuItem.isVisible()); verify(mSearchChipViewManager, times(1)).setChipsRowVisible(false); } private static Set getFakeSearchChipDataList() { final Set chipDataList = new HashSet<>(); chipDataList.add(new SearchChipData(MetricConsts.TYPE_CHIP_IMAGES, 0 /* titleRes */, new String[]{"image/*"})); chipDataList.add(new SearchChipData(MetricConsts.TYPE_CHIP_LARGE_FILES, 0 /* titleRes */, new String[]{""})); chipDataList.add(new SearchChipData(MetricConsts.TYPE_CHIP_FROM_THIS_WEEK, 0 /* titleRes */, new String[]{""})); return chipDataList; } }