From: Ryan Archer Date: Wed, 2 Aug 2017 01:41:28 -0400 Subject: Add an always-incognito mode More specifically, add a preference that causes all new tabs and all clicked links to launch as incognito. Make sure initial incognito status is correctly recognized. Enable incognito custom tabs and fix crashes for incognito/custom tab intents (credits to @uazo) --- chrome/android/chrome_java_sources.gni | 1 + .../java/res/xml/privacy_preferences.xml | 5 ++ .../AlwaysIncognitoLinkInterceptor.java | 74 +++++++++++++++++++ .../chrome/browser/ChromeTabbedActivity.java | 6 +- .../chrome/browser/app/ChromeActivity.java | 4 + .../AppMenuPropertiesDelegateImpl.java | 6 ++ .../ChromeContextMenuPopulator.java | 9 ++- .../CustomTabIntentDataProvider.java | 5 +- .../browser/init/StartupTabPreloader.java | 11 ++- .../privacy/settings/PrivacySettings.java | 4 +- .../browser/tabmodel/ChromeTabCreator.java | 16 +++- .../tabmodel/TabModelSelectorBase.java | 8 ++ .../browser/tabmodel/TabPersistentStore.java | 10 +++ .../webapps/WebappIntentDataProvider.java | 14 ++++ .../flags/android/chrome_feature_list.cc | 2 +- .../strings/android_chrome_strings.grd | 7 ++ 16 files changed, 172 insertions(+), 10 deletions(-) create mode 100644 chrome/android/java/src/org/chromium/chrome/browser/AlwaysIncognitoLinkInterceptor.java diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni --- a/chrome/android/chrome_java_sources.gni +++ b/chrome/android/chrome_java_sources.gni @@ -3,6 +3,7 @@ # found in the LICENSE file. chrome_java_sources = [ + "java/src/org/chromium/chrome/browser/AlwaysIncognitoLinkInterceptor.java", "java/src/com/google/android/apps/chrome/appwidget/bookmarks/BookmarkThumbnailWidgetProvider.java", "java/src/org/chromium/chrome/browser/ActivityTabProvider.java", "java/src/org/chromium/chrome/browser/AfterStartupTaskUtils.java", diff --git a/chrome/android/java/res/xml/privacy_preferences.xml b/chrome/android/java/res/xml/privacy_preferences.xml --- a/chrome/android/java/res/xml/privacy_preferences.xml +++ b/chrome/android/java/res/xml/privacy_preferences.xml @@ -18,6 +18,11 @@ android:summary="@string/preload_pages_summary" android:persistent="false" android:order="1"/> + Tabs instead. + */ +public class AlwaysIncognitoLinkInterceptor extends EmptyTabObserver { + + public static final String PREF_ALWAYS_INCOGNITO = "always_incognito"; + + private final SharedPreferences alwaysIncognitoContainer; + private final Map lastUrls; + private final Set revertingTabs; + + public AlwaysIncognitoLinkInterceptor(final SharedPreferences alwaysIncognitoContainer) { + + assert alwaysIncognitoContainer != null; + + this.alwaysIncognitoContainer = alwaysIncognitoContainer; + lastUrls = new HashMap(); + revertingTabs = new HashSet(); + } + + @Override + public void onDestroyed(final Tab tab) + { + lastUrls.remove(tab); + revertingTabs.remove(tab); + } + + @Override + public void onUpdateUrl(final Tab tab, final String url) { + + if (tab == null) return; + if (url == null) return; + if (tab.isIncognito()) return; + if (alwaysIncognitoContainer == null) return; + + final String lastUrl = lastUrls.put(tab, url); + + if (!alwaysIncognitoContainer.getBoolean(PREF_ALWAYS_INCOGNITO, false)) return; + if (revertingTabs.contains(tab)) { + revertingTabs.remove(tab); + return; + } + + ((ChromeTabbedActivity)tab.getWindowAndroid().getActivity().get()).getTabCreator(true).createNewTab(new LoadUrlParams(url), TabLaunchType.FROM_LINK, tab); + + if ((url.equals(lastUrl)) || (!tab.canGoBack())) { + // this call was triggered by a reload + } else { + revertingTabs.add(tab); + tab.goBack(); + } + } +} diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java --- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java @@ -49,6 +49,7 @@ import org.chromium.base.supplier.OneshotSupplierImpl; import org.chromium.base.supplier.Supplier; import org.chromium.base.task.PostTask; import org.chromium.chrome.R; +import org.chromium.chrome.browser.AlwaysIncognitoLinkInterceptor; import org.chromium.chrome.browser.IntentHandler.IntentHandlerDelegate; import org.chromium.chrome.browser.IntentHandler.TabOpenType; import org.chromium.chrome.browser.accessibility_tab_switcher.OverviewListLayout; @@ -1601,8 +1602,9 @@ public class ChromeTabbedActivity extends ChromeActivity throw new IllegalStateException( "Attempting to access TabCreator before initialization"); } + if (ContextUtils.getAppSharedPreferences().getBoolean(AlwaysIncognitoLinkInterceptor.PREF_ALWAYS_INCOGNITO, false)) { + return mIncognitoTabCreator; + } return incognito ? mIncognitoTabCreator : mRegularTabCreator; } diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java --- a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java @@ -523,6 +523,12 @@ public class AppMenuPropertiesDelegateImpl implements AppMenuPropertiesDelegate } private void prepareCommonMenuItems(Menu menu, @MenuGroup int menuGroup, boolean isIncognito) { + if (ContextUtils.getAppSharedPreferences().getBoolean("always_incognito", false)) { + final MenuItem newTabOption = menu.findItem(R.id.new_tab_menu_id); + if (newTabOption != null) + newTabOption.setVisible(false); + } + // We have to iterate all menu items since same menu item ID may be associated with more // than one menu items. boolean isMenuGroupTabsVisible = TabUiFeatureUtilities.isTabGroupsAndroidEnabled() diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java --- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java @@ -29,7 +29,9 @@ import androidx.annotation.VisibleForTesting; import org.chromium.base.ContextUtils; import org.chromium.base.metrics.RecordHistogram; import org.chromium.base.supplier.Supplier; +import org.chromium.base.ContextUtils; import org.chromium.chrome.R; +import org.chromium.chrome.browser.AlwaysIncognitoLinkInterceptor; import org.chromium.chrome.browser.compositor.bottombar.ephemeraltab.EphemeralTabCoordinator; import org.chromium.chrome.browser.contextmenu.ChromeContextMenuItem.Item; import org.chromium.chrome.browser.contextmenu.ChromeContextMenuPopulator.ContextMenuUma.Action; @@ -376,7 +378,12 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator { if (FirstRunStatus.getFirstRunFlowComplete() && !isEmptyUrl(mParams.getUrl()) && UrlUtilities.isAcceptedScheme(mParams.getUrl())) { if (mMode == ContextMenuMode.NORMAL) { - linkGroup.add(createListItem(Item.OPEN_IN_NEW_TAB)); + if (ContextUtils.getAppSharedPreferences().getBoolean(AlwaysIncognitoLinkInterceptor.PREF_ALWAYS_INCOGNITO, false) + && !mItemDelegate.isIncognito()) { + // disallow open in new tab + } else + linkGroup.add(createListItem(Item.OPEN_IN_NEW_TAB)); + if (!mItemDelegate.isIncognito() && mItemDelegate.isIncognitoSupported()) { linkGroup.add(createListItem(Item.OPEN_IN_INCOGNITO_TAB)); } diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java --- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java @@ -48,6 +48,9 @@ import org.chromium.components.browser_ui.widget.TintedDrawable; import org.chromium.components.embedder_support.util.UrlConstants; import org.chromium.device.mojom.ScreenOrientationLockType; +import org.chromium.base.ContextUtils; +import org.chromium.chrome.browser.AlwaysIncognitoLinkInterceptor; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -796,7 +799,7 @@ public class CustomTabIntentDataProvider extends BrowserServicesIntentDataProvid @Override public boolean isIncognito() { - return false; + return ContextUtils.getAppSharedPreferences().getBoolean(AlwaysIncognitoLinkInterceptor.PREF_ALWAYS_INCOGNITO, false); } @Nullable diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/StartupTabPreloader.java b/chrome/android/java/src/org/chromium/chrome/browser/init/StartupTabPreloader.java --- a/chrome/android/java/src/org/chromium/chrome/browser/init/StartupTabPreloader.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/init/StartupTabPreloader.java @@ -38,6 +38,9 @@ import org.chromium.ui.base.PageTransition; import org.chromium.ui.base.WindowAndroid; import org.chromium.url.GURL; +import org.chromium.base.ContextUtils; +import org.chromium.chrome.browser.AlwaysIncognitoLinkInterceptor; + /** * This class attempts to preload the tab if the url is known from the intent when the profile * is created. This is done to improve startup latency. @@ -195,9 +198,11 @@ public class StartupTabPreloader implements ProfileManager.Observer, Destroyable Intent intent = mIntentSupplier.get(); GURL url = UrlFormatter.fixupUrl(getUrlFromIntent(intent)); + boolean isIncognito = ContextUtils.getAppSharedPreferences().getBoolean(AlwaysIncognitoLinkInterceptor.PREF_ALWAYS_INCOGNITO, false); + ChromeTabCreator chromeTabCreator = - (ChromeTabCreator) mTabCreatorManager.getTabCreator(false); - WebContents webContents = WebContentsFactory.createWebContents(false, false); + (ChromeTabCreator) mTabCreatorManager.getTabCreator(isIncognito); + WebContents webContents = WebContentsFactory.createWebContents(isIncognito, false); mLoadUrlParams = new LoadUrlParams(url.getValidSpecOrEmpty()); String referrer = IntentHandler.getReferrerUrlIncludingExtraHeaders(intent); @@ -211,7 +216,7 @@ public class StartupTabPreloader implements ProfileManager.Observer, Destroyable // Create a detached tab, but don't add it to the tab model yet. We'll do that // later if the loadUrlParams etc... match. mTab = TabBuilder.createLiveTab(false) - .setIncognito(false) + .setIncognito(isIncognito) .setLaunchType(TabLaunchType.FROM_EXTERNAL_APP) .setWindow(mWindowAndroid) .setWebContents(webContents) diff --git a/chrome/android/java/src/org/chromium/chrome/browser/privacy/settings/PrivacySettings.java b/chrome/android/java/src/org/chromium/chrome/browser/privacy/settings/PrivacySettings.java --- a/chrome/android/java/src/org/chromium/chrome/browser/privacy/settings/PrivacySettings.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/privacy/settings/PrivacySettings.java @@ -46,9 +46,11 @@ public class PrivacySettings private static final String PREF_SECURE_DNS = "secure_dns"; private static final String PREF_DO_NOT_TRACK = "do_not_track"; private static final String PREF_CLEAR_BROWSING_DATA = "clear_browsing_data"; + private static final String PREF_ALWAYS_INCOGNITO = "always_incognito"; private static final String[] NEW_PRIVACY_PREFERENCE_ORDER = {PREF_CLEAR_BROWSING_DATA, PREF_CAN_MAKE_PAYMENT, PREF_NETWORK_PREDICTIONS, - PREF_SECURE_DNS, PREF_DO_NOT_TRACK + PREF_SECURE_DNS, PREF_DO_NOT_TRACK, + PREF_ALWAYS_INCOGNITO }; private ManagedPreferenceDelegate mManagedPreferenceDelegate; diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java --- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java @@ -39,6 +39,10 @@ import org.chromium.ui.base.PageTransition; import org.chromium.ui.base.WindowAndroid; import org.chromium.url.GURL; +import org.chromium.base.ContextUtils; +import org.chromium.chrome.browser.AlwaysIncognitoLinkInterceptor; +import org.chromium.chrome.browser.tab.TabObserver; + /** * This class creates various kinds of new tabs and adds them to the right {@link TabModel}. */ @@ -60,6 +64,7 @@ public class ChromeTabCreator extends TabCreator { private final ChromeActivity mActivity; private final StartupTabPreloader mStartupTabPreloader; private final boolean mIncognito; + private final TabObserver mExtraLogic; private WindowAndroid mNativeWindow; private TabModel mTabModel; @@ -78,6 +83,10 @@ public class ChromeTabCreator extends TabCreator { mNativeWindow = nativeWindow; mTabDelegateFactorySupplier = tabDelegateFactory; mIncognito = incognito; + if (!mIncognito) + mExtraLogic = new AlwaysIncognitoLinkInterceptor(ContextUtils.getAppSharedPreferences()); + else + mExtraLogic = null; mOverviewNTPCreator = overviewNTPCreator; mAsyncTabParamsManager = asyncTabParamsManager; } @@ -231,6 +240,8 @@ public class ChromeTabCreator extends TabCreator { if (creationState == TabCreationState.LIVE_IN_FOREGROUND && !openInForeground) { creationState = TabCreationState.LIVE_IN_BACKGROUND; } + if (mExtraLogic != null) + tab.addObserver(mExtraLogic); mTabModel.addTab(tab, position, type, creationState); return tab; } finally { @@ -265,6 +276,8 @@ public class ChromeTabCreator extends TabCreator { @TabCreationState int creationState = openInForeground ? TabCreationState.LIVE_IN_FOREGROUND : TabCreationState.LIVE_IN_BACKGROUND; + if (mExtraLogic != null) + tab.addObserver(mExtraLogic); mTabModel.addTab(tab, position, type, creationState); return true; } @@ -308,7 +321,6 @@ public class ChromeTabCreator extends TabCreator { // TODO(crbug.com/1081924): Clean up the launches from SearchActivity/Chrome. public Tab launchUrlFromExternalApp(String url, String referer, String headers, String appId, boolean forceNewTab, Intent intent, long intentTimestamp) { - assert !mIncognito; boolean isLaunchedFromChrome = TextUtils.equals(appId, mActivity.getPackageName()); if (forceNewTab && !isLaunchedFromChrome) { @@ -417,6 +429,8 @@ public class ChromeTabCreator extends TabCreator { .setSerializedCriticalPersistedTabData(serializedCriticalPersistedTabData) .build(); } + if (mExtraLogic != null) + tab.addObserver(mExtraLogic); if (state.isIncognito() != mIncognito) { throw new IllegalStateException("Incognito state mismatch. TabState: " diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorBase.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorBase.java --- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorBase.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorBase.java @@ -256,7 +256,15 @@ public abstract class TabModelSelectorBase implements TabModelSelector, Incognit public void markTabStateInitialized() { if (mTabStateInitialized) return; mTabStateInitialized = true; + for (TabModelSelectorObserver listener : mObservers) listener.onTabStateInitialized(); + + if (mStartIncognito) { + // profile is not set in always-incognito mode in TabModelSelectorProfileSupplier + // so force it + selectModel(false); // restore model so next call always set incognito mode + selectModel(true); + } } @Override diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java --- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java @@ -16,6 +16,7 @@ import androidx.annotation.VisibleForTesting; import androidx.core.util.AtomicFile; import org.chromium.base.Callback; +import org.chromium.base.ContextUtils; import org.chromium.base.Log; import org.chromium.base.ObserverList; import org.chromium.base.StreamUtil; @@ -49,6 +50,8 @@ import org.chromium.components.embedder_support.util.UrlUtilities; import org.chromium.content_public.browser.LoadUrlParams; import org.chromium.content_public.browser.UiThreadTaskTraits; +import org.chromium.chrome.browser.AlwaysIncognitoLinkInterceptor; + import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -595,6 +598,13 @@ public class TabPersistentStore extends TabPersister { } } + if (ContextUtils.getAppSharedPreferences().getBoolean(AlwaysIncognitoLinkInterceptor.PREF_ALWAYS_INCOGNITO, false)) { + if (!isIncognito) { + Log.w(TAG, "Failed to restore tab: not in incognito mode."); + return; + } + } + TabModel model = mTabModelSelector.getModel(isIncognito); if (model.isIncognito() != isIncognito) { diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappIntentDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappIntentDataProvider.java --- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappIntentDataProvider.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappIntentDataProvider.java @@ -19,6 +19,9 @@ import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProv import org.chromium.chrome.browser.flags.ActivityType; import org.chromium.components.browser_ui.widget.TintedDrawable; +import org.chromium.base.ContextUtils; +import org.chromium.chrome.browser.AlwaysIncognitoLinkInterceptor; + /** * Stores info about a web app. */ @@ -32,6 +35,8 @@ public class WebappIntentDataProvider extends BrowserServicesIntentDataProvider private final @ActivityType int mActivityType; private final Intent mIntent; + private boolean mIsIncognito = false; + /** * Returns the toolbar color to use if a custom color is not specified by the webapp. */ @@ -51,6 +56,10 @@ public class WebappIntentDataProvider extends BrowserServicesIntentDataProvider mWebappExtras = webappExtras; mWebApkExtras = webApkExtras; mActivityType = (webApkExtras != null) ? ActivityType.WEB_APK : ActivityType.WEBAPP; + + if (ContextUtils.getAppSharedPreferences().getBoolean(AlwaysIncognitoLinkInterceptor.PREF_ALWAYS_INCOGNITO, false)) { + mIsIncognito = true; + } } @Override @@ -138,6 +147,11 @@ public class WebappIntentDataProvider extends BrowserServicesIntentDataProvider return mWebApkExtras; } + @Override + public boolean isIncognito() { + return mIsIncognito; + } + @Override public int getDefaultOrientation() { return mWebappExtras.orientation; diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc --- a/chrome/browser/flags/android/chrome_feature_list.cc +++ b/chrome/browser/flags/android/chrome_feature_list.cc @@ -374,7 +374,7 @@ const base::Feature kCCTExternalLinkHandling{"CCTExternalLinkHandling", base::FEATURE_ENABLED_BY_DEFAULT}; const base::Feature kCCTIncognito{"CCTIncognito", - base::FEATURE_DISABLED_BY_DEFAULT}; + base::FEATURE_ENABLED_BY_DEFAULT}; const base::Feature kCCTPostMessageAPI{"CCTPostMessageAPI", base::FEATURE_ENABLED_BY_DEFAULT}; diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd --- a/chrome/browser/ui/android/strings/android_chrome_strings.grd +++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd @@ -832,6 +832,13 @@ Your Google account may have other forms of browsing history like searches and a Clears history and autocompletions in the address bar. + + + Open links in incognito tabs always + + + Opens links in incognito tabs when you click on new tab or on a link + Clears history and autocompletions in the address bar. Your Google Account may have other forms of browsing history at <link>myactivity.google.com</link>. -- 2.17.1