// Copyright 2020 The Chromium Authors
// 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.incognito;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.chromium.ui.test.util.ViewUtils.onViewWaiting;
import android.os.Environment;
import android.view.View;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameter;
import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate;
import org.chromium.base.test.params.ParameterizedRunner;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.chrome.browser.customtabs.IncognitoCustomTabActivityTestRule;
import org.chromium.chrome.browser.download.DownloadItem;
import org.chromium.chrome.browser.download.DownloadManagerService;
import org.chromium.chrome.browser.download.DownloadPromptStatus;
import org.chromium.chrome.browser.download.items.OfflineContentAggregatorFactory;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.profiles.ProfileKey;
import org.chromium.chrome.browser.profiles.ProfileManager;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.R;
import org.chromium.components.offline_items_collection.ContentId;
import org.chromium.components.offline_items_collection.OfflineContentProvider;
import org.chromium.components.offline_items_collection.OfflineItem;
import org.chromium.components.offline_items_collection.OfflineItemState;
import org.chromium.components.offline_items_collection.UpdateDelta;
import org.chromium.components.user_prefs.UserPrefs;
import org.chromium.content_public.browser.test.util.TouchCommon;
import org.chromium.net.test.EmbeddedTestServer;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* This test class checks download entries leakage between all different pairs of Activity types
* with a constraint that one of the interacting activity must be either Incognito Tab or Incognito
* CCT.
*/
@RunWith(ParameterizedRunner.class)
@UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
@CommandLineFlags.Add({
ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
ChromeSwitches.DISABLE_ALL_IPH
})
public class IncognitoDownloadLeakageTest {
private String mDownloadTestPage;
private final String mDownloadedFileName = "test.gzip";
private static final File DOWNLOAD_DIRECTORY =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
private static final String DOWNLOAD_TEST_PAGE_PATH =
"/chrome/test/data/android/download/get.html";
private EmbeddedTestServer mTestServer;
private CallbackHelper mHttpDownloadFinishedCallback = new CallbackHelper();
private CallbackHelper mRetrieveDownloadsCallback = new CallbackHelper();
private List<DownloadItem> mOffTheRecordDownloadItems;
private List<DownloadItem> mRegularDownloadItems;
private DownloadManagerService.DownloadObserver mTestDownloadManagerServiceObserver =
new DownloadManagerService.DownloadObserver() {
@Override
public void onAllDownloadsRetrieved(
List<DownloadItem> list, ProfileKey profileKey) {
if (profileKey.isOffTheRecord()) {
mOffTheRecordDownloadItems = new ArrayList<DownloadItem>(list);
} else {
mRegularDownloadItems = new ArrayList<DownloadItem>(list);
}
mRetrieveDownloadsCallback.notifyCalled();
}
@Override
public void onDownloadItemCreated(DownloadItem item) {}
@Override
public void onDownloadItemUpdated(DownloadItem item) {}
@Override
public void onDownloadItemRemoved(String guid) {}
@Override
public void onAddOrReplaceDownloadSharedPreferenceEntry(ContentId id) {}
};
private OfflineContentProvider.Observer mTestDownloadBackendObserver =
new OfflineContentProvider.Observer() {
@Override
public void onItemsAdded(List<OfflineItem> items) {}
@Override
public void onItemRemoved(ContentId id) {}
@Override
public void onItemUpdated(OfflineItem item, UpdateDelta updateDelta) {
if (item.state == OfflineItemState.COMPLETE) {
mHttpDownloadFinishedCallback.notifyCalled();
}
}
};
@Rule
public ChromeTabbedActivityTestRule mChromeActivityTestRule =
new ChromeTabbedActivityTestRule();
@Rule
public IncognitoCustomTabActivityTestRule mCustomTabActivityTestRule =
new IncognitoCustomTabActivityTestRule();
@Before
public void setUp() throws Exception {
mTestServer =
EmbeddedTestServer.createAndStartServer(
ApplicationProvider.getApplicationContext());
mDownloadTestPage = mTestServer.getURL(DOWNLOAD_TEST_PAGE_PATH);
// Ensuring native is initialized, as code below requires it.
IncognitoDataTestUtils.fireAndWaitForCctWarmup();
// Download related setUp steps.
ThreadUtils.runOnUiThreadBlocking(
() -> {
DownloadManagerService.getDownloadManagerService()
.addDownloadObserver(mTestDownloadManagerServiceObserver);
OfflineContentAggregatorFactory.get().addObserver(mTestDownloadBackendObserver);
});
ThreadUtils.runOnUiThreadBlocking(
() -> {
// This skips the download prompt dialog for tests.
UserPrefs.get(ProfileManager.getLastUsedRegularProfile())
.setInteger(
Pref.PROMPT_FOR_DOWNLOAD_ANDROID,
DownloadPromptStatus.DONT_SHOW);
});
deleteFilesInDownloadDirectory(mDownloadedFileName);
}
@After
public void tearDown() {
deleteFilesInDownloadDirectory(mDownloadedFileName);
ThreadUtils.runOnUiThreadBlocking(
() ->
DownloadManagerService.getDownloadManagerService()
.removeDownloadObserver(mTestDownloadManagerServiceObserver));
ThreadUtils.runOnUiThreadBlocking(
() -> IncognitoDataTestUtils.closeTabs(mChromeActivityTestRule));
}
private boolean hasFileDownloaded(String downloadedFileName) {
File file = new File(DOWNLOAD_DIRECTORY, downloadedFileName);
return file.exists();
}
public void deleteFilesInDownloadDirectory(String... filenames) {
for (String filename : filenames) {
final File fileToDelete = new File(DOWNLOAD_DIRECTORY, filename);
if (fileToDelete.exists()) {
Assert.assertTrue(
"Could not delete file " + filename + " that would block this test",
fileToDelete.delete());
}
}
}
private void waitForDownloadToFinish() throws TimeoutException {
int currentCallCount = mHttpDownloadFinishedCallback.getCallCount();
onViewWaiting(withId(R.id.message_primary_button)).perform(click());
mHttpDownloadFinishedCallback.waitForCallback(
currentCallCount, currentCallCount + 1, 5, TimeUnit.SECONDS);
}
private void startDownload(Tab tab) throws TimeoutException {
View currentView = tab.getView();
if (!currentView.hasFocus()) {
TouchCommon.singleClickView(currentView);
}
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
TouchCommon.singleClickView(currentView);
waitForDownloadToFinish();
}
@Test
@LargeTest
@UseMethodParameter(IncognitoDataTestUtils.TestParams.IncognitoToRegular.class)
public void testIncognitoDowloadEntriesNotVisibleInRegular(
String incognitoActivityType, String regularActivityType) throws Exception {
IncognitoDataTestUtils.ActivityType incognitoActivity =
IncognitoDataTestUtils.ActivityType.valueOf(incognitoActivityType);
IncognitoDataTestUtils.ActivityType regularActivity =
IncognitoDataTestUtils.ActivityType.valueOf(regularActivityType);
// Initiate download from incognito context.
Tab incognitoTab =
incognitoActivity.launchUrl(
mChromeActivityTestRule, mCustomTabActivityTestRule, mDownloadTestPage);
startDownload(incognitoTab);
// Check the file is downloaded
assertTrue(hasFileDownloaded(mDownloadedFileName));
// Retrieve downloads from the incognito DownloadService.
ThreadUtils.runOnUiThreadBlocking(
() -> {
Profile profile = incognitoTab.getProfile();
DownloadManagerService.getDownloadManagerService()
.getAllDownloads(profile.getOTRProfileID());
});
mRetrieveDownloadsCallback.waitForCallback(0);
// One download item should be visible.
assertEquals(1, mOffTheRecordDownloadItems.size());
// Loads "about:blank" in the regular Activity.
regularActivity.launchUrl(
mChromeActivityTestRule, mCustomTabActivityTestRule, "about:blank");
// Retrieve downloads for regular Profile.
ThreadUtils.runOnUiThreadBlocking(
() -> DownloadManagerService.getDownloadManagerService().getAllDownloads(null));
mRetrieveDownloadsCallback.waitForCallback(1);
// No download entries should leak from incognito to regular.
assertEquals(0, mRegularDownloadItems.size());
}
@Test
@LargeTest
@UseMethodParameter(IncognitoDataTestUtils.TestParams.IncognitoToIncognito.class)
public void testIncognitoDowloadEntriesNotVisibleInAnotherIncognito(
String incognitoActivityType1, String incognitoActivityType2) throws Exception {
IncognitoDataTestUtils.ActivityType incognitoActivity1 =
IncognitoDataTestUtils.ActivityType.valueOf(incognitoActivityType1);
IncognitoDataTestUtils.ActivityType incognitoActivity2 =
IncognitoDataTestUtils.ActivityType.valueOf(incognitoActivityType2);
// Initiate download from the first incognito Activity. This returns either a CCT or a
// Chrome incognito tab.
Tab incognitoTab1 =
incognitoActivity1.launchUrl(
mChromeActivityTestRule, mCustomTabActivityTestRule, mDownloadTestPage);
startDownload(incognitoTab1);
// Check the file is downloaded
assertTrue(hasFileDownloaded(mDownloadedFileName));
// Retrieve downloads from the incognito DownloadService.
ThreadUtils.runOnUiThreadBlocking(
() -> {
Profile profile = incognitoTab1.getProfile();
DownloadManagerService.getDownloadManagerService()
.getAllDownloads(profile.getOTRProfileID());
});
mRetrieveDownloadsCallback.waitForCallback(0);
// One download item should be visible.
assertEquals(1, mOffTheRecordDownloadItems.size());
// Load "about:blank" in the second incognito Activity.
Tab incognitoTab2 =
incognitoActivity2.launchUrl(
mChromeActivityTestRule, mCustomTabActivityTestRule, "about:blank");
// Retrieve downloads for the second incognito profile.
ThreadUtils.runOnUiThreadBlocking(
() -> {
Profile profile = incognitoTab2.getProfile();
DownloadManagerService.getDownloadManagerService()
.getAllDownloads(profile.getOTRProfileID());
});
mRetrieveDownloadsCallback.waitForCallback(1);
// No download entries should leak to/from an incognito CCT.
assertEquals(0, mOffTheRecordDownloadItems.size());
}
}