blob: 6adf29b3e9d1a7e5f3687d46d6f50723a0dede50 [file] [log] [blame]
// 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.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.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}.
*/
@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 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 final PersistentNotificationController mNotificationController;
private boolean mInTrustedWebActivity = true;
private int mControlsHidingToken = FullscreenManager.INVALID_TOKEN;
/** A {@link TabObserver} that checks whether we are on a verified Origin on page navigation. */
private final TabObserver mVerifyOnPageLoadObserver = new EmptyTabObserver() {
@Override
public void onDidFinishNavigation(Tab tab, String url, boolean isInMainFrame,
boolean isErrorPage, boolean hasCommitted, boolean isSameDocument,
boolean isFragmentNavigation, Integer pageTransition, int errorCode,
int httpStatusCode) {
if (!hasCommitted || !isInMainFrame) return;
String packageName = getClientPackageName();
assert packageName != null;
// This doesn't perform a network request or attempt new verification - it checks to
// see if a verification already exists for the given inputs.
Origin origin = new Origin(url);
boolean verified =
OriginVerifier.isValidOrigin(packageName, origin, RELATIONSHIP);
handleVerificationResult(verified, packageName, origin);
}
};
@Inject
public TrustedWebActivityUi(TrustedWebActivityDisclosure disclosure,
ChromeFullscreenManager fullscreenManager, ClientAppDataRecorder clientAppDataRecorder,
CustomTabIntentDataProvider intentDataProvider,
CustomTabsConnection customTabsConnection,
ActivityLifecycleDispatcher lifecycleDispatcher,
TabObserverRegistrar tabObserverRegistrar, ActivityTabProvider activityTabProvider,
CustomTabBrowserControlsVisibilityDelegate controlsVisibilityDelegate,
PersistentNotificationController notificationController) {
mFullscreenManager = fullscreenManager;
mClientAppDataRecorder = clientAppDataRecorder;
mDisclosure = disclosure;
mCustomTabsConnection = customTabsConnection;
mIntentDataProvider = intentDataProvider;
mActivityTabProvider = activityTabProvider;
mControlsVisibilityDelegate = controlsVisibilityDelegate;
mNotificationController = notificationController;
tabObserverRegistrar.registerTabObserver(mVerifyOnPageLoadObserver);
lifecycleDispatcher.register(this);
}
@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.
*/
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.showIfNeeded(packageName);
}
/**
* Perform verification for the URL that the CustomTabActivity starts on.
*/
public void attemptVerificationForInitialUrl(String url, Tab tab) {
String packageName = getClientPackageName();
assert packageName != null;
Origin origin = new Origin(url);
new OriginVerifier((packageName2, origin2, verified, online) -> {
if (!origin.equals(new Origin(tab.getUrl()))) return;
BrowserServicesMetrics.recordTwaOpened();
handleVerificationResult(verified, packageName, origin);
}, packageName, RELATIONSHIP).start(origin);
}
private void handleVerificationResult(boolean verified, String packageName, Origin origin) {
if (verified) {
registerClientAppData(packageName, origin);
mNotificationController.onOriginVerifiedForPackage(origin, packageName);
}
setTrustedWebActivityMode(verified);
}
@Override
public void onPreInflationStartup() {}
@Override
public void onPostInflationStartup() {
if (mInTrustedWebActivity) {
// Hide Android controls as soon as they are inflated.
mControlsHidingToken = mFullscreenManager.hideAndroidControls();
mControlsVisibilityDelegate.setTrustedWebActivityMode(true);
}
}
@Override
public void onResumeWithNative() {
mOpenTimeRecorder.onResume();
}
@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) {
if (mInTrustedWebActivity == enabled) return;
mInTrustedWebActivity = enabled;
mControlsVisibilityDelegate.setTrustedWebActivityMode(mInTrustedWebActivity);
if (enabled) {
mControlsHidingToken =
mFullscreenManager.hideAndroidControlsAndClearOldToken(mControlsHidingToken);
mDisclosure.showIfNeeded(getClientPackageName());
} else {
mFullscreenManager.releaseAndroidControlsHidingToken(mControlsHidingToken);
// Force showing the controls for a bit when leaving Trusted Web Activity mode.
mFullscreenManager.getBrowserVisibilityDelegate().showControlsTransient();
mDisclosure.dismiss();
}
}
/**
* Register that we have Chrome data relevant to the Client app.
*
* We do this here, when the Trusted Web Activity UI is shown instead of in OriginVerifier when
* verification completes because when an origin is being verified, we don't know whether it is
* for the purposes of Trusted Web Activities or for Post Message (where this behaviour is not
* required).
*
* Additionally we do it on every page navigation because an app can be verified for more than
* one Origin, eg:
* 1) App verifies with https://www.myfirsttwa.com/.
* 2) App verifies with https://www.mysecondtwa.com/.
* 3) App launches a TWA to https://www.myfirsttwa.com/.
* 4) App navigates to https://www.mysecondtwa.com/.
*
* At step 2, we don't know why the app is verifying with that origin (it could be for TWAs or
* for PostMessage). Only at step 4 do we know that Chrome should associate the browsing data
* for that origin with that app.
*/
private void registerClientAppData(String packageName, Origin origin) {
mClientAppDataRecorder.register(packageName, origin);
}
}