Commit 090f1f05 authored by Pavel Shmakov's avatar Pavel Shmakov Committed by Commit Bot

Migrate TrustedWebActivityUi and related classes to Dagger, decouple CustomTabActivity from it.

As a first step of simplifying CustomTabActivity, I extract all the code related
to TrustedWebActivityUi and use Dagger to organize dependencies for
TrustedWebActivityUi.

I make use of recently introduced activity lifecycle dispatching: https://crrev.com/c/1283256

Bug: 887926
Change-Id: Ia36a026a7f297e7c0f101189945c44454d12fa6e
Reviewed-on: https://chromium-review.googlesource.com/c/1281102
Commit-Queue: Pavel Shmakov <pshmakov@chromium.org>
Reviewed-by: default avatarYusuf Ozuysal <yusufo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#603122}
parent 78b9bb38
// Copyright 2018 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;
import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
import dagger.Module;
import dagger.Provides;
/**
* Makes entities provided by AppHooks available for injection with Dagger.
* TODO(pshmakov): merge this with Chrome's AppHooksImpl.
*/
@Module
public class AppHooksModule {
@Provides
public static CustomTabsConnection provideCustomTabsConnection() {
return CustomTabsConnection.getInstance();
}
}
......@@ -4,16 +4,23 @@
package org.chromium.chrome.browser.browserservices;
import static org.chromium.chrome.browser.dependency_injection.ChromeCommonQualifiers.APP_CONTEXT;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.text.TextUtils;
import org.chromium.base.Log;
import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import org.chromium.chrome.browser.util.UrlUtilities;
import java.util.HashSet;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Named;
/**
* Takes care of recording that Chrome contains data for the client app in the
* {@link ClientAppDataRegister}. It performs three main duties:
......@@ -25,15 +32,14 @@ import java.util.Set;
* {@link TrustedWebActivityUi}. Having more instances won't effect correctness, but will limit the
* performance benefits of the cache.
* Thread safety: All methods on this class should be called from the same thread.
* Native: The default {@link UrlTransformer} requires native to be loaded.
*/
@ActivityScope
public class ClientAppDataRecorder {
private static final String TAG = "TWAClientAppData";
private final PackageManager mPackageManager;
/** Underlying data register. */
private final ClientAppDataRegister mClientAppDataRegister;
private final UrlTransformer mUrlTransformer;
/**
* Cache so we don't send the same request multiple times. {@link #register} is called on each
......@@ -42,31 +48,18 @@ public class ClientAppDataRecorder {
*/
private final Set<String> mCache = new HashSet<>();
/** Class to allow mocking native calls in unit tests. */
/* package */ static class UrlTransformer {
/* package */ String getDomain(Origin origin) {
return UrlUtilities.getDomainAndRegistry(
origin.toString(), true /* includePrivateRegistries */);
}
}
/** Creates a ClientAppDataRecorder with default dependencies. */
public ClientAppDataRecorder(PackageManager packageManager) {
this(packageManager, new ClientAppDataRegister(), new UrlTransformer());
}
/** Creates a ClientAppDataRecorder providing all dependencies. */
public ClientAppDataRecorder(PackageManager packageManager,
ClientAppDataRegister clientAppDataRegister, UrlTransformer urlTransformer) {
mPackageManager = packageManager;
@Inject
public ClientAppDataRecorder(@Named(APP_CONTEXT) Context context,
ClientAppDataRegister clientAppDataRegister) {
mPackageManager = context.getPackageManager();
mClientAppDataRegister = clientAppDataRegister;
mUrlTransformer = urlTransformer;
}
/**
* Calls {@link ClientAppDataRegister#registerPackageForDomain}, looking up the uid
* and app name for the |packageName|, extracting the domain from the origin and deduplicating
* multiple requests with the same parameters.
* Requires native to be loaded.
*/
/* package */ void register(String packageName, Origin origin) {
if (mCache.contains(combine(packageName, origin))) return;
......@@ -82,7 +75,8 @@ public class ClientAppDataRecorder {
return;
}
String domain = mUrlTransformer.getDomain(origin);
String domain = UrlUtilities.getDomainAndRegistry(
origin.toString(), true /*includePrivateRegistries*/);
Log.d(TAG, "Registering %d (%s) for %s", ai.uid, appLabel, domain);
mClientAppDataRegister.registerPackageForDomain(ai.uid, appLabel, domain);
......
......@@ -14,6 +14,8 @@ import org.chromium.chrome.browser.snackbar.SnackbarManager;
import javax.inject.Inject;
import dagger.Lazy;
/**
* Shows the Trusted Web Activity disclosure when appropriate and records its acceptance.
*
......@@ -26,6 +28,7 @@ public class TrustedWebActivityDisclosure {
// TODO(peconn): Make this package private once TrustedWebActivityUi can be injected.
private final Resources mResources;
private final ChromePreferenceManager mPreferenceManager;
private final Lazy<SnackbarManager> mSnackbarManager;
private boolean mSnackbarShowing;
......@@ -50,30 +53,31 @@ public class TrustedWebActivityDisclosure {
@Inject
/* package */ TrustedWebActivityDisclosure(Resources resources,
ChromePreferenceManager preferenceManager) {
ChromePreferenceManager preferenceManager, Lazy<SnackbarManager> snackbarManager) {
mResources = resources;
mPreferenceManager = preferenceManager;
mSnackbarManager = snackbarManager;
}
/** Dismisses the Snackbar if it is showing. */
/* package */ void dismissSnackbarIfNeeded(SnackbarManager snackbarManager) {
/** Dismisses the disclosure if it is showing. */
/* package */ void dismiss() {
if (!mSnackbarShowing) return;
snackbarManager.dismissSnackbars(mSnackbarController);
mSnackbarManager.get().dismissSnackbars(mSnackbarController);
mSnackbarShowing = false;
}
/** Shows the Snackbar if it is not already showing and hasn't been accepted. */
/* package */ void showSnackbarIfNeeded(SnackbarManager snackbarManager, String packageName) {
/** Shows the disclosure if it is not already showing and hasn't been accepted. */
/* package */ void showIfNeeded(String packageName) {
if (mSnackbarShowing) return;
if (wasSnackbarDismissed(packageName)) return;
if (wasDismissed(packageName)) return;
snackbarManager.showSnackbar(makeRunningInChromeInfobar(packageName));
mSnackbarManager.get().showSnackbar(makeRunningInChromeInfobar(packageName));
mSnackbarShowing = true;
}
/** Has a Snackbar been dismissed for this client package before? */
private boolean wasSnackbarDismissed(String packageName) {
/** Has a disclosure been dismissed for this client package before? */
private boolean wasDismissed(String packageName) {
return mPreferenceManager.hasUserAcceptedTwaDisclosureForPackage(packageName);
}
......
......@@ -4,79 +4,54 @@
package org.chromium.chrome.browser.browserservices;
import android.support.annotation.Nullable;
import android.support.customtabs.CustomTabsService;
import org.chromium.chrome.browser.ActivityTabProvider;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.customtabs.CustomTabBrowserControlsVisibilityDelegate;
import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider;
import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
import org.chromium.chrome.browser.customtabs.TabObserverRegistrar;
import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
import org.chromium.chrome.browser.fullscreen.FullscreenManager;
import org.chromium.chrome.browser.snackbar.SnackbarManager;
import org.chromium.chrome.browser.tab.BrowserControlsVisibilityDelegate;
import org.chromium.chrome.browser.init.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.InflationObserver;
import org.chromium.chrome.browser.lifecycle.NativeInitObserver;
import org.chromium.chrome.browser.lifecycle.PauseResumeWithNativeObserver;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabObserver;
import javax.inject.Inject;
/**
* Class to handle the state and logic for CustomTabActivity to do with Trusted Web Activities.
*
* Lifecycle: There should be a 1-1 relationship between this class and
* {@link org.chromium.chrome.browser.customtabs.CustomTabActivity}.
* Thread safety: All methods on this class should be called on the UI thread.
*/
public class TrustedWebActivityUi {
// TODO(peconn): Convert this class to use dependency injection, when you do clean up
// CustomTabActivityComponent and TrustedWebActivityDisclosure.
@ActivityScope
public class TrustedWebActivityUi
implements InflationObserver, PauseResumeWithNativeObserver, NativeInitObserver {
/** The Digital Asset Link relationship used for Trusted Web Activities. */
private final static int RELATIONSHIP = CustomTabsService.RELATION_HANDLE_ALL_URLS;
private final TrustedWebActivityUiDelegate mDelegate;
private final TrustedWebActivityDisclosure mDisclosure;
private final TrustedWebActivityOpenTimeRecorder mOpenTimeRecorder =
new TrustedWebActivityOpenTimeRecorder();
private final ChromeFullscreenManager mFullscreenManager;
private final ClientAppDataRecorder mClientAppDataRecorder;
private final CustomTabsConnection mCustomTabsConnection;
private final CustomTabIntentDataProvider mIntentDataProvider;
private final ActivityTabProvider mActivityTabProvider;
private final CustomTabBrowserControlsVisibilityDelegate mControlsVisibilityDelegate;
private boolean mInTrustedWebActivity = true;
private int mControlsHidingToken = FullscreenManager.INVALID_TOKEN;
/**
* A delegate for embedders to implement to inject information into this class. The reason for
* using this instead of passing these dependencies into the constructor is because they may not
* be available at construction.
*/
public interface TrustedWebActivityUiDelegate {
/**
* Provides a {@link ChromeFullscreenManager} that is used to control visibility of the
* toolbar.
*/
ChromeFullscreenManager getFullscreenManager();
/**
* Provides the package name of the client for verification.
*/
String getClientPackageName();
/**
* Gives the SnackbarManager to use to display the disclosure.
*/
SnackbarManager getSnackbarManager();
}
/**
* A {@link BrowserControlsVisibilityDelegate} that disallows showing the Browser Controls when
* we are in a Trusted Web Activity.
*/
private final BrowserControlsVisibilityDelegate mInTwaVisibilityDelegate =
new BrowserControlsVisibilityDelegate() {
@Override
public boolean canShowBrowserControls() {
return !mInTrustedWebActivity;
}
@Override
public boolean canAutoHideBrowserControls() {
return true;
}
};
/** A {@link TabObserver} that checks whether we are on a verified Origin on page navigation. */
private final TabObserver mVerifyOnPageLoadObserver = new EmptyTabObserver() {
@Override
......@@ -86,7 +61,7 @@ public class TrustedWebActivityUi {
int httpStatusCode) {
if (!hasCommitted || !isInMainFrame) return;
String packageName = mDelegate.getClientPackageName();
String packageName = getClientPackageName();
assert packageName != null;
// This doesn't perform a network request or attempt new verification - it checks to
......@@ -95,58 +70,57 @@ public class TrustedWebActivityUi {
boolean verified =
OriginVerifier.isValidOrigin(packageName, origin, RELATIONSHIP);
if (verified) registerClientAppData(packageName, origin);
setTrustedWebActivityMode(verified, tab);
setTrustedWebActivityMode(verified);
}
};
/** Creates a TrustedWebActivityUi, providing a delegate from the embedder. */
public TrustedWebActivityUi(TrustedWebActivityUiDelegate delegate,
TrustedWebActivityDisclosure disclosure, ClientAppDataRecorder clientAppDataRecorder) {
mDelegate = delegate;
@Inject
public TrustedWebActivityUi(TrustedWebActivityDisclosure disclosure,
ChromeFullscreenManager fullscreenManager, ClientAppDataRecorder clientAppDataRecorder,
CustomTabIntentDataProvider intentDataProvider,
CustomTabsConnection customTabsConnection,
ActivityLifecycleDispatcher lifecycleDispatcher,
TabObserverRegistrar tabObserverRegistrar, ActivityTabProvider activityTabProvider,
CustomTabBrowserControlsVisibilityDelegate controlsVisibilityDelegate) {
mFullscreenManager = fullscreenManager;
mClientAppDataRecorder = clientAppDataRecorder;
mDisclosure = disclosure;
mCustomTabsConnection = customTabsConnection;
mIntentDataProvider = intentDataProvider;
mActivityTabProvider = activityTabProvider;
mControlsVisibilityDelegate = controlsVisibilityDelegate;
tabObserverRegistrar.registerTabObserver(mVerifyOnPageLoadObserver);
lifecycleDispatcher.register(this);
}
/**
* Gets a {@link BrowserControlsVisibilityDelegate} that will hide/show the Custom Tab toolbar
* on verification/leaving the verified origin.
*/
public BrowserControlsVisibilityDelegate getBrowserControlsVisibilityDelegate() {
return mInTwaVisibilityDelegate;
}
/**
* Gets a {@link TabObserver} that watches for navigations and sets whether we are in a Trusted
* Web Activity accordingly.
*/
public TabObserver getTabObserver() {
return mVerifyOnPageLoadObserver;
@Nullable
private String getClientPackageName() {
return mCustomTabsConnection.getClientPackageNameForSession(
mIntentDataProvider.getSession());
}
/**
* Shows the disclosure Snackbar if needed on the first Tab. Subsequent navigations will update
* the disclosure state automatically.
*/
public void initialShowSnackbarIfNeeded() {
assert mDelegate.getSnackbarManager() != null;
assert mDelegate.getClientPackageName() != null;
private void initialShowSnackbarIfNeeded() {
String packageName = getClientPackageName();
assert packageName != null;
// If we have left Trusted Web Activity mode (through onDidFinishNavigation), we don't need
// to show the Snackbar.
if (!mInTrustedWebActivity) return;
mDisclosure.showSnackbarIfNeeded(mDelegate.getSnackbarManager(),
mDelegate.getClientPackageName());
mDisclosure.showIfNeeded(packageName);
}
/**
* Perform verification for the URL that the CustomTabActivity starts on.
*/
public void attemptVerificationForInitialUrl(String url, Tab tab) {
assert mDelegate.getClientPackageName() != null;
String packageName = getClientPackageName();
assert packageName != null;
String packageName = mDelegate.getClientPackageName();
Origin origin = new Origin(url);
new OriginVerifier((packageName2, origin2, verified, online) -> {
......@@ -154,55 +128,64 @@ public class TrustedWebActivityUi {
BrowserServicesMetrics.recordTwaOpened();
if (verified) registerClientAppData(packageName, origin);
setTrustedWebActivityMode(verified, tab);
setTrustedWebActivityMode(verified);
}, packageName, RELATIONSHIP).start(origin);
}
@Override
public void onPreInflationStartup() {}
@Override
public void onPostInflationStartup() {
// TODO(pshmakov): Move this over to LifecycleObserver or something similar once available.
if (mInTrustedWebActivity) {
// Hide Android controls as soon as they are inflated.
mControlsHidingToken = mDelegate.getFullscreenManager().hideAndroidControls();
mControlsHidingToken = mFullscreenManager.hideAndroidControls();
mControlsVisibilityDelegate.setTrustedWebActivityMode(true);
}
}
/** Notify (for metrics purposes) that the TWA has been resumed. */
public void onResume() {
// TODO(peconn): Move this over to LifecycleObserver or something similar once available.
@Override
public void onResumeWithNative() {
mOpenTimeRecorder.onResume();
}
/** Notify (for metrics purposes) that the TWA has been paused. */
public void onPause() {
// TODO(peconn): Move this over to LifecycleObserver or something similar once available.
@Override
public void onPauseWithNative() {
mOpenTimeRecorder.onPause();
}
@Override
public void onFinishNativeInitialization() {
if (!ChromeFeatureList.isEnabled(ChromeFeatureList.TRUSTED_WEB_ACTIVITY_POST_MESSAGE)) {
mCustomTabsConnection.resetPostMessageHandlerForSession(
mIntentDataProvider.getSession(), null);
}
attemptVerificationForInitialUrl(
mIntentDataProvider.getUrlToLoad(), mActivityTabProvider.getActivityTab());
initialShowSnackbarIfNeeded();
}
/**
* Updates the UI appropriately for whether or not Trusted Web Activity mode is enabled.
*/
private void setTrustedWebActivityMode(boolean enabled, Tab tab) {
private void setTrustedWebActivityMode(boolean enabled) {
if (mInTrustedWebActivity == enabled) return;
mInTrustedWebActivity = enabled;
ChromeFullscreenManager fullscreenManager = mDelegate.getFullscreenManager();
mControlsVisibilityDelegate.setTrustedWebActivityMode(mInTrustedWebActivity);
if (enabled) {
mControlsHidingToken =
fullscreenManager.hideAndroidControlsAndClearOldToken(mControlsHidingToken);
mDisclosure.showSnackbarIfNeeded(mDelegate.getSnackbarManager(),
mDelegate.getClientPackageName());
mFullscreenManager.hideAndroidControlsAndClearOldToken(mControlsHidingToken);
mDisclosure.showIfNeeded(getClientPackageName());
} else {
fullscreenManager.releaseAndroidControlsHidingToken(mControlsHidingToken);
mFullscreenManager.releaseAndroidControlsHidingToken(mControlsHidingToken);
// Force showing the controls for a bit when leaving Trusted Web Activity mode.
fullscreenManager.getBrowserVisibilityDelegate().showControlsTransient();
mDisclosure.dismissSnackbarIfNeeded(mDelegate.getSnackbarManager());
mFullscreenManager.getBrowserVisibilityDelegate().showControlsTransient();
mDisclosure.dismiss();
}
// Apply the change in the BrowserControlsVisibilityDelegate
tab.updateFullscreenEnabledState();
}
/**
......
......@@ -58,29 +58,25 @@ import org.chromium.chrome.browser.IntentHandler.ExternalAppId;
import org.chromium.chrome.browser.KeyboardShortcuts;
import org.chromium.chrome.browser.LaunchIntentDispatcher;
import org.chromium.chrome.browser.ServiceTabLauncher;
import org.chromium.chrome.browser.UrlConstants;
import org.chromium.chrome.browser.WarmupManager;
import org.chromium.chrome.browser.WebContentsFactory;
import org.chromium.chrome.browser.appmenu.AppMenuPropertiesDelegate;
import org.chromium.chrome.browser.autofill_assistant.AutofillAssistantUiController;
import org.chromium.chrome.browser.browserservices.BrowserSessionContentHandler;
import org.chromium.chrome.browser.browserservices.BrowserSessionContentUtils;
import org.chromium.chrome.browser.browserservices.ClientAppDataRecorder;
import org.chromium.chrome.browser.browserservices.TrustedWebActivityUi;
import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
import org.chromium.chrome.browser.contextual_suggestions.ContextualSuggestionsModule;
import org.chromium.chrome.browser.customtabs.dependency_injection.CustomTabActivityComponent;
import org.chromium.chrome.browser.customtabs.dependency_injection.CustomTabActivityModule;
import org.chromium.chrome.browser.customtabs.dynamicmodule.ActivityDelegate;
import org.chromium.chrome.browser.customtabs.dynamicmodule.ActivityHostImpl;
import org.chromium.chrome.browser.customtabs.dynamicmodule.ModuleEntryPoint;
import org.chromium.chrome.browser.customtabs.dynamicmodule.ModuleLoader;
import org.chromium.chrome.browser.customtabs.dynamicmodule.ModuleMetrics;
import org.chromium.chrome.browser.dependency_injection.ChromeActivityCommonsModule;
import org.chromium.chrome.browser.dependency_injection.CustomTabActivityComponent;
import org.chromium.chrome.browser.externalauth.ExternalAuthUtils;
import org.chromium.chrome.browser.externalnav.ExternalNavigationDelegateImpl;
import org.chromium.chrome.browser.firstrun.FirstRunSignInProcessor;
import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
import org.chromium.chrome.browser.fullscreen.ComposedBrowserControlsVisibilityDelegate;
import org.chromium.chrome.browser.gsa.GSAState;
import org.chromium.chrome.browser.incognito.IncognitoTabHost;
import org.chromium.chrome.browser.incognito.IncognitoTabHostRegistry;
......@@ -88,8 +84,6 @@ import org.chromium.chrome.browser.infobar.InfoBarContainer;
import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
import org.chromium.chrome.browser.page_info.PageInfoController;
import org.chromium.chrome.browser.rappor.RapporServiceBridge;
import org.chromium.chrome.browser.snackbar.SnackbarManager;
import org.chromium.chrome.browser.tab.BrowserControlsVisibilityDelegate;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabDelegateFactory;
......@@ -175,8 +169,6 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
/** Adds and removes observers from tabs when needed. */
private final TabObserverRegistrar mTabObserverRegistrar = new TabObserverRegistrar();
private @Nullable TrustedWebActivityUi mTrustedWebActivityUi;
private String mSpeculatedUrl;
private boolean mUsingHiddenTab;
......@@ -276,7 +268,7 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
super.preInflationStartup();
if (mIntentDataProvider.isTrustedWebActivity()) {
mTrustedWebActivityUi = createTrustedWebActivityUi();
getComponent().resolveTrustedWebActivityUi();
}
mSession = mIntentDataProvider.getSession();
......@@ -288,7 +280,7 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
mMainTab = getHiddenTab();
if (mMainTab == null) mMainTab = createMainTab();
mIsFirstLoad = true;
loadUrlInTab(mMainTab, new LoadUrlParams(getUrlToLoad()),
loadUrlInTab(mMainTab, new LoadUrlParams(mIntentDataProvider.getUrlToLoad()),
IntentHandler.getTimestampFromIntent(getIntent()));
mHasCreatedTabEarly = true;
}
......@@ -309,28 +301,6 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
}
}
private TrustedWebActivityUi createTrustedWebActivityUi() {
return new TrustedWebActivityUi(
new TrustedWebActivityUi.TrustedWebActivityUiDelegate() {
@Override
public ChromeFullscreenManager getFullscreenManager() {
return CustomTabActivity.this.getFullscreenManager();
}
@Override
public String getClientPackageName() {
return mConnection != null
? mConnection.getClientPackageNameForSession(mSession) : null;
}
@Override
public SnackbarManager getSnackbarManager() {
return CustomTabActivity.this.getSnackbarManager();
}
}, getComponent().resolveTrustedWebActivityDisclosure(),
new ClientAppDataRecorder(getPackageManager()));
}
/**
* Dynamically loads a module using the component name specified in the intent if the feature is
* enabled, the package is Google-signed, and it is not loaded yet.
......@@ -476,10 +446,6 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
getFullscreenManager());
mBottomBarDelegate.showBottomBarIfNecessary();
mTopBarDelegate = new CustomTabTopBarDelegate(this);
if (mTrustedWebActivityUi != null) {
mTrustedWebActivityUi.onPostInflationStartup();
}
}
@Override
......@@ -505,16 +471,9 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
}
private CustomTabDelegateFactory createCustomTabDelegateFactory() {
BrowserControlsVisibilityDelegate delegate =
getFullscreenManager().getBrowserVisibilityDelegate();
if (mTrustedWebActivityUi != null) {
delegate = new ComposedBrowserControlsVisibilityDelegate(delegate,
mTrustedWebActivityUi.getBrowserControlsVisibilityDelegate()
);
}
return new CustomTabDelegateFactory(mIntentDataProvider.shouldEnableUrlBarHiding(),
mIntentDataProvider.isOpenedByChrome(), delegate);
mIntentDataProvider.isOpenedByChrome(),
getComponent().resolveControlsVisibilityDelegate());
}
@Override
......@@ -657,7 +616,7 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
recordClientPackageName();
mConnection.showSignInToastIfNecessary(mSession, getIntent());
String url = getUrlToLoad();
String url = mIntentDataProvider.getUrlToLoad();
String packageName = mConnection.getClientPackageNameForSession(mSession);
if (TextUtils.isEmpty(packageName)) {
packageName = mConnection.extractCreatorPackage(getIntent());
......@@ -684,15 +643,6 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
mAutofillAssistantUiController = new AutofillAssistantUiController(this);
}
if (mTrustedWebActivityUi != null) {
if (!ChromeFeatureList.isEnabled(ChromeFeatureList.TRUSTED_WEB_ACTIVITY_POST_MESSAGE)) {
mConnection.resetPostMessageHandlerForSession(mSession, null);
}
mTrustedWebActivityUi.attemptVerificationForInitialUrl(url, getActivityTab());
mTrustedWebActivityUi.initialShowSnackbarIfNeeded();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && useSeparateTask()) {
mTaskDescriptionHelper = new ActivityTabTaskDescriptionHelper(this,
ApiCompatibilityUtils.getColor(getResources(), R.color.default_primary_color));
......@@ -706,7 +656,7 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
* with additional initialization logic.
*/
private Tab getHiddenTab() {
String url = getUrlToLoad();
String url = mIntentDataProvider.getUrlToLoad();
String referrerUrl = mConnection.getReferrer(mSession, getIntent());
Tab tab = mConnection.takeHiddenTab(mSession, url, referrerUrl);
mUsingHiddenTab = tab != null;
......@@ -800,9 +750,6 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
mTabObserverRegistrar.registerTabObserver(mTabObserver);
mTabObserverRegistrar.registerTabObserver(mTabNavigationEventObserver);
if (mTrustedWebActivityUi != null) {
mTabObserverRegistrar.registerTabObserver(mTrustedWebActivityUi.getTabObserver());
}
mTabObserverRegistrar.registerPageLoadMetricsObserver(
new PageLoadMetricsObserver(mConnection, mSession, tab));
mTabObserverRegistrar.registerPageLoadMetricsObserver(
......@@ -929,10 +876,11 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
} else {
SharedPreferences preferences = ContextUtils.getAppSharedPreferences();
String lastUrl = preferences.getString(LAST_URL_PREF, null);
if (lastUrl != null && lastUrl.equals(getUrlToLoad())) {
String urlToLoad = mIntentDataProvider.getUrlToLoad();
if (lastUrl != null && lastUrl.equals(urlToLoad)) {
RecordUserAction.record("CustomTabsMenuOpenSameUrl");
} else {
preferences.edit().putString(LAST_URL_PREF, getUrlToLoad()).apply();
preferences.edit().putString(LAST_URL_PREF, urlToLoad).apply();
}
if (mIntentDataProvider.isOpenedByChrome()) {
......@@ -956,8 +904,6 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
} else if (isModuleLoading()) {
mModuleOnResumePending = true;
}
if (mTrustedWebActivityUi != null) mTrustedWebActivityUi.onResume();
}
@Override
......@@ -968,8 +914,6 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
}
if (mModuleActivityDelegate != null) mModuleActivityDelegate.onPause();
mModuleOnResumePending = false;
if (mTrustedWebActivityUi != null) mTrustedWebActivityUi.onPause();
}
@Override
......@@ -1027,7 +971,7 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
*/
private void loadUrlInTab(final Tab tab, final LoadUrlParams params, long timeStamp) {
Intent intent = getIntent();
String url = getUrlToLoad();
String url = mIntentDataProvider.getUrlToLoad();
// Caching isFirstLoad value to deal with multiple return points.
boolean isFirstLoad = mIsFirstLoad;
......@@ -1388,7 +1332,7 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
if (DomDistillerUrlUtils.isDistilledPage(url)) {
url = DomDistillerUrlUtils.getOriginalUrlFromDistillerUrl(url);
}
if (TextUtils.isEmpty(url)) url = getUrlToLoad();
if (TextUtils.isEmpty(url)) url = mIntentDataProvider.getUrlToLoad();
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(LaunchIntentDispatcher.EXTRA_IS_ALLOWED_TO_RETURN_TO_PARENT, false);
......@@ -1435,32 +1379,6 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
return true;
}
/**
* @return The URL that should be used from this intent. If it is a WebLite url, it may be
* overridden if the Data Reduction Proxy is using Lo-Fi previews.
*/
private String getUrlToLoad() {
String url = IntentHandler.getUrlFromIntent(getIntent());
// Intents fired for media viewers have an additional file:// URI passed along so that the
// tab can display the actual filename to the user when it is loaded.
if (mIntentDataProvider.isMediaViewer()) {
String mediaViewerUrl = mIntentDataProvider.getMediaViewerUrl();
if (!TextUtils.isEmpty(mediaViewerUrl)) {
Uri mediaViewerUri = Uri.parse(mediaViewerUrl);
if (UrlConstants.FILE_SCHEME.equals(mediaViewerUri.getScheme())) {
url = mediaViewerUrl;
}
}
}
if (!TextUtils.isEmpty(url)) {
url = DataReductionProxySettings.getInstance().maybeRewriteWebliteUrl(url);
}
return url;
}
/** Sets the initial background color for the Tab, shown before the page content is ready. */
private void prepareTabBackground(final Tab tab) {
if (!IntentHandler.notSecureIsIntentChromeOrFirstParty(getIntent())) return;
......@@ -1580,7 +1498,9 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
@Override
protected CustomTabActivityComponent createComponent(ChromeActivityCommonsModule commonsModule,
ContextualSuggestionsModule contextualSuggestionsModule) {
return ChromeApplication.getComponent().createCustomTabActivityComponent(commonsModule,
contextualSuggestionsModule);
CustomTabActivityModule customTabsModule =
new CustomTabActivityModule(mIntentDataProvider, mTabObserverRegistrar);
return ChromeApplication.getComponent().createCustomTabActivityComponent(
commonsModule, contextualSuggestionsModule, customTabsModule);
}
}
// Copyright 2018 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.customtabs;
import org.chromium.chrome.browser.ActivityTabProvider;
import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
import org.chromium.chrome.browser.tab.BrowserControlsVisibilityDelegate;
import org.chromium.chrome.browser.tab.Tab;
import javax.inject.Inject;
/**
* Implementation of {@link BrowserControlsVisibilityDelegate} for custom tabs.
*/
@ActivityScope
public class CustomTabBrowserControlsVisibilityDelegate
implements BrowserControlsVisibilityDelegate {
private final BrowserControlsVisibilityDelegate mFullscreenManagerDelegate;
private final ActivityTabProvider mTabProvider;
private boolean mIsInTwaMode;
@Inject
public CustomTabBrowserControlsVisibilityDelegate(
ChromeFullscreenManager fullscreenManager, ActivityTabProvider tabProvider) {
mFullscreenManagerDelegate = fullscreenManager.getBrowserVisibilityDelegate();
mTabProvider = tabProvider;
}
/**
* Sets trusted web activity mode. In trusted web activity mode browser controls should be
* hidden.
*/
public void setTrustedWebActivityMode(boolean isInTwaMode) {
if (mIsInTwaMode == isInTwaMode) {
return;
}
mIsInTwaMode = isInTwaMode;
Tab activeTab = mTabProvider.getActivityTab();
if (activeTab != null) {
activeTab.updateFullscreenEnabledState();
}
}
@Override
public boolean canShowBrowserControls() {
return !mIsInTwaMode && mFullscreenManagerDelegate.canShowBrowserControls();
}
@Override
public boolean canAutoHideBrowserControls() {
return mFullscreenManagerDelegate.canAutoHideBrowserControls();
}
}
......@@ -35,8 +35,10 @@ import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.ChromeVersionInfo;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.UrlConstants;
import org.chromium.chrome.browser.browserservices.BrowserSessionDataProvider;
import org.chromium.chrome.browser.externalauth.ExternalAuthUtils;
import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
import org.chromium.chrome.browser.util.ColorUtils;
import org.chromium.chrome.browser.util.IntentUtils;
import org.chromium.chrome.browser.widget.TintedDrawable;
......@@ -143,6 +145,8 @@ public class CustomTabIntentDataProvider extends BrowserSessionDataProvider {
+ "To make locally-built Chrome a first-party app, sign with release-test "
+ "signing keys and run on userdebug devices. See use_signing_keys GN arg.";
private final Intent mIntent;
private final int mUiType;
private final int mTitleVisibilityState;
private final String mMediaViewerUrl;
......@@ -156,6 +160,8 @@ public class CustomTabIntentDataProvider extends BrowserSessionDataProvider {
@Nullable
private final ComponentName mModuleComponentName;
private final boolean mIsIncognito;
@Nullable
private String mUrlToLoad;
private int mToolbarColor;
private int mBottomBarColor;
......@@ -200,6 +206,7 @@ public class CustomTabIntentDataProvider extends BrowserSessionDataProvider {
super(intent);
if (intent == null) assert false;
mIntent = intent;
mIsOpenedByChrome = IntentUtils.safeGetBooleanExtra(
intent, EXTRA_IS_OPENED_BY_CHROME, false);
......@@ -407,6 +414,40 @@ public class CustomTabIntentDataProvider extends BrowserSessionDataProvider {
return color | 0xFF000000;
}
private String resolveUrlToLoad(Intent intent) {
String url = IntentHandler.getUrlFromIntent(intent);
// Intents fired for media viewers have an additional file:// URI passed along so that the
// tab can display the actual filename to the user when it is loaded.
if (isMediaViewer()) {
String mediaViewerUrl = getMediaViewerUrl();
if (!TextUtils.isEmpty(mediaViewerUrl)) {
Uri mediaViewerUri = Uri.parse(mediaViewerUrl);
if (UrlConstants.FILE_SCHEME.equals(mediaViewerUri.getScheme())) {
url = mediaViewerUrl;
}
}
}
if (!TextUtils.isEmpty(url)) {
url = DataReductionProxySettings.getInstance().maybeRewriteWebliteUrl(url);
}
return url;
}
/**
* @return The URL that should be used from this intent. If it is a WebLite url, it may be
* overridden if the Data Reduction Proxy is using Lo-Fi previews.
* Must be called only after native has loaded.
*/
public String getUrlToLoad() {
if (mUrlToLoad == null) {
mUrlToLoad = resolveUrlToLoad(mIntent);
}
return mUrlToLoad;
}
/**
* @return Whether url bar hiding should be enabled in the custom tab. Default is false.
* It should be impossible to hide the url bar when the tab is opened for Payment Request.
......
......@@ -2,10 +2,14 @@
// 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.dependency_injection;
package org.chromium.chrome.browser.customtabs.dependency_injection;
import org.chromium.chrome.browser.browserservices.TrustedWebActivityDisclosure;
import org.chromium.chrome.browser.browserservices.TrustedWebActivityUi;
import org.chromium.chrome.browser.contextual_suggestions.ContextualSuggestionsModule;
import org.chromium.chrome.browser.customtabs.CustomTabBrowserControlsVisibilityDelegate;
import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import org.chromium.chrome.browser.dependency_injection.ChromeActivityCommonsModule;
import org.chromium.chrome.browser.dependency_injection.ChromeActivityComponent;
import dagger.Subcomponent;
......@@ -13,8 +17,10 @@ import dagger.Subcomponent;
* Activity-scoped component associated with
* {@link org.chromium.chrome.browser.customtabs.CustomTabActivity}.
*/
@Subcomponent(modules = {ChromeActivityCommonsModule.class, ContextualSuggestionsModule.class})
@Subcomponent(modules = {ChromeActivityCommonsModule.class, ContextualSuggestionsModule.class,
CustomTabActivityModule.class})
@ActivityScope
public interface CustomTabActivityComponent extends ChromeActivityComponent {
TrustedWebActivityDisclosure resolveTrustedWebActivityDisclosure();
TrustedWebActivityUi resolveTrustedWebActivityUi();
CustomTabBrowserControlsVisibilityDelegate resolveControlsVisibilityDelegate();
}
// Copyright 2018 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.customtabs.dependency_injection;
import org.chromium.chrome.browser.browserservices.ClientAppDataRegister;
import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider;
import org.chromium.chrome.browser.customtabs.TabObserverRegistrar;
import dagger.Module;
import dagger.Provides;
/**
* Module for custom tab specific bindings.
*/
@Module
public class CustomTabActivityModule {
private final CustomTabIntentDataProvider mIntentDataProvider;
private final TabObserverRegistrar mTabObserverRegistrar;
public CustomTabActivityModule(CustomTabIntentDataProvider intentDataProvider,
TabObserverRegistrar tabObserverRegistrar) {
mIntentDataProvider = intentDataProvider;
mTabObserverRegistrar = tabObserverRegistrar;
}
@Provides
public CustomTabIntentDataProvider provideIntentDataProvider() {
return mIntentDataProvider;
}
@Provides
public ClientAppDataRegister provideClientAppDataRegister() {
return new ClientAppDataRegister();
}
@Provides
public TabObserverRegistrar provideTabObserverRegistrar() {
return mTabObserverRegistrar;
}
}
......@@ -4,16 +4,23 @@
package org.chromium.chrome.browser.dependency_injection;
import static org.chromium.chrome.browser.dependency_injection.ChromeCommonQualifiers.ACTIVITY_CONTEXT;
import android.content.Context;
import android.content.res.Resources;
import org.chromium.chrome.browser.ActivityTabProvider;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
import org.chromium.chrome.browser.init.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.snackbar.SnackbarManager;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.toolbar.ToolbarManager;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
import javax.inject.Named;
import dagger.Module;
import dagger.Provides;
......@@ -64,8 +71,14 @@ public class ChromeActivityCommonsModule {
@Provides
public ChromeActivity provideChromeActivity() {
// Ideally this should provide only the Context instead of specific activity, but currently
// a lot of code is coupled specifically to ChromeActivity.
// Ideally providing Context should be enough, but currently a lot of code is coupled
// specifically to ChromeActivity.
return mActivity;
}
@Provides
@Named(ACTIVITY_CONTEXT)
public Context provideContext() {
return mActivity;
}
......@@ -78,4 +91,14 @@ public class ChromeActivityCommonsModule {
public ActivityLifecycleDispatcher provideLifecycleDispatcher() {
return mLifecycleDispatcher;
}
@Provides
public SnackbarManager provideSnackbarManager() {
return mActivity.getSnackbarManager();
}
@Provides
public ActivityTabProvider provideActivityTabProvider() {
return mActivity.getActivityTabProvider();
}
}
......@@ -4,8 +4,11 @@
package org.chromium.chrome.browser.dependency_injection;
import org.chromium.chrome.browser.AppHooksModule;
import org.chromium.chrome.browser.contextual_suggestions.ContextualSuggestionsModule;
import org.chromium.chrome.browser.contextual_suggestions.EnabledStateMonitor;
import org.chromium.chrome.browser.customtabs.dependency_injection.CustomTabActivityComponent;
import org.chromium.chrome.browser.customtabs.dependency_injection.CustomTabActivityModule;
import javax.inject.Singleton;
......@@ -14,14 +17,15 @@ import dagger.Component;
/**
* Component representing the Singletons in the main process of the application.
*/
@Component(modules = {ChromeAppModule.class})
@Component(modules = {ChromeAppModule.class, AppHooksModule.class})
@Singleton
public interface ChromeAppComponent {
ChromeActivityComponent createChromeActivityComponent(ChromeActivityCommonsModule module,
ContextualSuggestionsModule contextualSuggestionsModule);
CustomTabActivityComponent createCustomTabActivityComponent(ChromeActivityCommonsModule module,
ContextualSuggestionsModule contextualSuggestionsModule);
ContextualSuggestionsModule contextualSuggestionsModule,
CustomTabActivityModule customTabActivityModule);
// Temporary getters for DI migration process. All of these getters
// should eventually be replaced with constructor injection.
......
......@@ -4,8 +4,12 @@
package org.chromium.chrome.browser.dependency_injection;
import static org.chromium.chrome.browser.dependency_injection.ChromeCommonQualifiers.APP_CONTEXT;
import static org.chromium.chrome.browser.dependency_injection.ChromeCommonQualifiers.LAST_USED_PROFILE;
import android.content.Context;
import org.chromium.base.ContextUtils;
import org.chromium.chrome.browser.contextual_suggestions.EnabledStateMonitor;
import org.chromium.chrome.browser.contextual_suggestions.EnabledStateMonitorImpl;
import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
......@@ -41,4 +45,10 @@ public class ChromeAppModule {
public ChromePreferenceManager providesChromePreferenceManager() {
return ChromePreferenceManager.getInstance();
}
@Provides
@Named(APP_CONTEXT)
public Context provideContext() {
return ContextUtils.getApplicationContext();
}
}
......@@ -10,4 +10,7 @@ package org.chromium.chrome.browser.dependency_injection;
*/
public interface ChromeCommonQualifiers {
String LAST_USED_PROFILE = "LAST_USED_PROFILE";
String ACTIVITY_CONTEXT = "ACTIVITY_CONTEXT";
String APP_CONTEXT = "APP_CONTEXT";
}
......@@ -53,7 +53,7 @@ public class ChromeFullscreenManager
private final boolean mExitFullscreenOnStop;
private final TokenHolder mHidingTokenHolder = new TokenHolder(this::scheduleVisibilityUpdate);
private ControlContainer mControlContainer;
@Nullable private ControlContainer mControlContainer;
private int mTopControlContainerHeight;
private int mBottomControlContainerHeight;
private boolean mControlsResizeView;
......@@ -225,6 +225,7 @@ public class ChromeFullscreenManager
mRendererTopContentOffset = mTopControlContainerHeight;
updateControlOffset();
scheduleVisibilityUpdate();
}
/**
......@@ -398,8 +399,9 @@ public class ChromeFullscreenManager
}
/**
* @return The toolbar control container.
* @return The toolbar control container, null until {@link #initialize} is called.
*/
@Nullable
public ControlContainer getControlContainer() {
return mControlContainer;
}
......@@ -486,6 +488,9 @@ public class ChromeFullscreenManager
* animation, preventing message loop stalls due to untimely invalidation.
*/
private void scheduleVisibilityUpdate() {
if (mControlContainer == null) {
return;
}
final int desiredVisibility = shouldShowAndroidControls() ? View.VISIBLE : View.INVISIBLE;
if (mControlContainer.getView().getVisibility() == desiredVisibility) return;
mControlContainer.getView().removeCallbacks(mUpdateVisibilityRunnable);
......
......@@ -26,6 +26,9 @@ import java.util.regex.Pattern;
* Utilities for working with URIs (and URLs). These methods may be used in security-sensitive
* contexts (after all, origins are the security boundary on the web), and so the correctness bar
* must be high.
*
* Use ShadowUrlUtilities to mock out native-dependent methods in tests.
* TODO(pshmakov): we probably should just make those methods non-static.
*/
public class UrlUtilities {
private static final String TAG = "UrlUtilities";
......
......@@ -14,6 +14,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/ActivityTaskDescriptionIconGenerator.java",
"java/src/org/chromium/chrome/browser/AfterStartupTaskUtils.java",
"java/src/org/chromium/chrome/browser/AppHooks.java",
"java/src/org/chromium/chrome/browser/AppHooksModule.java",
"java/src/org/chromium/chrome/browser/AppIndexingUtil.java",
"java/src/org/chromium/chrome/browser/ApplicationInitialization.java",
"java/src/org/chromium/chrome/browser/ApplicationLifetime.java",
......@@ -385,6 +386,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java",
"java/src/org/chromium/chrome/browser/customtabs/CustomTabAppMenuPropertiesDelegate.java",
"java/src/org/chromium/chrome/browser/customtabs/CustomTabBottomBarDelegate.java",
"java/src/org/chromium/chrome/browser/customtabs/CustomTabBrowserControlsVisibilityDelegate.java",
"java/src/org/chromium/chrome/browser/customtabs/CustomTabDelegateFactory.java",
"java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java",
"java/src/org/chromium/chrome/browser/customtabs/CustomTabNavigationEventObserver.java",
......@@ -411,6 +413,8 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/customtabs/SeparateTaskCustomTabActivity9.java",
"java/src/org/chromium/chrome/browser/customtabs/SeparateTaskManagedCustomTabActivity.java",
"java/src/org/chromium/chrome/browser/customtabs/TabObserverRegistrar.java",
"java/src/org/chromium/chrome/browser/customtabs/dependency_injection/CustomTabActivityComponent.java",
"java/src/org/chromium/chrome/browser/customtabs/dependency_injection/CustomTabActivityModule.java",
"java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/ActivityDelegate.java",
"java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/ActivityHostImpl.java",
"java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/ModuleEntryPoint.java",
......@@ -425,7 +429,6 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/dependency_injection/ChromeAppComponent.java",
"java/src/org/chromium/chrome/browser/dependency_injection/ChromeAppModule.java",
"java/src/org/chromium/chrome/browser/dependency_injection/ChromeCommonQualifiers.java",
"java/src/org/chromium/chrome/browser/dependency_injection/CustomTabActivityComponent.java",
"java/src/org/chromium/chrome/browser/dependency_injection/ModuleFactoryOverrides.java",
"java/src/org/chromium/chrome/browser/device/DeviceClassManager.java",
"java/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutController.java",
......
......@@ -10,12 +10,16 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
......@@ -25,35 +29,29 @@ import org.robolectric.annotation.Config;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.util.test.ShadowUrlUtilities;
/**
* Tests for {@link ClientAppDataRecorder}.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
@Config(manifest = Config.NONE, shadows = {ShadowUrlUtilities.class})
public class ClientAppDataRecorderTest {
private static final int APP_UID = 123;
private static final String APP_NAME = "Example App";
private static final String APP_PACKAGE = "com.example.app";
private static final String MISSING_PACKAGE = "com.missing.app";
private static final Origin ORIGIN = new Origin("https://www.example.com/");
private static final Origin OTHER_ORIGIN = new Origin("https://www.example.com/");
private static final Origin OTHER_ORIGIN = new Origin("https://www.other.com/");
@Mock public ClientAppDataRegister mRegister;
@Mock public PackageManager mPackageManager;
@Mock private ClientAppDataRegister mRegister;
@Mock private PackageManager mPackageManager;
private ClientAppDataRecorder mRecorder;
private final ClientAppDataRecorder.UrlTransformer mUrlTransformer =
new ClientAppDataRecorder.UrlTransformer() {
@Override
String getDomain(Origin origin) {
return transform(origin);
}
};
private static String transform(Origin origin) {
private static String transform(String origin) {
// Just an arbitrary string transformation so we can check it is applied.
return origin.toString().toUpperCase();
return origin.toUpperCase();
}
@Before
......@@ -72,7 +70,22 @@ public class ClientAppDataRecorderTest {
.when(mPackageManager)
.getApplicationInfo(eq(MISSING_PACKAGE), anyInt());
mRecorder = new ClientAppDataRecorder(mPackageManager, mRegister, mUrlTransformer);
Context context = mock(Context.class);
when(context.getPackageManager()).thenReturn(mPackageManager);
ShadowUrlUtilities.setTestImpl(new ShadowUrlUtilities.TestImpl() {
@Override
public String getDomainAndRegistry(String uri, boolean includePrivateRegistries) {
return transform(uri);
}
});
mRecorder = new ClientAppDataRecorder(context, mRegister);
}
@After
public void tearDown() {
ShadowUrlUtilities.reset();
}
......@@ -80,7 +93,7 @@ public class ClientAppDataRecorderTest {
@Feature("TrustedWebActivities")
public void testRegister() {
mRecorder.register(APP_PACKAGE, ORIGIN);
verify(mRegister).registerPackageForDomain(APP_UID, APP_NAME, transform(ORIGIN));
verify(mRegister).registerPackageForDomain(APP_UID, APP_NAME, transform(ORIGIN.toString()));
}
@Test
......@@ -88,7 +101,7 @@ public class ClientAppDataRecorderTest {
public void testDeduplicate() {
mRecorder.register(APP_PACKAGE, ORIGIN);
mRecorder.register(APP_PACKAGE, ORIGIN);
verify(mRegister).registerPackageForDomain(APP_UID, APP_NAME, transform(ORIGIN));
verify(mRegister).registerPackageForDomain(APP_UID, APP_NAME, transform(ORIGIN.toString()));
}
@Test
......@@ -96,9 +109,9 @@ public class ClientAppDataRecorderTest {
public void testDifferentOrigins() {
mRecorder.register(APP_PACKAGE, ORIGIN);
mRecorder.register(APP_PACKAGE, OTHER_ORIGIN);
verify(mRegister).registerPackageForDomain(APP_UID, APP_NAME, transform(ORIGIN));
verify(mRegister).registerPackageForDomain(APP_UID, APP_NAME, transform(ORIGIN.toString()));
verify(mRegister).registerPackageForDomain(
APP_UID, APP_NAME, transform(OTHER_ORIGIN));
APP_UID, APP_NAME, transform(OTHER_ORIGIN.toString()));
}
@Test
......
......@@ -30,11 +30,14 @@ import org.chromium.chrome.browser.util.test.ShadowUrlUtilities;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/** Tests for {@link DomainDataCleaner}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE, shadows = {ShadowUrlUtilities.class})
public class DomainDataCleanerTest {
private final Map<String, String> mUrlToDomain = new HashMap<>();
@Mock
private ChromeBrowserInitializer mChromeBrowserInitializer;
@Mock
......@@ -64,6 +67,13 @@ public class DomainDataCleanerTest {
return null;
}).when(mWebsitePermissionsFetcher).fetchAllPreferences(any());
ShadowUrlUtilities.setTestImpl(new ShadowUrlUtilities.TestImpl() {
@Override
public String getDomainAndRegistry(String url, boolean includePrivateRegistries) {
return mUrlToDomain.get(url);
}
});
mCleaner = new DomainDataCleaner(
mChromeBrowserInitializer, mSiteDataCleaner, mWebsitePermissionsFetcher);
}
......@@ -104,9 +114,9 @@ public class DomainDataCleanerTest {
verify(mFinishCallback).run();
}
private Website makeMockWebsite(String origin, String domainAndRegistry) {
private Website makeMockWebsite(String origin, String domain) {
Website website = new Website(WebsiteAddress.create(origin), null);
ShadowUrlUtilities.setUrlToToDomainMapping(origin, domainAndRegistry);
mUrlToDomain.put(origin, domain);
return website;
}
......
......@@ -48,14 +48,15 @@ public class TrustedWebActivityDisclosureTest {
doReturn("any string").when(mResources).getString(anyInt());
doReturn(false).when(mPreferences).hasUserAcceptedTwaDisclosureForPackage(anyString());
mDisclosure = new TrustedWebActivityDisclosure(mResources, mPreferences);
mDisclosure = new TrustedWebActivityDisclosure(mResources, mPreferences,
() -> mSnackbarManager);
}
@Test
@Feature("TrustedWebActivities")
public void showIsIdempotent() {
mDisclosure.showSnackbarIfNeeded(mSnackbarManager, TWA_PACKAGE);
mDisclosure.showSnackbarIfNeeded(mSnackbarManager, TWA_PACKAGE);
mDisclosure.showIfNeeded(TWA_PACKAGE);
mDisclosure.showIfNeeded(TWA_PACKAGE);
verify(mSnackbarManager).showSnackbar(any());
}
......@@ -63,10 +64,10 @@ public class TrustedWebActivityDisclosureTest {
@Test
@Feature("TrustedWebActivities")
public void hideIsIdempotent() {
mDisclosure.showSnackbarIfNeeded(mSnackbarManager, TWA_PACKAGE);
mDisclosure.showIfNeeded(TWA_PACKAGE);
mDisclosure.dismissSnackbarIfNeeded(mSnackbarManager);
mDisclosure.dismissSnackbarIfNeeded(mSnackbarManager);
mDisclosure.dismiss();
mDisclosure.dismiss();
verify(mSnackbarManager).dismissSnackbars(any());
}
......@@ -76,7 +77,7 @@ public class TrustedWebActivityDisclosureTest {
public void noShowIfAlreadyAccepted() {
doReturn(true).when(mPreferences).hasUserAcceptedTwaDisclosureForPackage(anyString());
mDisclosure.showSnackbarIfNeeded(mSnackbarManager, TWA_PACKAGE);
mDisclosure.showIfNeeded(TWA_PACKAGE);
verify(mSnackbarManager, times(0)).showSnackbar(any());
}
......@@ -85,7 +86,7 @@ public class TrustedWebActivityDisclosureTest {
@Feature("TrustedWebActivities")
public void recordDismiss() {
ArgumentCaptor<Snackbar> snackbarCaptor = ArgumentCaptor.forClass(Snackbar.class);
mDisclosure.showSnackbarIfNeeded(mSnackbarManager, TWA_PACKAGE);
mDisclosure.showIfNeeded(TWA_PACKAGE);
verify(mSnackbarManager).showSnackbar(snackbarCaptor.capture());
......@@ -101,7 +102,7 @@ public class TrustedWebActivityDisclosureTest {
@Feature("TrustedWebActivities")
public void doNothingOnNoSnackbarAction() {
ArgumentCaptor<Snackbar> snackbarCaptor = ArgumentCaptor.forClass(Snackbar.class);
mDisclosure.showSnackbarIfNeeded(mSnackbarManager, TWA_PACKAGE);
mDisclosure.showIfNeeded(TWA_PACKAGE);
verify(mSnackbarManager).showSnackbar(snackbarCaptor.capture());
......
......@@ -8,35 +8,44 @@ import android.text.TextUtils;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
import org.chromium.chrome.browser.util.UrlUtilities;
import java.util.HashMap;
import java.util.Map;
/** Implementation of UrlUtilities which does not rely on native. */
@Implements(UrlUtilities.class)
public class ShadowUrlUtilities {
private static Map<String, String> sUrlToToDomain = new HashMap<>();
private static TestImpl sTestImpl = new TestImpl();
/** Set implementation for tests. Don't forget to call {@link #reset} later. */
public static void setTestImpl(TestImpl impl) {
sTestImpl = impl;
}
@Resetter
public static void reset() {
sTestImpl = new TestImpl();
}
@Implementation
public static boolean urlsMatchIgnoringFragments(String url, String url2) {
return TextUtils.equals(url, url2);
return sTestImpl.urlsMatchIgnoringFragments(url, url2);
}
@Implementation
public static String getDomainAndRegistry(String url, boolean includePrivateRegistries) {
String domain = sUrlToToDomain.get(url);
return domain == null ? url : domain;
public static String getDomainAndRegistry(String uri, boolean includePrivateRegistries) {
return sTestImpl.getDomainAndRegistry(uri, includePrivateRegistries);
}
/** Add a url to domain mapping. */
public static void setUrlToToDomainMapping(String url, String domain) {
sUrlToToDomain.put(url, domain);
}
/** Default implementation for tests. Override methods or add new ones as necessary. */
public static class TestImpl {
public boolean urlsMatchIgnoringFragments(String url, String url2) {
return TextUtils.equals(url, url2);
}
/** Clear the static state. */
public static void reset() {
sUrlToToDomain.clear();
public String getDomainAndRegistry(String uri, boolean includePrivateRegistries) {
return uri;
}
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment