| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.chrome.browser.download; |
| |
| import static android.support.test.espresso.Espresso.onView; |
| import static android.support.test.espresso.action.ViewActions.click; |
| import static android.support.test.espresso.action.ViewActions.longClick; |
| import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist; |
| import static android.support.test.espresso.assertion.ViewAssertions.matches; |
| import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; |
| import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription; |
| import static android.support.test.espresso.matcher.ViewMatchers.withId; |
| import static android.support.test.espresso.matcher.ViewMatchers.withText; |
| |
| import static org.hamcrest.Matchers.not; |
| |
| import android.content.Intent; |
| import android.content.SharedPreferences.Editor; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.support.annotation.StringRes; |
| import android.support.test.InstrumentationRegistry; |
| import android.support.test.filters.MediumTest; |
| import android.support.test.rule.ActivityTestRule; |
| import android.support.v7.widget.RecyclerView; |
| import android.support.v7.widget.RecyclerView.ViewHolder; |
| import android.text.TextUtils; |
| import android.view.View; |
| import android.widget.Spinner; |
| import android.widget.TextView; |
| |
| import org.junit.Assert; |
| import org.junit.Before; |
| import org.junit.ClassRule; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import org.chromium.base.ContextUtils; |
| import org.chromium.base.ThreadUtils; |
| import org.chromium.base.test.util.CallbackHelper; |
| import org.chromium.base.test.util.DisabledTest; |
| import org.chromium.base.test.util.FlakyTest; |
| import org.chromium.base.test.util.Restriction; |
| import org.chromium.base.test.util.RetryOnFailure; |
| import org.chromium.chrome.R; |
| import org.chromium.chrome.browser.ChromeFeatureList; |
| import org.chromium.chrome.browser.download.ui.DownloadHistoryAdapter; |
| import org.chromium.chrome.browser.download.ui.DownloadHistoryItemViewHolder; |
| import org.chromium.chrome.browser.download.ui.DownloadHistoryItemWrapper; |
| import org.chromium.chrome.browser.download.ui.DownloadHistoryItemWrapper.OfflineItemWrapper; |
| import org.chromium.chrome.browser.download.ui.DownloadItemView; |
| import org.chromium.chrome.browser.download.ui.DownloadManagerToolbar; |
| import org.chromium.chrome.browser.download.ui.DownloadManagerUi; |
| import org.chromium.chrome.browser.download.ui.SpaceDisplay; |
| import org.chromium.chrome.browser.download.ui.StubbedProvider; |
| import org.chromium.chrome.browser.preferences.PrefServiceBridge; |
| import org.chromium.chrome.browser.snackbar.SnackbarManager; |
| import org.chromium.chrome.browser.util.IntentUtils; |
| import org.chromium.chrome.browser.widget.ListMenuButton.Item; |
| import org.chromium.chrome.browser.widget.selection.SelectionDelegate.SelectionObserver; |
| import org.chromium.chrome.test.ChromeJUnit4ClassRunner; |
| import org.chromium.chrome.test.ui.DisableAnimationsTestRule; |
| import org.chromium.chrome.test.util.browser.Features.DisableFeatures; |
| import org.chromium.chrome.test.util.browser.Features.EnableFeatures; |
| import org.chromium.components.offline_items_collection.OfflineItem; |
| import org.chromium.content_public.browser.test.util.Criteria; |
| import org.chromium.content_public.browser.test.util.CriteriaHelper; |
| import org.chromium.ui.test.util.UiRestriction; |
| |
| import java.util.HashMap; |
| import java.util.List; |
| |
| /** |
| * Tests the DownloadActivity and the DownloadManagerUi. |
| */ |
| @RunWith(ChromeJUnit4ClassRunner.class) |
| @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE) |
| public class DownloadActivityTest { |
| // Disable animations to reduce flakiness. |
| @ClassRule |
| public static DisableAnimationsTestRule disableAnimationsRule = new DisableAnimationsTestRule(); |
| |
| @Rule |
| public ActivityTestRule<DownloadActivity> mActivityTestRule = |
| new ActivityTestRule<>(DownloadActivity.class); |
| |
| private static class TestObserver extends RecyclerView.AdapterDataObserver |
| implements SelectionObserver<DownloadHistoryItemWrapper>, SpaceDisplay.Observer { |
| public final CallbackHelper onChangedCallback = new CallbackHelper(); |
| public final CallbackHelper onSelectionCallback = new CallbackHelper(); |
| public final CallbackHelper onSpaceDisplayUpdatedCallback = new CallbackHelper(); |
| |
| private List<DownloadHistoryItemWrapper> mOnSelectionItems; |
| private Handler mHandler; |
| |
| public TestObserver() { |
| mHandler = new Handler(Looper.getMainLooper()); |
| } |
| |
| @Override |
| public void onChanged() { |
| // To guarantee that all real Observers have had a chance to react to the event, post |
| // the CallbackHelper.notifyCalled() call. |
| mHandler.post(() -> onChangedCallback.notifyCalled()); |
| } |
| |
| @Override |
| public void onSelectionStateChange(List<DownloadHistoryItemWrapper> selectedItems) { |
| mOnSelectionItems = selectedItems; |
| mHandler.post(() -> onSelectionCallback.notifyCalled()); |
| } |
| |
| @Override |
| public void onSpaceDisplayUpdated(SpaceDisplay display) { |
| mHandler.post(() -> onSpaceDisplayUpdatedCallback.notifyCalled()); |
| } |
| } |
| |
| private static final String PREF_SHOW_STORAGE_INFO_HEADER = |
| "download_home_show_storage_info_header"; |
| |
| private StubbedProvider mStubbedProvider; |
| private TestObserver mAdapterObserver; |
| private DownloadManagerUi mUi; |
| private DownloadHistoryAdapter mAdapter; |
| |
| private RecyclerView mRecyclerView; |
| private TextView mSpaceUsedDisplay; |
| |
| @Before |
| public void setUp() throws Exception { |
| Editor editor = ContextUtils.getAppSharedPreferences().edit(); |
| editor.putBoolean(PREF_SHOW_STORAGE_INFO_HEADER, true).apply(); |
| |
| ThreadUtils.runOnUiThreadBlocking(() -> { |
| PrefServiceBridge.getInstance().setPromptForDownloadAndroid( |
| DownloadPromptStatus.DONT_SHOW); |
| }); |
| |
| HashMap<String, Boolean> features = new HashMap<String, Boolean>(); |
| features.put(ChromeFeatureList.DOWNLOADS_LOCATION_CHANGE, false); |
| features.put(ChromeFeatureList.DOWNLOAD_HOME_SHOW_STORAGE_INFO, false); |
| features.put(ChromeFeatureList.DOWNLOAD_HOME_V2, false); |
| features.put(ChromeFeatureList.OVERSCROLL_HISTORY_NAVIGATION, false); |
| ChromeFeatureList.setTestFeatures(features); |
| |
| mStubbedProvider = new StubbedProvider(); |
| DownloadManagerUi.setProviderForTests(mStubbedProvider); |
| |
| mAdapterObserver = new TestObserver(); |
| mStubbedProvider.getSelectionDelegate().addObserver(mAdapterObserver); |
| |
| startDownloadActivity(); |
| mUi = mActivityTestRule.getActivity().getDownloadManagerUiForTests(); |
| mStubbedProvider.setUIDelegate(mUi); |
| mAdapter = mUi.getDownloadHistoryAdapterForTests(); |
| mAdapter.registerAdapterDataObserver(mAdapterObserver); |
| |
| mSpaceUsedDisplay = (TextView) mActivityTestRule.getActivity().findViewById( |
| org.chromium.chrome.download.R.id.size_downloaded); |
| mRecyclerView = |
| ((RecyclerView) mActivityTestRule.getActivity().findViewById(R.id.recycler_view)); |
| |
| mAdapter.getSpaceDisplayForTests().addObserverForTests(mAdapterObserver); |
| } |
| |
| @Test |
| @MediumTest |
| public void testSpaceDisplay() throws Exception { |
| // This first check is a Criteria because initialization of the Adapter is asynchronous. |
| onView(withText("6.00 GB downloaded")).check(matches(isDisplayed())); |
| |
| // Add a new item. |
| int callCount = mAdapterObserver.onChangedCallback.getCallCount(); |
| int spaceDisplayCallCount = mAdapterObserver.onSpaceDisplayUpdatedCallback.getCallCount(); |
| final DownloadItem updateItem = StubbedProvider.createDownloadItem(7, "20151021 07:28"); |
| ThreadUtils.runOnUiThread(() -> mAdapter.onDownloadItemCreated(updateItem)); |
| mAdapterObserver.onChangedCallback.waitForCallback(callCount, 2); |
| mAdapterObserver.onSpaceDisplayUpdatedCallback.waitForCallback(spaceDisplayCallCount); |
| // Use Criteria here because the text for SpaceDisplay is updated through an AsyncTask. |
| onView(withText("6.50 GB downloaded")).check(matches(isDisplayed())); |
| |
| // Mark one download as deleted on disk, which should prevent it from being counted. |
| callCount = mAdapterObserver.onChangedCallback.getCallCount(); |
| spaceDisplayCallCount = mAdapterObserver.onSpaceDisplayUpdatedCallback.getCallCount(); |
| final DownloadItem deletedItem = StubbedProvider.createDownloadItem(6, "20151021 07:28"); |
| deletedItem.setHasBeenExternallyRemoved(true); |
| ThreadUtils.runOnUiThread(() -> mAdapter.onDownloadItemUpdated(deletedItem)); |
| mAdapterObserver.onChangedCallback.waitForCallback(callCount, 2); |
| mAdapterObserver.onSpaceDisplayUpdatedCallback.waitForCallback(spaceDisplayCallCount); |
| onView(withText("5.50 GB downloaded")).check(matches(isDisplayed())); |
| |
| // Say that the offline page has been deleted. |
| callCount = mAdapterObserver.onChangedCallback.getCallCount(); |
| spaceDisplayCallCount = mAdapterObserver.onSpaceDisplayUpdatedCallback.getCallCount(); |
| final OfflineItem deletedPage = StubbedProvider.createOfflineItem(3, "20151021 07:28"); |
| ThreadUtils.runOnUiThread( |
| () |
| -> mStubbedProvider.getOfflineContentProvider().observer.onItemRemoved( |
| deletedPage.id)); |
| mAdapterObserver.onChangedCallback.waitForCallback(callCount, 2); |
| mAdapterObserver.onSpaceDisplayUpdatedCallback.waitForCallback(spaceDisplayCallCount); |
| onView(withText("512.00 MB downloaded")).check(matches(isDisplayed())); |
| } |
| |
| /** |
| * Clicking on filters affects various things in the UI. |
| */ |
| @DisabledTest(message = "crbug.com/855389") |
| @Test |
| @MediumTest |
| public void testFilters() throws Exception { |
| // This first check is a Criteria because initialization of the Adapter is asynchronous. |
| CriteriaHelper.pollUiThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| return TextUtils.equals("6.00 GB downloaded", mSpaceUsedDisplay.getText()); |
| } |
| }); |
| |
| // Change the filter to Pages. Only the space display, offline page and the date header |
| // should stay. |
| int spaceDisplayCallCount = mAdapterObserver.onSpaceDisplayUpdatedCallback.getCallCount(); |
| clickOnFilter(mUi, 1); |
| Assert.assertEquals(3, mAdapter.getItemCount()); |
| |
| // Check that the number of items displayed is correct. |
| // We need to poll because RecyclerView doesn't animate changes immediately. |
| CriteriaHelper.pollUiThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| return mRecyclerView.getChildCount() == 3; |
| } |
| }); |
| |
| // Filtering doesn't affect the total download size. |
| mAdapterObserver.onSpaceDisplayUpdatedCallback.waitForCallback(spaceDisplayCallCount); |
| Assert.assertEquals("6.00 GB downloaded", mSpaceUsedDisplay.getText()); |
| } |
| |
| @Test |
| @MediumTest |
| @RetryOnFailure |
| @FlakyTest(message = "crbug.com/854241") |
| public void testDeleteFiles() throws Exception { |
| SnackbarManager.setDurationForTesting(1); |
| |
| // This first check is a Criteria because initialization of the Adapter is asynchronous. |
| CriteriaHelper.pollUiThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| return TextUtils.equals("6.00 GB downloaded", mSpaceUsedDisplay.getText()); |
| } |
| }); |
| |
| // Select the first two items. |
| toggleItemSelection(2); |
| toggleItemSelection(3); |
| |
| // Click the delete button, which should delete the items and reset the toolbar. |
| Assert.assertEquals(12, mAdapter.getItemCount()); |
| // checkForExternallyRemovedFiles() should have been called once already in onResume(). |
| Assert.assertEquals( |
| 1, mStubbedProvider.getDownloadDelegate().checkExternalCallback.getCallCount()); |
| Assert.assertEquals( |
| 0, mStubbedProvider.getDownloadDelegate().removeDownloadCallback.getCallCount()); |
| Assert.assertEquals( |
| 0, mStubbedProvider.getOfflineContentProvider().deleteItemCallback.getCallCount()); |
| int callCount = mAdapterObserver.onSpaceDisplayUpdatedCallback.getCallCount(); |
| ThreadUtils.runOnUiThread( |
| () |
| -> Assert.assertTrue( |
| mUi.getDownloadManagerToolbarForTests() |
| .getMenu() |
| .performIdentifierAction( |
| R.id.selection_mode_delete_menu_id, 0))); |
| |
| mAdapterObserver.onSpaceDisplayUpdatedCallback.waitForCallback(callCount); |
| Assert.assertEquals( |
| 1, mStubbedProvider.getDownloadDelegate().checkExternalCallback.getCallCount()); |
| Assert.assertFalse(mStubbedProvider.getSelectionDelegate().isSelectionEnabled()); |
| Assert.assertEquals(9, mAdapter.getItemCount()); |
| Assert.assertEquals("0.65 KB downloaded", mSpaceUsedDisplay.getText()); |
| } |
| |
| @Test |
| @MediumTest |
| @RetryOnFailure |
| @FlakyTest(message = "crbug.com/855219") |
| public void testDeleteFileFromMenu() throws Exception { |
| SnackbarManager.setDurationForTesting(1); |
| |
| // This first check is a Criteria because initialization of the Adapter is asynchronous. |
| CriteriaHelper.pollUiThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| return TextUtils.equals("6.00 GB downloaded", mSpaceUsedDisplay.getText()); |
| } |
| }); |
| |
| Assert.assertEquals(12, mAdapter.getItemCount()); |
| // checkForExternallyRemovedFiles() should have been called once already in onResume(). |
| Assert.assertEquals( |
| 1, mStubbedProvider.getDownloadDelegate().checkExternalCallback.getCallCount()); |
| Assert.assertEquals( |
| 0, mStubbedProvider.getDownloadDelegate().removeDownloadCallback.getCallCount()); |
| Assert.assertEquals( |
| 0, mStubbedProvider.getOfflineContentProvider().deleteItemCallback.getCallCount()); |
| int callCount = mAdapterObserver.onSpaceDisplayUpdatedCallback.getCallCount(); |
| |
| // Simulate a delete context menu action on the item. |
| simulateContextMenu(2, R.string.delete); |
| mAdapterObserver.onSpaceDisplayUpdatedCallback.waitForCallback(callCount); |
| Assert.assertEquals( |
| 1, mStubbedProvider.getDownloadDelegate().checkExternalCallback.getCallCount()); |
| Assert.assertFalse(mStubbedProvider.getSelectionDelegate().isSelectionEnabled()); |
| Assert.assertEquals(11, mAdapter.getItemCount()); |
| Assert.assertEquals("5.00 GB downloaded", mSpaceUsedDisplay.getText()); |
| } |
| |
| @DisabledTest(message = "crbug.com/855389") |
| @Test |
| @MediumTest |
| @RetryOnFailure |
| public void testUndoDelete() throws Exception { |
| // Adapter positions: |
| // 0 = space display |
| // 1 = date |
| // 2 = download item #7 |
| // 3 = download item #8 |
| // 4 = date |
| // 5 = download item #6 |
| // 6 = offline page #3 |
| |
| SnackbarManager.setDurationForTesting(5000); |
| |
| // Add duplicate items. |
| int callCount = mAdapterObserver.onSpaceDisplayUpdatedCallback.getCallCount(); |
| final DownloadItem item7 = StubbedProvider.createDownloadItem(7, "20161021 07:28"); |
| final DownloadItem item8 = StubbedProvider.createDownloadItem(8, "20161021 17:28"); |
| ThreadUtils.runOnUiThread(() -> { |
| mAdapter.onDownloadItemCreated(item7); |
| mAdapter.onDownloadItemCreated(item8); |
| }); |
| |
| // The criteria is needed because an AsyncTask is fired to update the space display, which |
| // can result in either 1 or 2 updates. |
| mAdapterObserver.onSpaceDisplayUpdatedCallback.waitForCallback(callCount); |
| CriteriaHelper.pollUiThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| return TextUtils.equals("6.50 GB downloaded", mSpaceUsedDisplay.getText()); |
| } |
| }); |
| |
| // Select download item #7 and offline page #3. |
| toggleItemSelection(2); |
| toggleItemSelection(6); |
| |
| Assert.assertEquals(15, mAdapter.getItemCount()); |
| |
| // Click the delete button. |
| callCount = mAdapterObserver.onSpaceDisplayUpdatedCallback.getCallCount(); |
| ThreadUtils.runOnUiThread( |
| () |
| -> Assert.assertTrue( |
| mUi.getDownloadManagerToolbarForTests() |
| .getMenu() |
| .performIdentifierAction( |
| R.id.selection_mode_delete_menu_id, 0))); |
| mAdapterObserver.onSpaceDisplayUpdatedCallback.waitForCallback(callCount); |
| |
| // Assert that items are temporarily removed from the adapter. The two selected items, |
| // one duplicate item, and one date bucket should be removed. |
| Assert.assertEquals(11, mAdapter.getItemCount()); |
| Assert.assertEquals("1.00 GB downloaded", mSpaceUsedDisplay.getText()); |
| |
| // Click "Undo" on the snackbar. |
| callCount = mAdapterObserver.onSpaceDisplayUpdatedCallback.getCallCount(); |
| final View rootView = mUi.getView().getRootView(); |
| Assert.assertNotNull(rootView.findViewById(R.id.snackbar)); |
| ThreadUtils.runOnUiThread( |
| (Runnable) () -> rootView.findViewById(R.id.snackbar_button).callOnClick()); |
| |
| mAdapterObserver.onSpaceDisplayUpdatedCallback.waitForCallback(callCount); |
| |
| // Assert that items are restored. |
| Assert.assertEquals( |
| 0, mStubbedProvider.getDownloadDelegate().removeDownloadCallback.getCallCount()); |
| Assert.assertEquals( |
| 0, mStubbedProvider.getOfflineContentProvider().deleteItemCallback.getCallCount()); |
| Assert.assertFalse(mStubbedProvider.getSelectionDelegate().isSelectionEnabled()); |
| Assert.assertEquals(15, mAdapter.getItemCount()); |
| Assert.assertEquals("6.50 GB downloaded", mSpaceUsedDisplay.getText()); |
| } |
| |
| @DisabledTest(message = "crbug.com/855389") |
| @Test |
| @MediumTest |
| @RetryOnFailure |
| public void testUndoDeleteFromMenu() throws Exception { |
| // Adapter positions: |
| // 0 = space display |
| // 1 = date |
| // 2 = download item #7 |
| // 3 = download item #8 |
| // 4 = date |
| // 5 = download item #6 |
| // 6 = offline page #3 |
| |
| SnackbarManager.setDurationForTesting(5000); |
| |
| // Add duplicate items. |
| int callCount = mAdapterObserver.onSpaceDisplayUpdatedCallback.getCallCount(); |
| final DownloadItem item7 = StubbedProvider.createDownloadItem(7, "20161021 07:28"); |
| final DownloadItem item8 = StubbedProvider.createDownloadItem(8, "20161021 17:28"); |
| ThreadUtils.runOnUiThread(() -> { |
| mAdapter.onDownloadItemCreated(item7); |
| mAdapter.onDownloadItemCreated(item8); |
| }); |
| |
| // The criteria is needed because an AsyncTask is fired to update the space display, which |
| // can result in either 1 or 2 updates. |
| mAdapterObserver.onSpaceDisplayUpdatedCallback.waitForCallback(callCount); |
| CriteriaHelper.pollUiThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| return TextUtils.equals("6.50 GB downloaded", mSpaceUsedDisplay.getText()); |
| } |
| }); |
| |
| Assert.assertEquals(15, mAdapter.getItemCount()); |
| callCount = mAdapterObserver.onSpaceDisplayUpdatedCallback.getCallCount(); |
| |
| // Simulate a delete context menu action on the item. |
| simulateContextMenu(2, R.string.delete); |
| mAdapterObserver.onSpaceDisplayUpdatedCallback.waitForCallback(callCount); |
| |
| // Assert that items are temporarily removed from the adapter. The two selected items, |
| // one duplicate item, and one date bucket should be removed. |
| Assert.assertEquals(12, mAdapter.getItemCount()); |
| Assert.assertEquals("6.00 GB downloaded", mSpaceUsedDisplay.getText()); |
| |
| // Click "Undo" on the snackbar. |
| callCount = mAdapterObserver.onSpaceDisplayUpdatedCallback.getCallCount(); |
| final View rootView = mUi.getView().getRootView(); |
| Assert.assertNotNull(rootView.findViewById(R.id.snackbar)); |
| ThreadUtils.runOnUiThread( |
| (Runnable) () -> rootView.findViewById(R.id.snackbar_button).callOnClick()); |
| |
| mAdapterObserver.onSpaceDisplayUpdatedCallback.waitForCallback(callCount); |
| |
| // Assert that items are restored. |
| Assert.assertEquals( |
| 0, mStubbedProvider.getDownloadDelegate().removeDownloadCallback.getCallCount()); |
| Assert.assertEquals( |
| 0, mStubbedProvider.getOfflineContentProvider().deleteItemCallback.getCallCount()); |
| Assert.assertFalse(mStubbedProvider.getSelectionDelegate().isSelectionEnabled()); |
| Assert.assertEquals(15, mAdapter.getItemCount()); |
| Assert.assertEquals("6.50 GB downloaded", mSpaceUsedDisplay.getText()); |
| } |
| |
| @DisabledTest(message = "crbug.com/855389") |
| @Test |
| @MediumTest |
| @RetryOnFailure |
| public void testUndoDeleteDuplicatesSelected() throws Exception { |
| // Adapter positions: |
| // 0 = space display |
| // 1 = date |
| // 2 = download item #7 |
| // 3 = download item #8 |
| // .... |
| |
| SnackbarManager.setDurationForTesting(5000); |
| |
| // Add duplicate items. |
| int callCount = mAdapterObserver.onSpaceDisplayUpdatedCallback.getCallCount(); |
| final DownloadItem item7 = StubbedProvider.createDownloadItem(7, "20161021 07:28"); |
| final DownloadItem item8 = StubbedProvider.createDownloadItem(8, "20161021 17:28"); |
| ThreadUtils.runOnUiThread(() -> { |
| mAdapter.onDownloadItemCreated(item7); |
| mAdapter.onDownloadItemCreated(item8); |
| }); |
| |
| // The criteria is needed because an AsyncTask is fired to update the space display, which |
| // can result in either 1 or 2 updates. |
| mAdapterObserver.onSpaceDisplayUpdatedCallback.waitForCallback(callCount); |
| CriteriaHelper.pollUiThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| return TextUtils.equals("6.50 GB downloaded", mSpaceUsedDisplay.getText()); |
| } |
| }); |
| |
| // Select download item #7 and download item #8. |
| toggleItemSelection(2); |
| toggleItemSelection(3); |
| |
| Assert.assertEquals(15, mAdapter.getItemCount()); |
| |
| // Click the delete button. |
| callCount = mAdapterObserver.onSpaceDisplayUpdatedCallback.getCallCount(); |
| ThreadUtils.runOnUiThread( |
| () |
| -> Assert.assertTrue( |
| mUi.getDownloadManagerToolbarForTests() |
| .getMenu() |
| .performIdentifierAction( |
| R.id.selection_mode_delete_menu_id, 0))); |
| mAdapterObserver.onSpaceDisplayUpdatedCallback.waitForCallback(callCount); |
| |
| // Assert that the two items and their date bucket are temporarily removed from the adapter. |
| Assert.assertEquals(12, mAdapter.getItemCount()); |
| Assert.assertEquals("6.00 GB downloaded", mSpaceUsedDisplay.getText()); |
| |
| // Click "Undo" on the snackbar. |
| callCount = mAdapterObserver.onSpaceDisplayUpdatedCallback.getCallCount(); |
| final View rootView = mUi.getView().getRootView(); |
| Assert.assertNotNull(rootView.findViewById(R.id.snackbar)); |
| ThreadUtils.runOnUiThread( |
| (Runnable) () -> rootView.findViewById(R.id.snackbar_button).callOnClick()); |
| |
| mAdapterObserver.onSpaceDisplayUpdatedCallback.waitForCallback(callCount); |
| |
| // Assert that items are restored. |
| Assert.assertEquals(15, mAdapter.getItemCount()); |
| Assert.assertEquals("6.50 GB downloaded", mSpaceUsedDisplay.getText()); |
| } |
| |
| @Test |
| @MediumTest |
| @DisableFeatures("OfflinePagesSharing") |
| @FlakyTest(message = "crbug.com/855167") |
| public void testShareFiles() throws Exception { |
| // Adapter positions: |
| // 0 = space display |
| // 1 = date |
| // 2 = download item #6 |
| // 3 = offline page #3 |
| // 4 = date |
| // 5 = download item #3 |
| // 6 = download item #4 |
| // 7 = download item #5 |
| // 8 = date |
| // 9 = download item #0 |
| // 10 = download item #1 |
| // 11 = download item #2 |
| |
| // Select an image, download item #6. |
| toggleItemSelection(2); |
| Intent shareIntent = DownloadUtils.createShareIntent( |
| mUi.getBackendProvider().getSelectionDelegate().getSelectedItemsAsList(), null); |
| Assert.assertEquals("Incorrect intent action", Intent.ACTION_SEND, shareIntent.getAction()); |
| Assert.assertEquals("Incorrect intent mime type", "image/png", shareIntent.getType()); |
| Assert.assertNotNull( |
| "Intent expected to have stream", shareIntent.getExtras().get(Intent.EXTRA_STREAM)); |
| Assert.assertNull("Intent not expected to have parcelable ArrayList", |
| shareIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)); |
| |
| // Scroll to ensure the item at position 8 is visible. |
| ThreadUtils.runOnUiThread(() -> mRecyclerView.scrollToPosition(9)); |
| InstrumentationRegistry.getInstrumentation().waitForIdleSync(); |
| |
| // Select another image, download item #0. |
| toggleItemSelection(9); |
| shareIntent = DownloadUtils.createShareIntent( |
| mUi.getBackendProvider().getSelectionDelegate().getSelectedItemsAsList(), null); |
| Assert.assertEquals( |
| "Incorrect intent action", Intent.ACTION_SEND_MULTIPLE, shareIntent.getAction()); |
| Assert.assertEquals("Incorrect intent mime type", "image/*", shareIntent.getType()); |
| Assert.assertEquals("Intent expected to have parcelable ArrayList", 2, |
| shareIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM).size()); |
| |
| // Scroll to ensure the item at position 5 is visible. |
| ThreadUtils.runOnUiThread(() -> mRecyclerView.scrollToPosition(6)); |
| InstrumentationRegistry.getInstrumentation().waitForIdleSync(); |
| |
| // Select non-image item, download item #4. |
| toggleItemSelection(6); |
| shareIntent = DownloadUtils.createShareIntent( |
| mUi.getBackendProvider().getSelectionDelegate().getSelectedItemsAsList(), null); |
| Assert.assertEquals( |
| "Incorrect intent action", Intent.ACTION_SEND_MULTIPLE, shareIntent.getAction()); |
| Assert.assertEquals("Incorrect intent mime type", "*/*", shareIntent.getType()); |
| Assert.assertEquals("Intent expected to have parcelable ArrayList", 3, |
| shareIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM).size()); |
| |
| // Scroll to ensure the item at position 2 is visible. |
| ThreadUtils.runOnUiThread(() -> mRecyclerView.scrollToPosition(3)); |
| InstrumentationRegistry.getInstrumentation().waitForIdleSync(); |
| |
| // Select an offline page #3. |
| toggleItemSelection(3); |
| shareIntent = DownloadUtils.createShareIntent( |
| mUi.getBackendProvider().getSelectionDelegate().getSelectedItemsAsList(), null); |
| Assert.assertEquals( |
| "Incorrect intent action", Intent.ACTION_SEND_MULTIPLE, shareIntent.getAction()); |
| Assert.assertEquals("Incorrect intent mime type", "*/*", shareIntent.getType()); |
| Assert.assertEquals("Intent expected to have parcelable ArrayList", 3, |
| shareIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM).size()); |
| Assert.assertEquals("Intent expected to have plain text for offline page URL", |
| "https://thangs.com", |
| IntentUtils.safeGetStringExtra(shareIntent, Intent.EXTRA_TEXT)); |
| } |
| |
| // TODO(carlosk): OfflineItems used here come from StubbedProvider so this might not be the best |
| // place to test peer-2-peer sharing. |
| @DisabledTest(message = "crbug.com/855389") |
| @Test |
| @MediumTest |
| @EnableFeatures("OfflinePagesSharing") |
| public void testShareOfflinePageWithP2PSharingEnabled() throws Exception { |
| // Scroll to ensure the item at position 2 is visible. |
| ThreadUtils.runOnUiThread(() -> mRecyclerView.scrollToPosition(3)); |
| InstrumentationRegistry.getInstrumentation().waitForIdleSync(); |
| |
| // Select the offline page located at position #3. |
| toggleItemSelection(3); |
| List<DownloadHistoryItemWrapper> selectedItems = |
| mUi.getBackendProvider().getSelectionDelegate().getSelectedItemsAsList(); |
| Assert.assertEquals("There should be only one item selected", 1, selectedItems.size()); |
| Intent shareIntent = DownloadUtils.createShareIntent(selectedItems, null); |
| |
| Assert.assertEquals("Incorrect intent action", Intent.ACTION_SEND, shareIntent.getAction()); |
| Assert.assertEquals("Incorrect intent mime type", "*/*", shareIntent.getType()); |
| Assert.assertNotNull("Intent expected to have parcelable ArrayList", |
| shareIntent.getParcelableExtra(Intent.EXTRA_STREAM)); |
| Assert.assertEquals("Intent expected to have parcelable Uri", |
| "file:///data/fake_path/Downloads/4", |
| shareIntent.getParcelableExtra(Intent.EXTRA_STREAM).toString()); |
| Assert.assertNull("Intent expected to not have any text for offline page", |
| IntentUtils.safeGetStringExtra(shareIntent, Intent.EXTRA_TEXT)); |
| |
| // Pass a map that contains a new file path. |
| HashMap<String, String> newFilePathMap = new HashMap<String, String>(); |
| newFilePathMap.put(((OfflineItemWrapper) selectedItems.get(0)).getId(), |
| "/data/new_fake_path/Downloads/4"); |
| shareIntent = DownloadUtils.createShareIntent(selectedItems, newFilePathMap); |
| |
| Assert.assertEquals("Incorrect intent action", Intent.ACTION_SEND, shareIntent.getAction()); |
| Assert.assertEquals("Incorrect intent mime type", "*/*", shareIntent.getType()); |
| Assert.assertNotNull("Intent expected to have parcelable ArrayList", |
| shareIntent.getParcelableExtra(Intent.EXTRA_STREAM)); |
| Assert.assertEquals("Intent expected to have parcelable Uri", |
| "file:///data/new_fake_path/Downloads/4", |
| shareIntent.getParcelableExtra(Intent.EXTRA_STREAM).toString()); |
| Assert.assertNull("Intent expected to not have any text for offline page", |
| IntentUtils.safeGetStringExtra(shareIntent, Intent.EXTRA_TEXT)); |
| } |
| |
| @Test |
| @MediumTest |
| @DisableFeatures(ChromeFeatureList.DOWNLOADS_LOCATION_CHANGE) |
| public void testLongClickItem() throws Exception { |
| // The selection toolbar should not be showing. |
| onView(withContentDescription("Cancel selection")).check(doesNotExist()); |
| onView(withId(R.id.close_menu_id)).check(matches(isDisplayed())); |
| onView(withId(R.id.selection_mode_number)).check(matches(not(isDisplayed()))); |
| onView(withId(org.chromium.chrome.download.R.id.selection_mode_share_menu_id)) |
| .check(doesNotExist()); |
| onView(withId(R.id.selection_mode_delete_menu_id)).check(doesNotExist()); |
| Assert.assertFalse(mStubbedProvider.getSelectionDelegate().isSelectionEnabled()); |
| |
| // Select an item. |
| onView(withText("huge_image.png")).perform(longClick()); |
| |
| // The toolbar should flip states to allow doing things with the selected items. |
| onView(withId(R.id.close_menu_id)).check(doesNotExist()); |
| onView(withId(R.id.selection_mode_number)).check(matches(isDisplayed())); |
| onView(withId(org.chromium.chrome.download.R.id.selection_mode_share_menu_id)) |
| .check(matches(isDisplayed())); |
| onView(withId(R.id.selection_mode_delete_menu_id)).check(matches(isDisplayed())); |
| Assert.assertTrue(mStubbedProvider.getSelectionDelegate().isSelectionEnabled()); |
| |
| // Deselect the same item. |
| onView(withText("huge_image.png")).perform(longClick()); |
| |
| // The toolbar should flip back. |
| onView(withContentDescription("Cancel selection")).check(doesNotExist()); |
| onView(withId(R.id.close_menu_id)).check(matches(isDisplayed())); |
| onView(withId(R.id.selection_mode_number)).check(matches(not(isDisplayed()))); |
| onView(withId(org.chromium.chrome.download.R.id.selection_mode_share_menu_id)) |
| .check(doesNotExist()); |
| onView(withId(R.id.selection_mode_delete_menu_id)).check(doesNotExist()); |
| Assert.assertFalse(mStubbedProvider.getSelectionDelegate().isSelectionEnabled()); |
| } |
| |
| @Test |
| @MediumTest |
| @DisableFeatures(ChromeFeatureList.DOWNLOADS_LOCATION_CHANGE) |
| public void testSearchView() throws Exception { |
| final DownloadManagerToolbar toolbar = mUi.getDownloadManagerToolbarForTests(); |
| onView(withId(R.id.search_text)).check(matches(not(isDisplayed()))); |
| |
| onView(withText("huge_image.png")).perform(longClick()); |
| |
| Assert.assertTrue(mStubbedProvider.getSelectionDelegate().isSelectionEnabled()); |
| |
| int callCount = mAdapterObserver.onSelectionCallback.getCallCount(); |
| ThreadUtils.runOnUiThreadBlocking( |
| (Runnable) () -> toolbar.getMenu().performIdentifierAction(R.id.search_menu_id, 0)); |
| |
| // The selection should be cleared when a search is started. |
| mAdapterObserver.onSelectionCallback.waitForCallback(callCount, 1); |
| Assert.assertFalse(mStubbedProvider.getSelectionDelegate().isSelectionEnabled()); |
| onView(withId(R.id.search_text)).check(matches(isDisplayed())); |
| |
| // Select an item and assert that the search view is no longer showing. |
| onView(withText("huge_image.png")).perform(longClick()); |
| Assert.assertTrue(mStubbedProvider.getSelectionDelegate().isSelectionEnabled()); |
| onView(withId(R.id.search_text)).check(matches(not(isDisplayed()))); |
| |
| // Clear the selection and assert that the search view is showing again. |
| onView(withText("huge_image.png")).perform(longClick()); |
| Assert.assertFalse(mStubbedProvider.getSelectionDelegate().isSelectionEnabled()); |
| onView(withId(R.id.search_text)).check(matches(isDisplayed())); |
| |
| // Close the search view. |
| onView(withContentDescription("Go back")).perform(click()); |
| onView(withId(R.id.search_text)).check(matches(not(isDisplayed()))); |
| } |
| |
| @Test |
| @MediumTest |
| @DisableFeatures(ChromeFeatureList.DOWNLOADS_LOCATION_CHANGE) |
| public void testSpinner() throws Exception { |
| // Open spinner. |
| onView(withId(org.chromium.chrome.download.R.id.spinner)).perform(click()); |
| |
| // Check all TextViews displayed. |
| onView(withText("All")).check(matches(isDisplayed())); |
| onView(withText("Pages")).check(matches(isDisplayed())); |
| onView(withText("Video")).check(matches(isDisplayed())); |
| onView(withText("Audio")).check(matches(isDisplayed())); |
| onView(withText("Images")).check(matches(isDisplayed())); |
| onView(withText("Documents")).check(matches(isDisplayed())); |
| onView(withText("Other")).check(matches(isDisplayed())); |
| |
| // Click Pages. |
| onView(withText("Pages")).perform(click()); |
| onView(withText("page 4")).check(matches(isDisplayed())); |
| |
| // Click Video. |
| onView(withId(org.chromium.chrome.download.R.id.spinner)).perform(click()); |
| onView(withText("Video")).perform(click()); |
| onView(withText("four.webm")).check(matches(isDisplayed())); |
| |
| // Click Audio. |
| onView(withId(org.chromium.chrome.download.R.id.spinner)).perform(click()); |
| onView(withText("Audio")).perform(click()); |
| onView(withText("five.mp3")).check(matches(isDisplayed())); |
| onView(withText("six.mp3")).check(matches(isDisplayed())); |
| |
| // Click Images. |
| onView(withId(org.chromium.chrome.download.R.id.spinner)).perform(click()); |
| onView(withText("Images")).perform(click()); |
| onView(withText("huge_image.png")).check(matches(isDisplayed())); |
| onView(withText("first_file.jpg")).check(matches(isDisplayed())); |
| onView(withText("second_file.gif")).check(matches(isDisplayed())); |
| |
| // Click Documents. |
| onView(withId(org.chromium.chrome.download.R.id.spinner)).perform(click()); |
| onView(withText("Documents")).perform(click()); |
| onView(withText("third_file")).check(matches(isDisplayed())); |
| |
| // Click Other. |
| onView(withId(org.chromium.chrome.download.R.id.spinner)).perform(click()); |
| onView(withText("Other")).perform(click()); |
| onView(withText("No downloads here")).check(matches(isDisplayed())); |
| } |
| |
| private DownloadActivity startDownloadActivity() throws Exception { |
| // Load up the downloads lists. |
| DownloadItem item0 = StubbedProvider.createDownloadItem(0, "19551112 06:38"); |
| DownloadItem item1 = StubbedProvider.createDownloadItem(1, "19551112 06:38"); |
| DownloadItem item2 = StubbedProvider.createDownloadItem(2, "19551112 06:38"); |
| DownloadItem item3 = StubbedProvider.createDownloadItem(3, "19851026 09:00"); |
| DownloadItem item4 = StubbedProvider.createDownloadItem(4, "19851026 09:00"); |
| DownloadItem item5 = StubbedProvider.createDownloadItem(5, "19851026 09:00"); |
| DownloadItem item6 = StubbedProvider.createDownloadItem(6, "20151021 07:28"); |
| OfflineItem item7 = StubbedProvider.createOfflineItem(3, "20151021 07:28"); |
| mStubbedProvider.getDownloadDelegate().regularItems.add(item0); |
| mStubbedProvider.getDownloadDelegate().regularItems.add(item1); |
| mStubbedProvider.getDownloadDelegate().regularItems.add(item2); |
| mStubbedProvider.getDownloadDelegate().regularItems.add(item3); |
| mStubbedProvider.getDownloadDelegate().regularItems.add(item4); |
| mStubbedProvider.getDownloadDelegate().regularItems.add(item5); |
| mStubbedProvider.getDownloadDelegate().regularItems.add(item6); |
| mStubbedProvider.getOfflineContentProvider().items.add(item7); |
| |
| // Start the activity up. |
| Intent intent = new Intent(); |
| intent.setClass(InstrumentationRegistry.getTargetContext(), DownloadActivity.class); |
| intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| return mActivityTestRule.launchActivity(intent); |
| } |
| |
| private void clickOnFilter(final DownloadManagerUi ui, final int position) throws Exception { |
| int previousCount = mAdapterObserver.onChangedCallback.getCallCount(); |
| final Spinner spinner = mUi.getDownloadManagerToolbarForTests().getSpinnerForTests(); |
| ThreadUtils.runOnUiThread(() -> { |
| spinner.performClick(); |
| spinner.setSelection(position); |
| }); |
| mAdapterObserver.onChangedCallback.waitForCallback(previousCount); |
| } |
| |
| private void toggleItemSelection(int position) throws Exception { |
| int callCount = mAdapterObserver.onSelectionCallback.getCallCount(); |
| final DownloadItemView itemView = getView(position); |
| ThreadUtils.runOnUiThread((Runnable) () -> itemView.performLongClick()); |
| mAdapterObserver.onSelectionCallback.waitForCallback(callCount, 1); |
| } |
| |
| private void simulateContextMenu(int position, @StringRes int text) throws Exception { |
| final DownloadItemView view = getView(position); |
| ThreadUtils.runOnUiThread((Runnable) () -> { |
| Item[] items = view.getItems(); |
| for (Item item : items) { |
| if (item.getTextId() == text) { |
| view.onItemSelected(item); |
| return; |
| } |
| } |
| throw new IllegalStateException("Context menu option not found " + text); |
| }); |
| } |
| |
| private DownloadItemView getView(int position) throws Exception { |
| int callCount = mAdapterObserver.onSelectionCallback.getCallCount(); |
| ViewHolder mostRecentHolder = mRecyclerView.findViewHolderForAdapterPosition(position); |
| Assert.assertTrue(mostRecentHolder instanceof DownloadHistoryItemViewHolder); |
| return ((DownloadHistoryItemViewHolder) mostRecentHolder).getItemView(); |
| } |
| } |