Commit 55ff13ac authored by Pavel Shmakov's avatar Pavel Shmakov Committed by Commit Bot

Web Share Target for TWA 2/2

This CL builds the implementation of Web Share Target for TWAs
on top of the previous one (http://crrev.com/c/1715374):

- Adds TwaIntentHandlingStrategy for TWA-specific logic of intent
handling. It sends the intent to TwaSharingController to check
whether it's a sharing intent.

- Adds TwaSharingController that parses share
target json, constructs and launches GET requests, and delegates
handling POST requests to WebApkPostShareTargetNavigator.

- Adds unit tests for share target parsing, and a few integration
tests for the whole sharing process.


Bug: 985331
Change-Id: Ifa98e46a5e5c681a30442aecfbd6df57a60d9e39
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1724686
Commit-Queue: Pavel Shmakov <pshmakov@chromium.org>
Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Reviewed-by: default avatarPeter Conn <peconn@chromium.org>
Reviewed-by: default avatarPeter Kotwicz <pkotwicz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#688554}
parent 77aba032
...@@ -665,7 +665,7 @@ deps = { ...@@ -665,7 +665,7 @@ deps = {
}, },
'src/third_party/android_sdk/androidx_browser/src': { 'src/third_party/android_sdk/androidx_browser/src': {
'url': Var('chromium_git') + '/external/gob/android/platform/frameworks/support/browser.git' + '@' + 'aeeea8bd0a6703bc4a148e9bcd6998553def74ab', 'url': Var('chromium_git') + '/external/gob/android/platform/frameworks/support/browser.git' + '@' + 'fe843d13cd587d066c3bb3e5c636089a4a05056b',
'condition': 'checkout_android', 'condition': 'checkout_android',
}, },
......
...@@ -159,10 +159,12 @@ chrome_java_sources = [ ...@@ -159,10 +159,12 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/TrustedWebActivityPermissionStore.java", "java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/TrustedWebActivityPermissionStore.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/TrustedWebActivityCoordinator.java", "java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/TrustedWebActivityCoordinator.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/TrustedWebActivityModel.java", "java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/TrustedWebActivityModel.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/TwaIntentHandlingStrategy.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/ClientAppDataRecorder.java", "java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/ClientAppDataRecorder.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityDisclosureController.java", "java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityDisclosureController.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityOpenTimeRecorder.java", "java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityOpenTimeRecorder.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityVerifier.java", "java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityVerifier.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/sharing/TwaSharingController.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/splashscreen/SplashImageHolder.java", "java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/splashscreen/SplashImageHolder.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/splashscreen/TwaSplashController.java", "java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/splashscreen/TwaSplashController.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/view/TrustedWebActivityDisclosureView.java", "java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/view/TrustedWebActivityDisclosureView.java",
......
...@@ -64,7 +64,9 @@ chrome_test_java_sources = [ ...@@ -64,7 +64,9 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java", "javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java",
"javatests/src/org/chromium/chrome/browser/browserservices/OriginVerifierTest.java", "javatests/src/org/chromium/chrome/browser/browserservices/OriginVerifierTest.java",
"javatests/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityClientTest.java", "javatests/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityClientTest.java",
"javatests/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityShareTargetTest.java",
"javatests/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityTest.java", "javatests/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityTest.java",
"javatests/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityTestUtil.java",
"javatests/src/org/chromium/chrome/browser/browserservices/permissiondelegation/TrustedWebActivityPermissionsTest.java", "javatests/src/org/chromium/chrome/browser/browserservices/permissiondelegation/TrustedWebActivityPermissionsTest.java",
"javatests/src/org/chromium/chrome/browser/browserservices/permissiondelegation/TrustedWebActivityPreferencesUiTest.java", "javatests/src/org/chromium/chrome/browser/browserservices/permissiondelegation/TrustedWebActivityPreferencesUiTest.java",
"javatests/src/org/chromium/chrome/browser/browsing_data/BrowsingDataRemoverIntegrationTest.java", "javatests/src/org/chromium/chrome/browser/browsing_data/BrowsingDataRemoverIntegrationTest.java",
......
...@@ -1167,6 +1167,7 @@ by a child template that "extends" this file. ...@@ -1167,6 +1167,7 @@ by a child template that "extends" this file.
<category android:name="androidx.browser.trusted.category.TrustedWebActivitySplashScreensV1"/> <category android:name="androidx.browser.trusted.category.TrustedWebActivitySplashScreensV1"/>
<category android:name="androidx.browser.customtabs.category.NavBarColorCustomization"/> <category android:name="androidx.browser.customtabs.category.NavBarColorCustomization"/>
<category android:name="androidx.browser.customtabs.category.ColorSchemeCustomization"/> <category android:name="androidx.browser.customtabs.category.ColorSchemeCustomization"/>
<category android:name="androidx.browser.trusted.category.WebShareTargetV2"/>
</intent-filter> </intent-filter>
</service> </service>
<service android:name="androidx.browser.customtabs.PostMessageService" /> <service android:name="androidx.browser.customtabs.PostMessageService" />
......
...@@ -1961,6 +1961,7 @@ ...@@ -1961,6 +1961,7 @@
<category android:name="androidx.browser.trusted.category.TrustedWebActivities"/> <category android:name="androidx.browser.trusted.category.TrustedWebActivities"/>
<category <category
android:name="androidx.browser.trusted.category.TrustedWebActivitySplashScreensV1"/> android:name="androidx.browser.trusted.category.TrustedWebActivitySplashScreensV1"/>
<category android:name="androidx.browser.trusted.category.WebShareTargetV2"/>
</intent-filter> </intent-filter>
</service> </service>
<service <service
......
...@@ -134,7 +134,7 @@ public class TrustedWebActivityClient { ...@@ -134,7 +134,7 @@ public class TrustedWebActivityClient {
} }
int id = service.getSmallIconId(); int id = service.getSmallIconId();
if (id == TrustedWebActivityService.NO_ID) { if (id == TrustedWebActivityService.SMALL_ICON_NOT_SET) {
recordFallback(FALLBACK_ICON_NOT_PROVIDED); recordFallback(FALLBACK_ICON_NOT_PROVIDED);
return; return;
} }
......
// Copyright 2019 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.trustedwebactivityui;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.sharing.TwaSharingController;
import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider;
import org.chromium.chrome.browser.customtabs.content.CustomTabIntentHandlingStrategy;
import org.chromium.chrome.browser.customtabs.content.DefaultCustomTabIntentHandlingStrategy;
import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import javax.inject.Inject;
/**
* TWA-specific implementation of {@link CustomTabIntentHandlingStrategy}.
* Currently adds Web Share Target capabilities on top of the Custom Tabs intent handling.
*/
@ActivityScope
public class TwaIntentHandlingStrategy implements CustomTabIntentHandlingStrategy {
private final DefaultCustomTabIntentHandlingStrategy mDefaultStrategy;
private final TwaSharingController mSharingController;
@Inject
public TwaIntentHandlingStrategy(DefaultCustomTabIntentHandlingStrategy defaultStrategy,
TwaSharingController sharingController) {
mDefaultStrategy = defaultStrategy;
mSharingController = sharingController;
}
@Override
public void handleInitialIntent(CustomTabIntentDataProvider intentDataProvider) {
handleIntent(intentDataProvider);
}
@Override
public void handleNewIntent(CustomTabIntentDataProvider intentDataProvider) {
// TODO(pshmakov): we can have a significant delay here in case of POST sharing.
// Allow showing splash screen, if it's provided in the intent.
handleIntent(intentDataProvider);
}
private void handleIntent(CustomTabIntentDataProvider intentDataProvider) {
mSharingController.deliverToShareTarget(intentDataProvider).then((delivered) -> {
if (!delivered) {
mDefaultStrategy.handleInitialIntent(intentDataProvider);
}
});
}
}
...@@ -11,6 +11,7 @@ import android.support.annotation.Nullable; ...@@ -11,6 +11,7 @@ import android.support.annotation.Nullable;
import org.chromium.base.ContextUtils; import org.chromium.base.ContextUtils;
import org.chromium.base.ObserverList; import org.chromium.base.ObserverList;
import org.chromium.base.Promise;
import org.chromium.chrome.browser.ChromeActivity; import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeFeatureList; import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.browserservices.Origin; import org.chromium.chrome.browser.browserservices.Origin;
...@@ -103,7 +104,7 @@ public class TrustedWebActivityVerifier implements NativeInitObserver, Destroyab ...@@ -103,7 +104,7 @@ public class TrustedWebActivityVerifier implements NativeInitObserver, Destroyab
assert false : "Shouldn't observe navigation when TWAs are disabled"; assert false : "Shouldn't observe navigation when TWAs are disabled";
return; return;
} }
verify(new Origin(navigation.getUrl())); verifyVisitedOrigin(new Origin(navigation.getUrl()));
} }
}; };
...@@ -114,7 +115,7 @@ public class TrustedWebActivityVerifier implements NativeInitObserver, Destroyab ...@@ -114,7 +115,7 @@ public class TrustedWebActivityVerifier implements NativeInitObserver, Destroyab
// When a link with target="_blank" is followed and the user navigates back, we // When a link with target="_blank" is followed and the user navigates back, we
// don't get the onDidFinishNavigation event (because the original page wasn't // don't get the onDidFinishNavigation event (because the original page wasn't
// navigated away from, it was only ever hidden). https://crbug.com/942088 // navigated away from, it was only ever hidden). https://crbug.com/942088
verify(new Origin(tab.getUrl())); verifyVisitedOrigin(new Origin(tab.getUrl()));
} }
}; };
...@@ -184,7 +185,7 @@ public class TrustedWebActivityVerifier implements NativeInitObserver, Destroyab ...@@ -184,7 +185,7 @@ public class TrustedWebActivityVerifier implements NativeInitObserver, Destroyab
} }
collectTrustedOrigins(initialOrigin); collectTrustedOrigins(initialOrigin);
verify(initialOrigin); verifyVisitedOrigin(initialOrigin);
// This doesn't belong here, but doesn't deserve a separate class. Do extract it if more // This doesn't belong here, but doesn't deserve a separate class. Do extract it if more
// PostMessage-related code appears. // PostMessage-related code appears.
...@@ -205,15 +206,32 @@ public class TrustedWebActivityVerifier implements NativeInitObserver, Destroyab ...@@ -205,15 +206,32 @@ public class TrustedWebActivityVerifier implements NativeInitObserver, Destroyab
} }
} }
/** Returns whether the given |url| is on an Origin that the package has been verified for. */ /**
* Returns whether the given |url| is on an Origin that the package has been previously
* verified for.
*/
public boolean isPageOnVerifiedOrigin(String url) { public boolean isPageOnVerifiedOrigin(String url) {
return mOriginVerifier.wasPreviouslyVerified(new Origin(url)); return mOriginVerifier.wasPreviouslyVerified(new Origin(url));
} }
/** /**
* Perform verification for the given origin. * Verifies an arbitrary url.
* Returns a {@link Promise<Boolean>} with boolean telling whether verification succeeded.
*/
public Promise<Boolean> verifyOrigin(String url) {
if (mOriginVerifier.wasPreviouslyVerified(new Origin(url))) {
return Promise.fulfilled(true);
}
Promise<Boolean> promise = new Promise<>();
mOriginVerifier.start((packageName, origin, verified, online) -> promise.fulfill(verified),
new Origin(url));
return promise;
}
/**
* Perform verification for the origin the user is currently on.
*/ */
private void verify(Origin origin) { private void verifyVisitedOrigin(Origin origin) {
if (mOriginsToVerify.contains(origin)) { if (mOriginsToVerify.contains(origin)) {
// Do verification bypassing the cache. // Do verification bypassing the cache.
updateState(origin, VerificationStatus.PENDING); updateState(origin, VerificationStatus.PENDING);
......
// Copyright 2019 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.trustedwebactivityui.sharing;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Pair;
import org.chromium.base.Promise;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller.TrustedWebActivityVerifier;
import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider;
import org.chromium.chrome.browser.customtabs.content.CustomTabActivityNavigationController;
import org.chromium.chrome.browser.customtabs.content.CustomTabActivityTabProvider;
import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.webapps.WebApkInfo;
import org.chromium.chrome.browser.webapps.WebApkPostShareTargetNavigator;
import java.util.ArrayList;
import java.util.Locale;
import javax.inject.Inject;
import androidx.browser.trusted.sharing.ShareData;
import androidx.browser.trusted.sharing.ShareTarget;
/**
* Handles sharing intents coming to Trusted Web Activities.
*/
@ActivityScope
public class TwaSharingController {
private final CustomTabActivityTabProvider mTabProvider;
private final CustomTabActivityNavigationController mNavigationController;
private final WebApkPostShareTargetNavigator mPostNavigator;
private final TrustedWebActivityVerifier mVerifier;
@Inject
public TwaSharingController(CustomTabActivityTabProvider tabProvider,
CustomTabActivityNavigationController navigationController,
WebApkPostShareTargetNavigator postNavigator,
TrustedWebActivityVerifier verifier) {
mTabProvider = tabProvider;
mNavigationController = navigationController;
mPostNavigator = postNavigator;
mVerifier = verifier;
}
/**
* Checks whether the incoming intent (represented by a {@link CustomTabIntentDataProvider})
* is a sharing intent and attempts to perform the sharing.
*
* Returns a {@link Promise<Boolean>} with a boolean telling whether sharing was successful.
*/
public Promise<Boolean> deliverToShareTarget(CustomTabIntentDataProvider intentDataProvider) {
ShareData shareData = intentDataProvider.getShareData();
ShareTarget shareTarget = intentDataProvider.getShareTarget();
if (shareTarget == null || shareData == null) {
return Promise.fulfilled(false);
}
return mVerifier.verifyOrigin(shareTarget.action).then(
(Promise.Function<Boolean, Boolean>) (verified) -> {
if (!verified) {
return false;
}
WebApkInfo.ShareTarget target = toShareTargetInternal(shareTarget);
if (target.isShareMethodPost()) {
return sendPost(shareData, target);
}
mNavigationController.navigate(computeStartUrlForGETShareTarget(shareData, target));
return true;
});
}
/**
* Converts to internal format.
* TODO(pshmakov): pull WebApkInfo.ShareTarget out of WebApkInfo and rename to
* ShareTargetInternal. Also, replace WebApkInfo.ShareData with ShareData from TWA API.
*/
private WebApkInfo.ShareTarget toShareTargetInternal(ShareTarget shareTarget) {
ShareTarget.Params params = shareTarget.params;
String action = shareTarget.action;
String paramTitle = params.title;
String paramText = params.text;
String paramUrl = ""; // Not supported on Android
String method = shareTarget.method;
boolean isPost = method != null && "POST".equals(method.toUpperCase(Locale.ENGLISH));
String encodingType = shareTarget.encodingType;
boolean isMultipart = encodingType != null &&
"multipart/form-data".equals(encodingType.toLowerCase(Locale.ENGLISH));
int numFiles = params.files == null ? 0 : params.files.size();
String[] filesArray = new String[numFiles];
String[][] acceptsArray = new String[numFiles][];
for (int i = 0; i < numFiles; i++) {
ShareTarget.FileFormField file = params.files.get(i);
filesArray[i] = file.name;
acceptsArray[i] = file.acceptedTypes.toArray(new String[file.acceptedTypes.size()]);
}
return new WebApkInfo.ShareTarget(action, paramTitle, paramText, paramUrl, isPost,
isMultipart, filesArray, acceptsArray);
}
private boolean sendPost(ShareData shareData, WebApkInfo.ShareTarget target) {
WebApkInfo.ShareData webApkData = new WebApkInfo.ShareData();
if (shareData.uris != null) {
webApkData.files = new ArrayList<>(shareData.uris);
}
webApkData.subject = shareData.title;
webApkData.text = shareData.text;
Tab tab = mTabProvider.getTab();
if (tab == null) {
assert false : "Null tab when sharing";
return false;
}
return mPostNavigator.navigateIfPostShareTarget(target.getAction(), target, webApkData,
tab.getWebContents());
}
// Copy of HostBrowserLauncherParams#computeStartUrlForGETShareTarget().
// Since the latter is in the WebAPK client code, we can't reuse it.
private static String computeStartUrlForGETShareTarget(
ShareData data, WebApkInfo.ShareTarget target) {
// These can be null, they are checked downstream.
ArrayList<Pair<String, String>> entryList = new ArrayList<>();
entryList.add(new Pair<>(target.getParamTitle(), data.title));
entryList.add(new Pair<>(target.getParamText(), data.text));
return createGETWebShareTargetUriString(target.getAction(), entryList);
}
private static String createGETWebShareTargetUriString(
String action, ArrayList<Pair<String, String>> entryList) {
Uri.Builder queryBuilder = new Uri.Builder();
for (Pair<String, String> nameValue : entryList) {
if (!TextUtils.isEmpty(nameValue.first) && !TextUtils.isEmpty(nameValue.second)) {
// Uri.Builder does URL escaping.
queryBuilder.appendQueryParameter(nameValue.first, nameValue.second);
}
}
Uri shareUri = Uri.parse(action);
Uri.Builder builder = shareUri.buildUpon();
// Uri.Builder uses %20 rather than + for spaces, the spec requires +.
String queryString = queryBuilder.build().toString();
if (TextUtils.isEmpty(queryString)) {
return action;
}
builder.encodedQuery(queryString.replace("%20", "+").substring(1));
return builder.build().toString();
}
}
...@@ -6,6 +6,8 @@ package org.chromium.chrome.browser.browserservices.trustedwebactivityui.splashs ...@@ -6,6 +6,8 @@ package org.chromium.chrome.browser.browserservices.trustedwebactivityui.splashs
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static androidx.browser.trusted.TrustedWebActivityIntentBuilder.EXTRA_SPLASH_SCREEN_PARAMS;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
...@@ -33,7 +35,8 @@ import org.chromium.ui.base.ActivityWindowAndroid; ...@@ -33,7 +35,8 @@ import org.chromium.ui.base.ActivityWindowAndroid;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.browser.customtabs.TrustedWebUtils; import androidx.browser.customtabs.TrustedWebUtils;
import androidx.browser.customtabs.TrustedWebUtils.SplashScreenParamKey; import androidx.browser.trusted.TrustedWebActivityIntentBuilder;
import androidx.browser.trusted.splashscreens.SplashScreenParamKey;
/** /**
* Orchestrates the flow of showing and removing splash screens for apps based on Trusted Web * Orchestrates the flow of showing and removing splash screens for apps based on Trusted Web
...@@ -174,8 +177,7 @@ public class TwaSplashController ...@@ -174,8 +177,7 @@ public class TwaSplashController
} }
private Bundle getSplashScreenParamsFromIntent() { private Bundle getSplashScreenParamsFromIntent() {
return mIntentDataProvider.getIntent().getBundleExtra( return mIntentDataProvider.getIntent().getBundleExtra(EXTRA_SPLASH_SCREEN_PARAMS);
TrustedWebUtils.EXTRA_SPLASH_SCREEN_PARAMS);
} }
/** /**
...@@ -184,9 +186,8 @@ public class TwaSplashController ...@@ -184,9 +186,8 @@ public class TwaSplashController
public static boolean intentIsForTwaWithSplashScreen(Intent intent) { public static boolean intentIsForTwaWithSplashScreen(Intent intent) {
boolean isTrustedWebActivity = IntentUtils.safeGetBooleanExtra( boolean isTrustedWebActivity = IntentUtils.safeGetBooleanExtra(
intent, TrustedWebUtils.EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY, false); intent, TrustedWebUtils.EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY, false);
boolean requestsSplashScreen = IntentUtils.safeGetParcelableExtra( boolean requestsSplashScreen =
intent, TrustedWebUtils.EXTRA_SPLASH_SCREEN_PARAMS) IntentUtils.safeGetParcelableExtra(intent, EXTRA_SPLASH_SCREEN_PARAMS) != null;
!= null;
return isTrustedWebActivity && requestsSplashScreen; return isTrustedWebActivity && requestsSplashScreen;
} }
...@@ -200,7 +201,7 @@ public class TwaSplashController ...@@ -200,7 +201,7 @@ public class TwaSplashController
if (!intentIsForTwaWithSplashScreen(intent)) return false; if (!intentIsForTwaWithSplashScreen(intent)) return false;
Bundle params = IntentUtils.safeGetBundleExtra( Bundle params = IntentUtils.safeGetBundleExtra(
intent, TrustedWebUtils.EXTRA_SPLASH_SCREEN_PARAMS); intent, TrustedWebActivityIntentBuilder.EXTRA_SPLASH_SCREEN_PARAMS);
boolean shownInClient = IntentUtils.safeGetBoolean(params, KEY_SHOWN_IN_CLIENT, true); boolean shownInClient = IntentUtils.safeGetBoolean(params, KEY_SHOWN_IN_CLIENT, true);
// shownInClient is "true" by default for the following reasons: // shownInClient is "true" by default for the following reasons:
// - For compatibility with older clients which don't use this bundle key. // - For compatibility with older clients which don't use this bundle key.
......
...@@ -403,7 +403,7 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent ...@@ -403,7 +403,7 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
} }
@Override @Override
protected void onNewIntent(Intent intent) { public void onNewIntent(Intent intent) {
Intent originalIntent = getIntent(); Intent originalIntent = getIntent();
super.onNewIntent(intent); super.onNewIntent(intent);
// Currently we can't handle arbitrary updates of intent parameters, so make sure // Currently we can't handle arbitrary updates of intent parameters, so make sure
......
...@@ -56,6 +56,9 @@ import androidx.browser.customtabs.CustomTabColorSchemeParams; ...@@ -56,6 +56,9 @@ import androidx.browser.customtabs.CustomTabColorSchemeParams;
import androidx.browser.customtabs.CustomTabsIntent; import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.customtabs.CustomTabsSessionToken; import androidx.browser.customtabs.CustomTabsSessionToken;
import androidx.browser.customtabs.TrustedWebUtils; import androidx.browser.customtabs.TrustedWebUtils;
import androidx.browser.trusted.TrustedWebActivityIntentBuilder;
import androidx.browser.trusted.sharing.ShareData;
import androidx.browser.trusted.sharing.ShareTarget;
/** /**
* A model class that parses the incoming intent for Custom Tabs specific customization data. * A model class that parses the incoming intent for Custom Tabs specific customization data.
...@@ -323,7 +326,7 @@ public class CustomTabIntentDataProvider extends BrowserSessionDataProvider { ...@@ -323,7 +326,7 @@ public class CustomTabIntentDataProvider extends BrowserSessionDataProvider {
mIsTrustedWebActivity = IntentUtils.safeGetBooleanExtra( mIsTrustedWebActivity = IntentUtils.safeGetBooleanExtra(
intent, TrustedWebUtils.EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY, false); intent, TrustedWebUtils.EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY, false);
mTrustedWebActivityAdditionalOrigins = IntentUtils.safeGetStringArrayListExtra(intent, mTrustedWebActivityAdditionalOrigins = IntentUtils.safeGetStringArrayListExtra(intent,
TrustedWebUtils.EXTRA_ADDITIONAL_TRUSTED_ORIGINS); TrustedWebActivityIntentBuilder.EXTRA_ADDITIONAL_TRUSTED_ORIGINS);
mTitleVisibilityState = IntentUtils.safeGetIntExtra( mTitleVisibilityState = IntentUtils.safeGetIntExtra(
intent, CustomTabsIntent.EXTRA_TITLE_VISIBILITY_STATE, CustomTabsIntent.NO_TITLE); intent, CustomTabsIntent.EXTRA_TITLE_VISIBILITY_STATE, CustomTabsIntent.NO_TITLE);
mShowShareItem = IntentUtils.safeGetBooleanExtra(intent, mShowShareItem = IntentUtils.safeGetBooleanExtra(intent,
...@@ -846,7 +849,7 @@ public class CustomTabIntentDataProvider extends BrowserSessionDataProvider { ...@@ -846,7 +849,7 @@ public class CustomTabIntentDataProvider extends BrowserSessionDataProvider {
/** /**
* @return Whether the Custom Tab should attempt to display a Trusted Web Activity. * @return Whether the Custom Tab should attempt to display a Trusted Web Activity.
*/ */
boolean isTrustedWebActivity() { public boolean isTrustedWebActivity() {
return mIsTrustedWebActivity; return mIsTrustedWebActivity;
} }
...@@ -950,4 +953,38 @@ public class CustomTabIntentDataProvider extends BrowserSessionDataProvider { ...@@ -950,4 +953,38 @@ public class CustomTabIntentDataProvider extends BrowserSessionDataProvider {
ChromeFeatureList.isEnabled(ChromeFeatureList.CCT_TARGET_TRANSLATE_LANGUAGE); ChromeFeatureList.isEnabled(ChromeFeatureList.CCT_TARGET_TRANSLATE_LANGUAGE);
return isEnabled ? mTranslateLanguage : null; return isEnabled ? mTranslateLanguage : null;
} }
/**
* Returns {@link ShareTarget} describing the share target, or null if the intent is not about
* sharing.
*/
@Nullable
public ShareTarget getShareTarget() {
Bundle bundle = IntentUtils.safeGetBundleExtra(mIntent,
TrustedWebActivityIntentBuilder.EXTRA_SHARE_TARGET);
if (bundle == null) return null;
try {
return ShareTarget.fromBundle(bundle);
} catch (Throwable e) {
// Catch unparcelling errors.
return null;
}
}
/**
* Returns {@link ShareData} describing the data to be shared, or null if the intent is not
* about sharing.
*/
@Nullable
public ShareData getShareData() {
Bundle bundle = IntentUtils.safeGetParcelableExtra(mIntent,
TrustedWebActivityIntentBuilder.EXTRA_SHARE_DATA);
if (bundle == null) return null;
try {
return ShareData.fromBundle(bundle);
} catch (Throwable e) {
// Catch unparcelling errors.
return null;
}
}
} }
...@@ -55,7 +55,7 @@ public class CustomTabsClientFileProcessor { ...@@ -55,7 +55,7 @@ public class CustomTabsClientFileProcessor {
return false; return false;
} }
switch (purpose) { switch (purpose) {
case CustomTabsService.FILE_PURPOSE_TWA_SPLASH_IMAGE: case CustomTabsService.FILE_PURPOSE_TRUSTED_WEB_ACTIVITY_SPLASH_IMAGE:
return receiveTwaSplashImage(session, uri); return receiveTwaSplashImage(session, uri);
} }
Log.w(TAG, "Unknown FilePurpose " + purpose); Log.w(TAG, "Unknown FilePurpose " + purpose);
......
...@@ -5,14 +5,18 @@ ...@@ -5,14 +5,18 @@
package org.chromium.chrome.browser.customtabs.dependency_injection; package org.chromium.chrome.browser.customtabs.dependency_injection;
import org.chromium.chrome.browser.browserservices.ClientAppDataRegister; import org.chromium.chrome.browser.browserservices.ClientAppDataRegister;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.TwaIntentHandlingStrategy;
import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider; import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider;
import org.chromium.chrome.browser.customtabs.CustomTabNightModeStateController; import org.chromium.chrome.browser.customtabs.CustomTabNightModeStateController;
import org.chromium.chrome.browser.customtabs.content.CustomTabIntentHandler.IntentIgnoringCriterion; import org.chromium.chrome.browser.customtabs.content.CustomTabIntentHandler.IntentIgnoringCriterion;
import org.chromium.chrome.browser.customtabs.content.CustomTabIntentHandlingStrategy; import org.chromium.chrome.browser.customtabs.content.CustomTabIntentHandlingStrategy;
import org.chromium.chrome.browser.customtabs.content.DefaultCustomTabIntentHandlingStrategy; import org.chromium.chrome.browser.customtabs.content.DefaultCustomTabIntentHandlingStrategy;
import org.chromium.chrome.browser.webapps.WebApkPostShareTargetNavigator;
import dagger.Lazy;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import dagger.Reusable;
/** /**
* Module for custom tab specific bindings. * Module for custom tab specific bindings.
...@@ -48,12 +52,19 @@ public class CustomTabActivityModule { ...@@ -48,12 +52,19 @@ public class CustomTabActivityModule {
@Provides @Provides
public CustomTabIntentHandlingStrategy provideIntentHandler( public CustomTabIntentHandlingStrategy provideIntentHandler(
DefaultCustomTabIntentHandlingStrategy defaultHandler) { Lazy<DefaultCustomTabIntentHandlingStrategy> defaultHandler,
return defaultHandler; Lazy<TwaIntentHandlingStrategy> twaHandler) {
return mIntentDataProvider.isTrustedWebActivity() ? twaHandler.get() : defaultHandler.get();
} }
@Provides @Provides
public IntentIgnoringCriterion provideIntentIgnoringCriterion() { public IntentIgnoringCriterion provideIntentIgnoringCriterion() {
return mIntentIgnoringCriterion; return mIntentIgnoringCriterion;
} }
@Provides
@Reusable
public WebApkPostShareTargetNavigator providePostShareTargetNavigator() {
return new WebApkPostShareTargetNavigator();
}
} }
...@@ -7,6 +7,7 @@ package org.chromium.chrome.browser.webapps; ...@@ -7,6 +7,7 @@ package org.chromium.chrome.browser.webapps;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.os.SystemClock; import android.os.SystemClock;
import android.text.TextUtils;
import org.chromium.base.VisibleForTesting; import org.chromium.base.VisibleForTesting;
import org.chromium.base.library_loader.LibraryLoader; import org.chromium.base.library_loader.LibraryLoader;
...@@ -182,8 +183,14 @@ public class WebApkActivity extends WebappActivity { ...@@ -182,8 +183,14 @@ public class WebApkActivity extends WebappActivity {
@Override @Override
protected boolean loadUrlIfPostShareTarget(WebappInfo webappInfo) { protected boolean loadUrlIfPostShareTarget(WebappInfo webappInfo) {
WebApkInfo webApkInfo = (WebApkInfo) webappInfo; WebApkInfo webApkInfo = (WebApkInfo) webappInfo;
return WebApkPostShareTargetNavigator.navigateIfPostShareTarget( WebApkInfo.ShareData shareData = webApkInfo.shareData();
webApkInfo, getActivityTab().getWebContents()); if (shareData == null || !TextUtils.equals(shareData.shareActivityClassName,
webApkInfo.shareTargetActivityName())) {
return false;
}
return new WebApkPostShareTargetNavigator().navigateIfPostShareTarget(
webApkInfo.url(), webApkInfo.shareTarget(), shareData,
getActivityTab().getWebContents());
} }
@Override @Override
......
...@@ -49,7 +49,7 @@ import java.util.Map; ...@@ -49,7 +49,7 @@ import java.util.Map;
*/ */
public class WebApkInfo extends WebappInfo { public class WebApkInfo extends WebappInfo {
// A class that stores share information from share intent. // A class that stores share information from share intent.
protected static class ShareData { public static class ShareData {
public String subject; public String subject;
public String text; public String text;
public ArrayList<Uri> files; public ArrayList<Uri> files;
......
...@@ -4,17 +4,19 @@ ...@@ -4,17 +4,19 @@
package org.chromium.chrome.browser.webapps; package org.chromium.chrome.browser.webapps;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.content_public.browser.WebContents; import org.chromium.content_public.browser.WebContents;
/** /**
* Perform navigation for share target with POST request. * Perform navigation for share target with POST request.
*/ */
public class WebApkPostShareTargetNavigator { public class WebApkPostShareTargetNavigator {
public static boolean navigateIfPostShareTarget( public boolean navigateIfPostShareTarget(
WebApkInfo webApkInfo, WebContents webContents) { String url,
WebApkInfo.ShareTarget target,
WebApkInfo.ShareData data, WebContents webContents) {
WebApkShareTargetUtil.PostData postData = WebApkShareTargetUtil.PostData postData =
WebApkShareTargetUtil.computePostData(webApkInfo.shareTargetActivityName(), WebApkShareTargetUtil.computePostData(target, data);
webApkInfo.shareTarget(), webApkInfo.shareData());
if (postData == null) { if (postData == null) {
return false; return false;
} }
...@@ -24,14 +26,18 @@ public class WebApkPostShareTargetNavigator { ...@@ -24,14 +26,18 @@ public class WebApkPostShareTargetNavigator {
isValueFileUris[i] = postData.isValueFileUri.get(i); isValueFileUris[i] = postData.isValueFileUri.get(i);
} }
nativeLoadViewForShareTargetPost(postData.isMultipartEncoding, WebApkPostShareTargetNavigatorJni.get().nativeLoadViewForShareTargetPost(
postData.names.toArray(new String[0]), postData.values.toArray(new String[0]), postData.isMultipartEncoding, postData.names.toArray(new String[0]),
isValueFileUris, postData.filenames.toArray(new String[0]), postData.values.toArray(new String[0]), isValueFileUris,
postData.types.toArray(new String[0]), webApkInfo.url(), webContents); postData.filenames.toArray(new String[0]), postData.types.toArray(new String[0]),
url, webContents);
return true; return true;
} }
private static native void nativeLoadViewForShareTargetPost(boolean isMultipartEncoding, @NativeMethods
String[] names, String[] values, boolean[] isValueFileUris, String[] filenames, public interface Natives {
String[] types, String startUrl, WebContents webContents); void nativeLoadViewForShareTargetPost(boolean isMultipartEncoding,
String[] names, String[] values, boolean[] isValueFileUris, String[] filenames,
String[] types, String startUrl, WebContents webContents);
}
} }
...@@ -150,7 +150,7 @@ public class WebApkShareTargetUtil { ...@@ -150,7 +150,7 @@ public class WebApkShareTargetUtil {
for (Uri fileUri : shareFiles) { for (Uri fileUri : shareFiles) {
String fileType, fileName; String fileType, fileName;
try (StrictModeContext strictModeContextUnused = StrictModeContext.allowDiskReads()) { try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
fileType = getFileTypeFromContentUri(fileUri); fileType = getFileTypeFromContentUri(fileUri);
fileName = getFileNameFromContentUri(fileUri); fileName = getFileNameFromContentUri(fileUri);
} }
...@@ -193,10 +193,9 @@ public class WebApkShareTargetUtil { ...@@ -193,10 +193,9 @@ public class WebApkShareTargetUtil {
} }
} }
protected static PostData computePostData(String shareTargetActivityName, protected static PostData computePostData(
WebApkInfo.ShareTarget shareTarget, WebApkInfo.ShareData shareData) { WebApkInfo.ShareTarget shareTarget, WebApkInfo.ShareData shareData) {
if (shareTarget == null || !shareTarget.isShareMethodPost() || shareData == null if (shareTarget == null || !shareTarget.isShareMethodPost() || shareData == null) {
|| !shareData.shareActivityClassName.equals(shareTargetActivityName)) {
return null; return null;
} }
......
// Copyright 2019 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 static org.chromium.chrome.browser.browserservices.TrustedWebActivityTestUtil.createSession;
import static org.chromium.chrome.browser.browserservices.TrustedWebActivityTestUtil.createTrustedWebActivityIntent;
import static org.chromium.chrome.browser.browserservices.TrustedWebActivityTestUtil.spoofVerification;
import android.content.Intent;
import android.support.test.filters.MediumTest;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.ContextUtils;
import org.chromium.base.library_loader.LibraryLoader;
import org.chromium.base.library_loader.LibraryProcessType;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.webapps.WebApkPostShareTargetNavigator;
import org.chromium.chrome.browser.webapps.WebApkPostShareTargetNavigatorJni;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.util.ChromeTabUtils;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.net.test.EmbeddedTestServerRule;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import androidx.browser.trusted.TrustedWebActivityIntentBuilder;
import androidx.browser.trusted.sharing.ShareData;
import androidx.browser.trusted.sharing.ShareTarget;
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class TrustedWebActivityShareTargetTest {
// We are not actually navigating to POST target, so ok not to use test pages here.
private static final ShareTarget POST_SHARE_TARGET =
new ShareTarget("https://pwa.rocks/share.html", "POST", null,
new ShareTarget.Params("received_title", "received_text", null));
private static final ShareTarget UNVERIFIED_ORIGIN_POST_SHARE_TARGET =
new ShareTarget("https://random.website/share.html", "POST", null,
new ShareTarget.Params("received_title", "received_text", null));
@Rule
public CustomTabActivityTestRule mCustomTabActivityTestRule = new CustomTabActivityTestRule();
@Rule
public EmbeddedTestServerRule mEmbeddedTestServerRule = new EmbeddedTestServerRule();
@Rule
public JniMocker mJniMocker = new JniMocker();
private static final String TEST_PAGE = "/chrome/test/data/android/google.html";
private static final String SHARE_TEST_PAGE = "/chrome/test/data/android/about.html";
private static final String PACKAGE_NAME =
ContextUtils.getApplicationContext().getPackageName();
private final MockPostNavigatorNatives mPostNavigatorNatives = new MockPostNavigatorNatives();
private final CallbackHelper mPostNavigatorCallback = new CallbackHelper();
private Intent mIntent;
private ShareTarget mGetShareTarget;
// Expected URL when using mGetShareTarget as input.
private String mExpectedGetRequestUrl;
@Before
public void setUp() throws Exception {
mJniMocker.mock(WebApkPostShareTargetNavigatorJni.TEST_HOOKS, mPostNavigatorNatives);
LibraryLoader.getInstance().ensureInitialized(LibraryProcessType.PROCESS_BROWSER);
mEmbeddedTestServerRule.setServerUsesHttps(true);
String testPage = mEmbeddedTestServerRule.getServer().getURL(TEST_PAGE);
String shareTestPage = mEmbeddedTestServerRule.getServer().getURL(SHARE_TEST_PAGE);
mGetShareTarget = new ShareTarget(shareTestPage, "GET", null,
new ShareTarget.Params("received_title", "received_text", null));
mExpectedGetRequestUrl = shareTestPage
+ "?received_title=test_title&received_text=test_text";
spoofVerification(PACKAGE_NAME, testPage);
spoofVerification(PACKAGE_NAME, "https://pwa.rocks");
mIntent = createTrustedWebActivityIntent(testPage);
createSession(mIntent, PACKAGE_NAME);
}
@Test
@MediumTest
public void sharesDataWithGet_FromInitialIntent() throws Exception {
putShareData(mIntent, mGetShareTarget);
mCustomTabActivityTestRule.startCustomTabActivityWithIntent(mIntent);
assertGetRequestUrl(mExpectedGetRequestUrl);
}
@Test
@MediumTest
public void sharesDataWithPost_FromInitialIntent() throws Exception {
putShareData(mIntent, POST_SHARE_TARGET);
mCustomTabActivityTestRule.startCustomTabActivityWithIntent(mIntent);
assertPostNavigatorCalled();
}
@Test
@MediumTest
public void sharesDataWithPost_FromNewIntent() throws Exception {
mCustomTabActivityTestRule.startCustomTabActivityWithIntent(mIntent);
putShareData(mIntent, POST_SHARE_TARGET);
deliverNewIntent(mIntent);
assertPostNavigatorCalled();
}
@Test
@MediumTest
public void sharesDataWithGet_FromNewIntent() throws Exception {
mCustomTabActivityTestRule.startCustomTabActivityWithIntent(mIntent);
putShareData(mIntent, mGetShareTarget);
deliverNewIntent(mIntent);
assertGetRequestUrl(mExpectedGetRequestUrl);
}
@Test(expected = TimeoutException.class)
@MediumTest
public void doesntShareWithUnverifiedOrigin() throws Exception {
putShareData(mIntent, UNVERIFIED_ORIGIN_POST_SHARE_TARGET);
mCustomTabActivityTestRule.startCustomTabActivityWithIntent(mIntent);
mPostNavigatorCallback.waitForCallback(0, 1, 1000, TimeUnit.MILLISECONDS);
}
private void putShareData(Intent intent, ShareTarget shareTarget) {
ShareData shareData = new ShareData("test_title", "test_text", Collections.emptyList());
intent.putExtra(TrustedWebActivityIntentBuilder.EXTRA_SHARE_DATA, shareData.toBundle());
intent.putExtra(TrustedWebActivityIntentBuilder.EXTRA_SHARE_TARGET, shareTarget.toBundle());
}
private void assertGetRequestUrl(final String expectedGetRequestUrl)
throws InterruptedException {
// startCustomTabActivityWithIntent waits for native, so the tab must be present already.
Tab tab = mCustomTabActivityTestRule.getActivity().getActivityTab();
ChromeTabUtils.waitForTabPageLoaded(tab, expectedGetRequestUrl);
}
private void assertPostNavigatorCalled() throws InterruptedException, TimeoutException {
// Constructing POST requests is unit-tested elsewhere.
// Here we only care that the request reaches the native code.
mPostNavigatorCallback.waitForCallback(0);
}
private void deliverNewIntent(Intent intent) {
// Delivering intents to existing CustomTabActivity in tests is error-prone and out of scope
// of these tests. Thus calling onNewIntent directly.
TestThreadUtils.runOnUiThreadBlocking(
() -> mCustomTabActivityTestRule.getActivity().onNewIntent(intent));
}
private class MockPostNavigatorNatives implements WebApkPostShareTargetNavigator.Natives {
@Override
public void nativeLoadViewForShareTargetPost(boolean isMultipartEncoding, String[] names,
String[] values, boolean[] isValueFileUris, String[] filenames, String[] types,
String startUrl, WebContents webContents) {
mPostNavigatorCallback.notifyCalled();
}
}
}
...@@ -7,11 +7,14 @@ package org.chromium.chrome.browser.browserservices; ...@@ -7,11 +7,14 @@ package org.chromium.chrome.browser.browserservices;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.chromium.chrome.browser.browserservices.TrustedWebActivityTestUtil.createSession;
import static org.chromium.chrome.browser.browserservices.TrustedWebActivityTestUtil.createTrustedWebActivityIntent;
import static org.chromium.chrome.browser.browserservices.TrustedWebActivityTestUtil.isTrustedWebActivity;
import static org.chromium.chrome.browser.browserservices.TrustedWebActivityTestUtil.spoofVerification;
import android.content.Intent; import android.content.Intent;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest; import android.support.test.filters.MediumTest;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
...@@ -24,18 +27,11 @@ import org.chromium.base.library_loader.ProcessInitException; ...@@ -24,18 +27,11 @@ import org.chromium.base.library_loader.ProcessInitException;
import org.chromium.base.test.util.CommandLineFlags; import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.chrome.browser.ChromeSwitches; import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule; import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule;
import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
import org.chromium.chrome.browser.customtabs.CustomTabsTestUtils;
import org.chromium.chrome.browser.tab.TabBrowserControlsState;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner; import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.content_public.browser.test.util.TestThreadUtils; import org.chromium.net.test.EmbeddedTestServerRule;
import org.chromium.net.test.EmbeddedTestServer;
import org.chromium.net.test.ServerCertificate;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import androidx.browser.customtabs.CustomTabsService;
import androidx.browser.customtabs.CustomTabsSessionToken;
import androidx.browser.customtabs.TrustedWebUtils; import androidx.browser.customtabs.TrustedWebUtils;
/** /**
...@@ -48,12 +44,13 @@ public class TrustedWebActivityTest { ...@@ -48,12 +44,13 @@ public class TrustedWebActivityTest {
// TODO(peconn): Add test for navigating away from the trusted origin. // TODO(peconn): Add test for navigating away from the trusted origin.
@Rule @Rule
public CustomTabActivityTestRule mCustomTabActivityTestRule = new CustomTabActivityTestRule(); public CustomTabActivityTestRule mCustomTabActivityTestRule = new CustomTabActivityTestRule();
@Rule
public EmbeddedTestServerRule mEmbeddedTestServerRule = new EmbeddedTestServerRule();
private static final String TEST_PAGE = "/chrome/test/data/android/google.html"; private static final String TEST_PAGE = "/chrome/test/data/android/google.html";
private static final String PACKAGE_NAME = private static final String PACKAGE_NAME =
ContextUtils.getApplicationContext().getPackageName(); ContextUtils.getApplicationContext().getPackageName();
private EmbeddedTestServer mTestServer;
private String mTestPage; private String mTestPage;
@Before @Before
...@@ -61,48 +58,8 @@ public class TrustedWebActivityTest { ...@@ -61,48 +58,8 @@ public class TrustedWebActivityTest {
// Native needs to be initialized to start the test server. // Native needs to be initialized to start the test server.
LibraryLoader.getInstance().ensureInitialized(LibraryProcessType.PROCESS_BROWSER); LibraryLoader.getInstance().ensureInitialized(LibraryProcessType.PROCESS_BROWSER);
// TWAs only work with HTTPS. mEmbeddedTestServerRule.setServerUsesHttps(true); // TWAs only work with HTTPS.
mTestServer = EmbeddedTestServer.createAndStartHTTPSServer( mTestPage = mEmbeddedTestServerRule.getServer().getURL(TEST_PAGE);
InstrumentationRegistry.getInstrumentation().getContext(),
ServerCertificate.CERT_OK);
mTestPage = mTestServer.getURL(TEST_PAGE);
}
/** Creates an Intent that will launch a Custom Tab to the given |url|. */
private static Intent createTrustedWebActivityIntent(String url) {
Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent(
InstrumentationRegistry.getTargetContext(), url);
intent.putExtra(TrustedWebUtils.EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY, true);
return intent;
}
/** Caches a successful verification for the given |packageName| and |url|. */
private static void spoofVerification(String packageName, String url) {
TestThreadUtils.runOnUiThreadBlocking(
() -> OriginVerifier.addVerificationOverride(packageName, new Origin(url),
CustomTabsService.RELATION_HANDLE_ALL_URLS));
}
/** Creates a Custom Tabs Session from the Intent, specifying the |packageName|. */
private static void createSession(Intent intent, String packageName)
throws TimeoutException, InterruptedException {
CustomTabsSessionToken token = CustomTabsSessionToken.getSessionTokenFromIntent(intent);
CustomTabsConnection connection = CustomTabsTestUtils.warmUpAndWait();
connection.newSession(token);
connection.overridePackageNameForSessionForTesting(token, packageName);
}
private boolean isTrustedWebActivity() {
// A key part of the Trusted Web Activity UI is the lack of browser controls.
return !TestThreadUtils.runOnUiThreadBlockingNoException(
() -> TabBrowserControlsState
.get(mCustomTabActivityTestRule.getActivity().getActivityTab())
.canShow());
}
@After
public void tearDown() throws TimeoutException {
mTestServer.stopAndDestroyServer();
} }
@Test @Test
...@@ -114,7 +71,7 @@ public class TrustedWebActivityTest { ...@@ -114,7 +71,7 @@ public class TrustedWebActivityTest {
mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
assertTrue(isTrustedWebActivity()); assertTrue(isTrustedWebActivity(mCustomTabActivityTestRule.getActivity()));
} }
@Test @Test
...@@ -128,7 +85,7 @@ public class TrustedWebActivityTest { ...@@ -128,7 +85,7 @@ public class TrustedWebActivityTest {
mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
assertFalse(isTrustedWebActivity()); assertFalse(isTrustedWebActivity(mCustomTabActivityTestRule.getActivity()));
} }
@Test @Test
...@@ -139,6 +96,6 @@ public class TrustedWebActivityTest { ...@@ -139,6 +96,6 @@ public class TrustedWebActivityTest {
mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
assertFalse(isTrustedWebActivity()); assertFalse(isTrustedWebActivity(mCustomTabActivityTestRule.getActivity()));
} }
} }
// Copyright 2019 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.content.Intent;
import android.support.test.InstrumentationRegistry;
import org.chromium.chrome.browser.customtabs.CustomTabActivity;
import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
import org.chromium.chrome.browser.customtabs.CustomTabsTestUtils;
import org.chromium.chrome.browser.tab.TabBrowserControlsState;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import java.util.concurrent.TimeoutException;
import androidx.browser.customtabs.CustomTabsService;
import androidx.browser.customtabs.CustomTabsSessionToken;
import androidx.browser.customtabs.TrustedWebUtils;
public class TrustedWebActivityTestUtil {
/** Creates an Intent that will launch a Custom Tab to the given |url|. */
public static Intent createTrustedWebActivityIntent(String url) {
Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent(
InstrumentationRegistry.getTargetContext(), url);
intent.putExtra(TrustedWebUtils.EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY, true);
return intent;
}
/** Caches a successful verification for the given |packageName| and |url|. */
public static void spoofVerification(String packageName, String url) {
TestThreadUtils.runOnUiThreadBlocking(
() -> OriginVerifier.addVerificationOverride(packageName, new Origin(url),
CustomTabsService.RELATION_HANDLE_ALL_URLS));
}
/** Creates a Custom Tabs Session from the Intent, specifying the |packageName|. */
public static void createSession(Intent intent, String packageName)
throws TimeoutException, InterruptedException {
CustomTabsSessionToken token = CustomTabsSessionToken.getSessionTokenFromIntent(intent);
CustomTabsConnection connection = CustomTabsTestUtils.warmUpAndWait();
connection.newSession(token);
connection.overridePackageNameForSessionForTesting(token, packageName);
}
/** Checks if given instance of {@link CustomTabActivity} is a Trusted Web Activity. */
public static boolean isTrustedWebActivity(CustomTabActivity activity) {
// A key part of the Trusted Web Activity UI is the lack of browser controls.
return !TestThreadUtils.runOnUiThreadBlockingNoException(
() -> TabBrowserControlsState
.get(activity.getActivityTab())
.canShow());
}
}
...@@ -155,7 +155,7 @@ void NavigateShareTargetPost( ...@@ -155,7 +155,7 @@ void NavigateShareTargetPost(
} }
} // namespace webapk } // namespace webapk
void JNI_WebApkPostShareTargetNavigator_LoadViewForShareTargetPost( static void JNI_WebApkPostShareTargetNavigator_NativeLoadViewForShareTargetPost(
JNIEnv* env, JNIEnv* env,
const jboolean java_is_multipart_encoding, const jboolean java_is_multipart_encoding,
const JavaParamRef<jobjectArray>& java_names, const JavaParamRef<jobjectArray>& java_names,
......
...@@ -25,7 +25,7 @@ public class TestTrustedWebActivityService extends TrustedWebActivityService { ...@@ -25,7 +25,7 @@ public class TestTrustedWebActivityService extends TrustedWebActivityService {
} }
@Override @Override
protected boolean notifyNotificationWithChannel(String platformTag, int platformId, public boolean notifyNotificationWithChannel(String platformTag, int platformId,
Notification notification, String channelName) { Notification notification, String channelName) {
MessengerService.sMessageHandler MessengerService.sMessageHandler
.recordNotifyNotification(platformTag, platformId, channelName); .recordNotifyNotification(platformTag, platformId, channelName);
...@@ -33,12 +33,12 @@ public class TestTrustedWebActivityService extends TrustedWebActivityService { ...@@ -33,12 +33,12 @@ public class TestTrustedWebActivityService extends TrustedWebActivityService {
} }
@Override @Override
protected void cancelNotification(String platformTag, int platformId) { public void cancelNotification(String platformTag, int platformId) {
MessengerService.sMessageHandler.recordCancelNotification(platformTag, platformId); MessengerService.sMessageHandler.recordCancelNotification(platformTag, platformId);
} }
@Override @Override
protected int getSmallIconId() { public int getSmallIconId() {
MessengerService.sMessageHandler.recordGetSmallIconId(); MessengerService.sMessageHandler.recordGetSmallIconId();
return SMALL_ICON_ID; return SMALL_ICON_ID;
} }
......
...@@ -23,7 +23,11 @@ android_library("androidx_browser_java") { ...@@ -23,7 +23,11 @@ android_library("androidx_browser_java") {
"./src/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityServiceWrapper.java", "./src/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityServiceWrapper.java",
"./src/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityServiceConnectionManager.java", "./src/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityServiceConnectionManager.java",
"./src/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityService.java", "./src/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityService.java",
"./src/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityBuilder.java", "./src/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityIntent.java",
"./src/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityIntentBuilder.java",
"./src/browser/src/main/java/androidx/browser/trusted/sharing/ShareData.java",
"./src/browser/src/main/java/androidx/browser/trusted/sharing/ShareTarget.java",
"./src/browser/src/main/java/androidx/browser/trusted/splashscreens/SplashScreenParamKey.java",
] ]
deps = [ deps = [
"//third_party/android_deps:android_support_v7_appcompat_java", "//third_party/android_deps:android_support_v7_appcompat_java",
......
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