Commit 641ed0eb authored by Michael van Ouwerkerk's avatar Michael van Ouwerkerk Committed by Commit Bot

Reland WebAPK Site Settings Shortcut using startActivityForResult.

Bug: 1138210
Change-Id: I95d57fb3fe0802298fad44d4415aaac7e87871bd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2510250Reviewed-by: default avatarPeter Conn <peconn@chromium.org>
Reviewed-by: default avatarYaron Friedman <yfriedman@chromium.org>
Commit-Queue: Michael van Ouwerkerk <mvanouwerkerk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#824882}
parent cd7c8f92
...@@ -75,6 +75,7 @@ chrome_test_java_sources = [ ...@@ -75,6 +75,7 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkPersonalizedSigninPromoTest.java", "javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkPersonalizedSigninPromoTest.java",
"javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java", "javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java",
"javatests/src/org/chromium/chrome/browser/bookmarks/bottomsheet/BookmarkBottomSheetTest.java", "javatests/src/org/chromium/chrome/browser/bookmarks/bottomsheet/BookmarkBottomSheetTest.java",
"javatests/src/org/chromium/chrome/browser/browserservices/ManageTrustedWebActivityDataActivityTest.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/QualityEnforcerTest.java", "javatests/src/org/chromium/chrome/browser/browserservices/QualityEnforcerTest.java",
"javatests/src/org/chromium/chrome/browser/browserservices/RunningInChromeTest.java", "javatests/src/org/chromium/chrome/browser/browserservices/RunningInChromeTest.java",
......
...@@ -1116,6 +1116,7 @@ ...@@ -1116,6 +1116,7 @@
<category android:name="androidx.browser.customtabs.category.NavBarColorCustomization"/> <category android:name="androidx.browser.customtabs.category.NavBarColorCustomization"/>
<category android:name="androidx.browser.trusted.category.ImmersiveMode"/> <category android:name="androidx.browser.trusted.category.ImmersiveMode"/>
<category android:name="androidx.browser.trusted.category.LaunchSiteSettings"/> <category android:name="androidx.browser.trusted.category.LaunchSiteSettings"/>
<category android:name="androidx.browser.trusted.category.LaunchWebApkSiteSettings"/>
<category android:name="androidx.browser.trusted.category.TrustedWebActivities"/> <category android:name="androidx.browser.trusted.category.TrustedWebActivities"/>
<category android:name="androidx.browser.trusted.category.TrustedWebActivitySplashScreensV1"/> <category android:name="androidx.browser.trusted.category.TrustedWebActivitySplashScreensV1"/>
<category android:name="androidx.browser.trusted.category.WebShareTargetV2"/> <category android:name="androidx.browser.trusted.category.WebShareTargetV2"/>
......
...@@ -1018,6 +1018,7 @@ ...@@ -1018,6 +1018,7 @@
<category android:name="androidx.browser.customtabs.category.NavBarColorCustomization"/> <category android:name="androidx.browser.customtabs.category.NavBarColorCustomization"/>
<category android:name="androidx.browser.trusted.category.ImmersiveMode"/> <category android:name="androidx.browser.trusted.category.ImmersiveMode"/>
<category android:name="androidx.browser.trusted.category.LaunchSiteSettings"/> <category android:name="androidx.browser.trusted.category.LaunchSiteSettings"/>
<category android:name="androidx.browser.trusted.category.LaunchWebApkSiteSettings"/>
<category android:name="androidx.browser.trusted.category.TrustedWebActivities"/> <category android:name="androidx.browser.trusted.category.TrustedWebActivities"/>
<category android:name="androidx.browser.trusted.category.TrustedWebActivitySplashScreensV1"/> <category android:name="androidx.browser.trusted.category.TrustedWebActivitySplashScreensV1"/>
<category android:name="androidx.browser.trusted.category.WebShareTargetV2"/> <category android:name="androidx.browser.trusted.category.WebShareTargetV2"/>
......
...@@ -1069,6 +1069,7 @@ by a child template that "extends" this file. ...@@ -1069,6 +1069,7 @@ by a child template that "extends" this file.
<category android:name="androidx.browser.customtabs.category.NavBarColorCustomization"/> <category android:name="androidx.browser.customtabs.category.NavBarColorCustomization"/>
<category android:name="androidx.browser.trusted.category.ImmersiveMode"/> <category android:name="androidx.browser.trusted.category.ImmersiveMode"/>
<category android:name="androidx.browser.trusted.category.LaunchSiteSettings" /> <category android:name="androidx.browser.trusted.category.LaunchSiteSettings" />
<category android:name="androidx.browser.trusted.category.LaunchWebApkSiteSettings"/>
<category android:name="androidx.browser.trusted.category.TrustedWebActivities"/> <category android:name="androidx.browser.trusted.category.TrustedWebActivities"/>
<category android:name="androidx.browser.trusted.category.TrustedWebActivitySplashScreensV1"/> <category android:name="androidx.browser.trusted.category.TrustedWebActivitySplashScreensV1"/>
<category android:name="androidx.browser.trusted.category.WebShareTargetV2"/> <category android:name="androidx.browser.trusted.category.WebShareTargetV2"/>
......
...@@ -7,6 +7,7 @@ package org.chromium.chrome.browser.browserservices; ...@@ -7,6 +7,7 @@ package org.chromium.chrome.browser.browserservices;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.browser.customtabs.CustomTabsSessionToken; import androidx.browser.customtabs.CustomTabsSessionToken;
...@@ -14,6 +15,7 @@ import org.chromium.base.Log; ...@@ -14,6 +15,7 @@ import org.chromium.base.Log;
import org.chromium.chrome.browser.ChromeApplication; import org.chromium.chrome.browser.ChromeApplication;
import org.chromium.chrome.browser.customtabs.CustomTabsConnection; import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
import org.chromium.chrome.browser.init.ChromeBrowserInitializer; import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
import org.chromium.webapk.lib.common.WebApkConstants;
/** /**
* Launched by Trusted Web Activity apps when the user clears data. * Launched by Trusted Web Activity apps when the user clears data.
...@@ -25,15 +27,20 @@ public class ManageTrustedWebActivityDataActivity extends AppCompatActivity { ...@@ -25,15 +27,20 @@ public class ManageTrustedWebActivityDataActivity extends AppCompatActivity {
private static final String TAG = "TwaDataActivity"; private static final String TAG = "TwaDataActivity";
private static String sMockCallingPackage;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
launchSettings();
String urlToLaunchSettingsFor = getIntent().getData().toString();
boolean isWebApk = getIntent().getBooleanExtra(WebApkConstants.EXTRA_IS_WEBAPK, false);
launchSettings(urlToLaunchSettingsFor, isWebApk);
finish(); finish();
} }
private void launchSettings() { private void launchSettings(@Nullable String urlToLaunchSettingsFor, boolean isWebApk) {
String packageName = getClientPackageName(); String packageName = getClientPackageName(isWebApk);
if (packageName == null) { if (packageName == null) {
logNoPackageName(); logNoPackageName();
finish(); finish();
...@@ -41,11 +48,26 @@ public class ManageTrustedWebActivityDataActivity extends AppCompatActivity { ...@@ -41,11 +48,26 @@ public class ManageTrustedWebActivityDataActivity extends AppCompatActivity {
} }
new TrustedWebActivityUmaRecorder(ChromeBrowserInitializer.getInstance()) new TrustedWebActivityUmaRecorder(ChromeBrowserInitializer.getInstance())
.recordOpenedSettingsViaManageSpace(); .recordOpenedSettingsViaManageSpace();
TrustedWebActivitySettingsLauncher.launchForPackageName(this, packageName);
if (isWebApk) {
TrustedWebActivitySettingsLauncher.launchForWebApkPackageName(
this, packageName, urlToLaunchSettingsFor);
} else {
TrustedWebActivitySettingsLauncher.launchForPackageName(this, packageName);
}
}
@VisibleForTesting
public static void setCallingPackageForTesting(String packageName) {
sMockCallingPackage = packageName;
} }
@Nullable @Nullable
private String getClientPackageName() { private String getClientPackageName(boolean isWebApk) {
if (isWebApk) {
return sMockCallingPackage != null ? sMockCallingPackage : getCallingPackage();
}
CustomTabsSessionToken session = CustomTabsSessionToken session =
CustomTabsSessionToken.getSessionTokenFromIntent(getIntent()); CustomTabsSessionToken.getSessionTokenFromIntent(getIntent());
if (session == null) { if (session == null) {
......
...@@ -13,10 +13,12 @@ import org.chromium.base.Log; ...@@ -13,10 +13,12 @@ import org.chromium.base.Log;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.settings.SettingsLauncher; import org.chromium.chrome.browser.settings.SettingsLauncher;
import org.chromium.chrome.browser.settings.SettingsLauncherImpl; import org.chromium.chrome.browser.settings.SettingsLauncherImpl;
import org.chromium.chrome.browser.webapps.ChromeWebApkHost;
import org.chromium.components.browser_ui.site_settings.AllSiteSettings; import org.chromium.components.browser_ui.site_settings.AllSiteSettings;
import org.chromium.components.browser_ui.site_settings.SettingsNavigationSource; import org.chromium.components.browser_ui.site_settings.SettingsNavigationSource;
import org.chromium.components.browser_ui.site_settings.SingleWebsiteSettings; import org.chromium.components.browser_ui.site_settings.SingleWebsiteSettings;
import org.chromium.components.browser_ui.site_settings.SiteSettingsCategory; import org.chromium.components.browser_ui.site_settings.SiteSettingsCategory;
import org.chromium.components.webapk.lib.client.WebApkValidator;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
...@@ -33,13 +35,8 @@ public class TrustedWebActivitySettingsLauncher { ...@@ -33,13 +35,8 @@ public class TrustedWebActivitySettingsLauncher {
* able to work with each of them. * able to work with each of them.
*/ */
public static void launchForPackageName(Context context, String packageName) { public static void launchForPackageName(Context context, String packageName) {
int applicationUid; Integer applicationUid = getApplicationUid(context, packageName);
try { if (applicationUid == null) return;
applicationUid = context.getPackageManager().getApplicationInfo(packageName, 0).uid;
} catch (PackageManager.NameNotFoundException e) {
Log.d(TAG, "Package " + packageName + " not found");
return;
}
ClientAppDataRegister register = new ClientAppDataRegister(); ClientAppDataRegister register = new ClientAppDataRegister();
Collection<String> domains = register.getDomainsForRegisteredUid(applicationUid); Collection<String> domains = register.getDomainsForRegisteredUid(applicationUid);
...@@ -51,6 +48,33 @@ public class TrustedWebActivitySettingsLauncher { ...@@ -51,6 +48,33 @@ public class TrustedWebActivitySettingsLauncher {
launch(context, origins, domains); launch(context, origins, domains);
} }
/**
* Launches site-settings for a WebApk with a given package name and associated url.
*/
public static void launchForWebApkPackageName(
Context context, String packageName, String webApkUrl) {
if (!WebApkValidator.canWebApkHandleUrl(context, packageName, webApkUrl)) {
Log.d(TAG, "WebApk " + packageName + " can't handle url " + webApkUrl);
return;
}
if (getApplicationUid(context, packageName) == null) return;
// Handle the case when settings are selected but Chrome was not running.
ChromeWebApkHost.init();
openSingleWebsitePrefs(context, webApkUrl);
}
private static Integer getApplicationUid(Context context, String packageName) {
int applicationUid;
try {
applicationUid = context.getPackageManager().getApplicationInfo(packageName, 0).uid;
} catch (PackageManager.NameNotFoundException e) {
Log.d(TAG, "Package " + packageName + " not found");
return null;
}
return applicationUid;
}
/** /**
* Same as above, but with list of associated origins and domains already retrieved. * Same as above, but with list of associated origins and domains already retrieved.
*/ */
......
// Copyright 2020 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.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.support.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Matchers;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.components.webapk.lib.client.WebApkValidator;
import org.chromium.content_public.browser.test.util.Criteria;
import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.webapk.lib.common.WebApkConstants;
/**
* Instrumentation tests for launching site settings for WebApks.
* Site settings are added as a dynamic android shortcut.
* The shortcut launches a {@link ManageTrustedWebActivityDataActivity}
* intent that validates the WebApk and launches the chromium SettingsActivity.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class ManageTrustedWebActivityDataActivityTest {
private static final String SETTINGS_ACTIVITY_NAME =
"org.chromium.chrome.browser.settings.SettingsActivity";
private static final String WEBAPK_TEST_URL = "https://www.example.com";
private static final String TEST_PACKAGE_NAME =
InstrumentationRegistry.getTargetContext().getPackageName();
@Test
@MediumTest
public void launchesWebApkSiteSettings() throws Exception {
WebApkValidator.setDisableValidationForTesting(true);
ManageTrustedWebActivityDataActivity.setCallingPackageForTesting(TEST_PACKAGE_NAME);
TrustedWebActivityTestUtil.spoofVerification(TEST_PACKAGE_NAME, WEBAPK_TEST_URL);
launchSettings(TEST_PACKAGE_NAME, Uri.parse(WEBAPK_TEST_URL));
// Check settings activity is running.
CriteriaHelper.pollUiThread(() -> {
try {
Criteria.checkThat("Site settings activity was not launched",
siteSettingsActivityRunning(), Matchers.is(true));
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
});
}
private boolean siteSettingsActivityRunning() throws PackageManager.NameNotFoundException {
for (Activity a : ApplicationStatus.getRunningActivities()) {
String activityName =
a.getPackageManager().getActivityInfo(a.getComponentName(), 0).name;
if (activityName.equals(SETTINGS_ACTIVITY_NAME)) {
return true;
}
}
return false;
}
private static void launchSettings(String packageName, Uri uri) {
Intent intent = new Intent();
intent.setAction(
"android.support.customtabs.action.ACTION_MANAGE_TRUSTED_WEB_ACTIVITY_DATA");
intent.setPackage(packageName);
intent.setData(uri);
intent.putExtra(WebApkConstants.EXTRA_IS_WEBAPK, true);
// The following flag is required because the test starts the intent outside of an activity.
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
}
}
...@@ -33,6 +33,7 @@ public final class WebApkConstants { ...@@ -33,6 +33,7 @@ public final class WebApkConstants {
"org.chromium.chrome.browser.webapk.splash_provided_by_webapk"; "org.chromium.chrome.browser.webapk.splash_provided_by_webapk";
// Tells the host browser to relaunch the WebAPK. // Tells the host browser to relaunch the WebAPK.
public static final String EXTRA_RELAUNCH = "org.chromium.webapk.relaunch"; public static final String EXTRA_RELAUNCH = "org.chromium.webapk.relaunch";
public static final String EXTRA_IS_WEBAPK = "org.chromium.webapk.is_webapk";
// Must be kept in sync with chrome/browser/android/shortcut_info.h. // Must be kept in sync with chrome/browser/android/shortcut_info.h.
public static final int SHORTCUT_SOURCE_UNKNOWN = 0; public static final int SHORTCUT_SOURCE_UNKNOWN = 0;
......
...@@ -32,6 +32,10 @@ ...@@ -32,6 +32,10 @@
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
</intent> </intent>
<intent>
<action android:name="android.support.customtabs.action.ACTION_MANAGE_TRUSTED_WEB_ACTIVITY_DATA" />
<data android:scheme="https" />
</intent>
</queries> </queries>
<application <application
...@@ -59,6 +63,10 @@ ...@@ -59,6 +63,10 @@
{{{raw_intent_filters}}} {{{raw_intent_filters}}}
</activity> </activity>
<activity android:name="org.chromium.webapk.shell_apk.ManageDataLauncherActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
</activity>
<activity android:name="org.chromium.webapk.shell_apk.h2o.H2OOpaqueMainActivity" <activity android:name="org.chromium.webapk.shell_apk.h2o.H2OOpaqueMainActivity"
android:theme="@style/SplashTheme" android:theme="@style/SplashTheme"
android:relinquishTaskIdentity="true" android:relinquishTaskIdentity="true"
......
...@@ -61,6 +61,7 @@ template("webapk_java") { ...@@ -61,6 +61,7 @@ template("webapk_java") {
"src/org/chromium/webapk/shell_apk/IdentityService.java", "src/org/chromium/webapk/shell_apk/IdentityService.java",
"src/org/chromium/webapk/shell_apk/InstallHostBrowserDialog.java", "src/org/chromium/webapk/shell_apk/InstallHostBrowserDialog.java",
"src/org/chromium/webapk/shell_apk/LaunchHostBrowserSelector.java", "src/org/chromium/webapk/shell_apk/LaunchHostBrowserSelector.java",
"src/org/chromium/webapk/shell_apk/ManageDataLauncherActivity.java",
"src/org/chromium/webapk/shell_apk/TransparentLauncherActivity.java", "src/org/chromium/webapk/shell_apk/TransparentLauncherActivity.java",
"src/org/chromium/webapk/shell_apk/WebApkSharedPreferences.java", "src/org/chromium/webapk/shell_apk/WebApkSharedPreferences.java",
"src/org/chromium/webapk/shell_apk/WebApkUtils.java", "src/org/chromium/webapk/shell_apk/WebApkUtils.java",
...@@ -150,6 +151,7 @@ template("webapk_tmpl") { ...@@ -150,6 +151,7 @@ template("webapk_tmpl") {
android_resources(_resources_target_name) { android_resources(_resources_target_name) {
sources = [ sources = [
"res/drawable-hdpi/ic_site_settings.png",
"res/drawable-hdpi/last_resort_runtime_host_logo.png", "res/drawable-hdpi/last_resort_runtime_host_logo.png",
"res/drawable-hdpi/notification_badge.png", "res/drawable-hdpi/notification_badge.png",
"res/drawable-hdpi/shortcut_1_icon.png", "res/drawable-hdpi/shortcut_1_icon.png",
...@@ -157,24 +159,28 @@ template("webapk_tmpl") { ...@@ -157,24 +159,28 @@ template("webapk_tmpl") {
"res/drawable-hdpi/shortcut_3_icon.png", "res/drawable-hdpi/shortcut_3_icon.png",
"res/drawable-hdpi/shortcut_4_icon.png", "res/drawable-hdpi/shortcut_4_icon.png",
"res/drawable-hdpi/splash_icon.xml", "res/drawable-hdpi/splash_icon.xml",
"res/drawable-mdpi/ic_site_settings.png",
"res/drawable-mdpi/notification_badge.png", "res/drawable-mdpi/notification_badge.png",
"res/drawable-mdpi/shortcut_1_icon.png", "res/drawable-mdpi/shortcut_1_icon.png",
"res/drawable-mdpi/shortcut_2_icon.png", "res/drawable-mdpi/shortcut_2_icon.png",
"res/drawable-mdpi/shortcut_3_icon.png", "res/drawable-mdpi/shortcut_3_icon.png",
"res/drawable-mdpi/shortcut_4_icon.png", "res/drawable-mdpi/shortcut_4_icon.png",
"res/drawable-mdpi/splash_icon.xml", "res/drawable-mdpi/splash_icon.xml",
"res/drawable-xhdpi/ic_site_settings.png",
"res/drawable-xhdpi/notification_badge.png", "res/drawable-xhdpi/notification_badge.png",
"res/drawable-xhdpi/shortcut_1_icon.png", "res/drawable-xhdpi/shortcut_1_icon.png",
"res/drawable-xhdpi/shortcut_2_icon.png", "res/drawable-xhdpi/shortcut_2_icon.png",
"res/drawable-xhdpi/shortcut_3_icon.png", "res/drawable-xhdpi/shortcut_3_icon.png",
"res/drawable-xhdpi/shortcut_4_icon.png", "res/drawable-xhdpi/shortcut_4_icon.png",
"res/drawable-xhdpi/splash_icon.xml", "res/drawable-xhdpi/splash_icon.xml",
"res/drawable-xxhdpi/ic_site_settings.png",
"res/drawable-xxhdpi/notification_badge.png", "res/drawable-xxhdpi/notification_badge.png",
"res/drawable-xxhdpi/shortcut_1_icon.png", "res/drawable-xxhdpi/shortcut_1_icon.png",
"res/drawable-xxhdpi/shortcut_2_icon.png", "res/drawable-xxhdpi/shortcut_2_icon.png",
"res/drawable-xxhdpi/shortcut_3_icon.png", "res/drawable-xxhdpi/shortcut_3_icon.png",
"res/drawable-xxhdpi/shortcut_4_icon.png", "res/drawable-xxhdpi/shortcut_4_icon.png",
"res/drawable-xxhdpi/splash_icon.xml", "res/drawable-xxhdpi/splash_icon.xml",
"res/drawable-xxxhdpi/ic_site_settings.png",
"res/drawable-xxxhdpi/notification_badge.png", "res/drawable-xxxhdpi/notification_badge.png",
"res/drawable-xxxhdpi/shortcut_1_icon.png", "res/drawable-xxxhdpi/shortcut_1_icon.png",
"res/drawable-xxxhdpi/shortcut_2_icon.png", "res/drawable-xxxhdpi/shortcut_2_icon.png",
......
...@@ -12,4 +12,4 @@ ...@@ -12,4 +12,4 @@
# //chrome/android/webapk/shell_apk:webapk is changed. This includes # //chrome/android/webapk/shell_apk:webapk is changed. This includes
# Java files, Android resource files and AndroidManifest.xml. Does not affect # Java files, Android resource files and AndroidManifest.xml. Does not affect
# Chrome.apk # Chrome.apk
current_shell_apk_version = 132 current_shell_apk_version = 133
...@@ -4,6 +4,11 @@ ...@@ -4,6 +4,11 @@
package org.chromium.webapk.shell_apk.h2o; package org.chromium.webapk.shell_apk.h2o;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.chromium.webapk.shell_apk.ManageDataLauncherActivity.SITE_SETTINGS_SHORTCUT_ID;
import android.app.Activity; import android.app.Activity;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.content.ComponentName; import android.content.ComponentName;
...@@ -11,6 +16,9 @@ import android.content.Context; ...@@ -11,6 +16,9 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
...@@ -40,6 +48,8 @@ import org.chromium.webapk.shell_apk.WebApkSharedPreferences; ...@@ -40,6 +48,8 @@ import org.chromium.webapk.shell_apk.WebApkSharedPreferences;
import org.chromium.webapk.test.WebApkTestHelper; import org.chromium.webapk.test.WebApkTestHelper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/** Tests launching WebAPK. */ /** Tests launching WebAPK. */
@RunWith(LocalRobolectricTestRunner.class) @RunWith(LocalRobolectricTestRunner.class)
...@@ -48,9 +58,12 @@ public final class LaunchTest { ...@@ -48,9 +58,12 @@ public final class LaunchTest {
/** Values based on manifest specified in GN file. */ /** Values based on manifest specified in GN file. */
private static final String BROWSER_PACKAGE_NAME = "com.google.android.apps.chrome"; private static final String BROWSER_PACKAGE_NAME = "com.google.android.apps.chrome";
private static final String DEFAULT_START_URL = "https://pwa.rocks/"; private static final String DEFAULT_START_URL = "https://pwa.rocks/";
private static final String CATEGORY_LAUNCH_WEBAPK_SITE_SETTINGS =
"androidx.browser.trusted.category.LaunchWebApkSiteSettings";
/** Chromium version which does not support showing the splash screen within WebAPK. */ /** Chromium version which does not support showing the splash screen within WebAPK. */
private static final int BROWSER_H2O_INCOMPATIBLE_VERSION = 57; private static final int BROWSER_H2O_INCOMPATIBLE_VERSION = 57;
public static final int SITE_SETTINGS_COMPATIBLE_BROWSER_VERSION = 87;
private static String sWebApkPackageName; private static String sWebApkPackageName;
...@@ -89,8 +102,8 @@ public final class LaunchTest { ...@@ -89,8 +102,8 @@ public final class LaunchTest {
ArrayList<Intent> launchedIntents = ArrayList<Intent> launchedIntents =
launchAndCheckBrowserLaunched(false /* opaqueMainActivityInitiallyEnabled */, launchAndCheckBrowserLaunched(false /* opaqueMainActivityInitiallyEnabled */,
true /* browserCompatibleWithSplashActivity */, launchIntent, launchIntent, H2OTransparentLauncherActivity.class,
H2OTransparentLauncherActivity.class); HostBrowserUtils.MINIMUM_REQUIRED_CHROMIUM_VERSION_NEW_SPLASH);
Assert.assertEquals(1, launchedIntents.size()); Assert.assertEquals(1, launchedIntents.size());
assertIntentIsForBrowserLaunch(launchedIntents.get(0), deepLinkUrl); assertIntentIsForBrowserLaunch(launchedIntents.get(0), deepLinkUrl);
...@@ -115,18 +128,17 @@ public final class LaunchTest { ...@@ -115,18 +128,17 @@ public final class LaunchTest {
launchIntent.setPackage(sWebApkPackageName); launchIntent.setPackage(sWebApkPackageName);
ArrayList<Intent> launchedIntents; ArrayList<Intent> launchedIntents;
launchedIntents = launchedIntents = launchAndCheckBrowserLaunched(
launchAndCheckBrowserLaunched(false /* opaqueMainActivityInitiallyEnabled */, false /* opaqueMainActivityInitiallyEnabled */, launchIntent,
false /* browserCompatibleWithSplashActivity */, launchIntent, H2OTransparentLauncherActivity.class, BROWSER_H2O_INCOMPATIBLE_VERSION);
H2OTransparentLauncherActivity.class);
Assert.assertEquals(1, launchedIntents.size()); Assert.assertEquals(1, launchedIntents.size());
assertIntentIsForBrowserLaunch(launchedIntents.get(0), deepLinkUrl); assertIntentIsForBrowserLaunch(launchedIntents.get(0), deepLinkUrl);
assertOnlyEnabledMainIntentHandler(H2OMainActivity.class); assertOnlyEnabledMainIntentHandler(H2OMainActivity.class);
launchedIntents = launchedIntents =
launchAndCheckBrowserLaunched(false /* opaqueMainActivityInitiallyEnabled */, launchAndCheckBrowserLaunched(false /* opaqueMainActivityInitiallyEnabled */,
true /* browserCompatibleWithSplashActivity */, launchIntent, launchIntent, H2OTransparentLauncherActivity.class,
H2OTransparentLauncherActivity.class); HostBrowserUtils.MINIMUM_REQUIRED_CHROMIUM_VERSION_NEW_SPLASH);
Assert.assertEquals(5, launchedIntents.size()); Assert.assertEquals(5, launchedIntents.size());
assertIntentComponentClassNameEquals(H2OMainActivity.class, launchedIntents.get(0)); assertIntentComponentClassNameEquals(H2OMainActivity.class, launchedIntents.get(0));
Assert.assertEquals(BROWSER_PACKAGE_NAME, launchedIntents.get(1).getPackage()); Assert.assertEquals(BROWSER_PACKAGE_NAME, launchedIntents.get(1).getPackage());
...@@ -136,10 +148,9 @@ public final class LaunchTest { ...@@ -136,10 +148,9 @@ public final class LaunchTest {
assertIntentIsForBrowserLaunch(launchedIntents.get(4), deepLinkUrl); assertIntentIsForBrowserLaunch(launchedIntents.get(4), deepLinkUrl);
assertOnlyEnabledMainIntentHandler(H2OOpaqueMainActivity.class); assertOnlyEnabledMainIntentHandler(H2OOpaqueMainActivity.class);
launchedIntents = launchedIntents = launchAndCheckBrowserLaunched(
launchAndCheckBrowserLaunched(true /* opaqueMainActivityInitiallyEnabled */, true /* opaqueMainActivityInitiallyEnabled */, launchIntent,
false /* browserCompatibleWithSplashActivity */, launchIntent, H2OTransparentLauncherActivity.class, BROWSER_H2O_INCOMPATIBLE_VERSION);
H2OTransparentLauncherActivity.class);
Assert.assertEquals(2, launchedIntents.size()); Assert.assertEquals(2, launchedIntents.size());
assertIntentComponentClassNameEquals(SplashActivity.class, launchedIntents.get(0)); assertIntentComponentClassNameEquals(SplashActivity.class, launchedIntents.get(0));
assertIntentIsForBrowserLaunch(launchedIntents.get(1), deepLinkUrl); assertIntentIsForBrowserLaunch(launchedIntents.get(1), deepLinkUrl);
...@@ -147,8 +158,8 @@ public final class LaunchTest { ...@@ -147,8 +158,8 @@ public final class LaunchTest {
launchedIntents = launchedIntents =
launchAndCheckBrowserLaunched(true /* opaqueMainActivityInitiallyEnabled */, launchAndCheckBrowserLaunched(true /* opaqueMainActivityInitiallyEnabled */,
true /* browserCompatibleWithSplashActivity */, launchIntent, launchIntent, H2OTransparentLauncherActivity.class,
H2OTransparentLauncherActivity.class); HostBrowserUtils.MINIMUM_REQUIRED_CHROMIUM_VERSION_NEW_SPLASH);
Assert.assertEquals(2, launchedIntents.size()); Assert.assertEquals(2, launchedIntents.size());
assertIntentComponentClassNameEquals(SplashActivity.class, launchedIntents.get(0)); assertIntentComponentClassNameEquals(SplashActivity.class, launchedIntents.get(0));
assertIntentIsForBrowserLaunch(launchedIntents.get(1), deepLinkUrl); assertIntentIsForBrowserLaunch(launchedIntents.get(1), deepLinkUrl);
...@@ -166,16 +177,14 @@ public final class LaunchTest { ...@@ -166,16 +177,14 @@ public final class LaunchTest {
ArrayList<Intent> launchedIntents; ArrayList<Intent> launchedIntents;
launchedIntents = launchedIntents =
launchAndCheckBrowserLaunched(false /* opaqueMainActivityInitiallyEnabled */, launchAndCheckBrowserLaunched(false /* opaqueMainActivityInitiallyEnabled */,
false /* browserCompatibleWithSplashActivity */, launchIntent, launchIntent, H2OMainActivity.class, BROWSER_H2O_INCOMPATIBLE_VERSION);
H2OMainActivity.class);
Assert.assertEquals(1, launchedIntents.size()); Assert.assertEquals(1, launchedIntents.size());
assertIntentIsForBrowserLaunch(launchedIntents.get(0), DEFAULT_START_URL); assertIntentIsForBrowserLaunch(launchedIntents.get(0), DEFAULT_START_URL);
assertOnlyEnabledMainIntentHandler(H2OMainActivity.class); assertOnlyEnabledMainIntentHandler(H2OMainActivity.class);
launchedIntents = launchedIntents = launchAndCheckBrowserLaunched(
launchAndCheckBrowserLaunched(false /* opaqueMainActivityInitiallyEnabled */, false /* opaqueMainActivityInitiallyEnabled */, launchIntent, H2OMainActivity.class,
true /* browserCompatibleWithSplashActivity */, launchIntent, HostBrowserUtils.MINIMUM_REQUIRED_CHROMIUM_VERSION_NEW_SPLASH);
H2OMainActivity.class);
Assert.assertEquals(4, launchedIntents.size()); Assert.assertEquals(4, launchedIntents.size());
Assert.assertEquals(BROWSER_PACKAGE_NAME, launchedIntents.get(0).getPackage()); Assert.assertEquals(BROWSER_PACKAGE_NAME, launchedIntents.get(0).getPackage());
assertIntentComponentClassNameEquals( assertIntentComponentClassNameEquals(
...@@ -184,10 +193,9 @@ public final class LaunchTest { ...@@ -184,10 +193,9 @@ public final class LaunchTest {
assertIntentIsForBrowserLaunch(launchedIntents.get(3), DEFAULT_START_URL); assertIntentIsForBrowserLaunch(launchedIntents.get(3), DEFAULT_START_URL);
assertOnlyEnabledMainIntentHandler(H2OOpaqueMainActivity.class); assertOnlyEnabledMainIntentHandler(H2OOpaqueMainActivity.class);
launchedIntents = launchedIntents = launchAndCheckBrowserLaunched(
launchAndCheckBrowserLaunched(true /* opaqueMainActivityInitiallyEnabled */, true /* opaqueMainActivityInitiallyEnabled */, launchIntent,
false /* browserCompatibleWithSplashActivity */, launchIntent, H2OOpaqueMainActivity.class, BROWSER_H2O_INCOMPATIBLE_VERSION);
H2OOpaqueMainActivity.class);
Assert.assertEquals(2, launchedIntents.size()); Assert.assertEquals(2, launchedIntents.size());
assertIntentComponentClassNameEquals(SplashActivity.class, launchedIntents.get(0)); assertIntentComponentClassNameEquals(SplashActivity.class, launchedIntents.get(0));
assertIntentIsForBrowserLaunch(launchedIntents.get(1), DEFAULT_START_URL); assertIntentIsForBrowserLaunch(launchedIntents.get(1), DEFAULT_START_URL);
...@@ -195,8 +203,8 @@ public final class LaunchTest { ...@@ -195,8 +203,8 @@ public final class LaunchTest {
launchedIntents = launchedIntents =
launchAndCheckBrowserLaunched(true /* opaqueMainActivityInitiallyEnabled */, launchAndCheckBrowserLaunched(true /* opaqueMainActivityInitiallyEnabled */,
true /* browserCompatibleWithSplashActivity */, launchIntent, launchIntent, H2OOpaqueMainActivity.class,
H2OOpaqueMainActivity.class); HostBrowserUtils.MINIMUM_REQUIRED_CHROMIUM_VERSION_NEW_SPLASH);
Assert.assertEquals(2, launchedIntents.size()); Assert.assertEquals(2, launchedIntents.size());
assertIntentComponentClassNameEquals(SplashActivity.class, launchedIntents.get(0)); assertIntentComponentClassNameEquals(SplashActivity.class, launchedIntents.get(0));
assertIntentIsForBrowserLaunch(launchedIntents.get(1), DEFAULT_START_URL); assertIntentIsForBrowserLaunch(launchedIntents.get(1), DEFAULT_START_URL);
...@@ -227,10 +235,9 @@ public final class LaunchTest { ...@@ -227,10 +235,9 @@ public final class LaunchTest {
launchIntent.setComponent(new ComponentName(sWebApkPackageName, shareActivityClassName)); launchIntent.setComponent(new ComponentName(sWebApkPackageName, shareActivityClassName));
launchIntent.putExtra(Intent.EXTRA_TEXT, "subject_value"); launchIntent.putExtra(Intent.EXTRA_TEXT, "subject_value");
ArrayList<Intent> launchedIntents = ArrayList<Intent> launchedIntents = launchAndCheckBrowserLaunched(
launchAndCheckBrowserLaunched(true /* opaqueMainActivityInitiallyEnabled */, true /* opaqueMainActivityInitiallyEnabled */, launchIntent,
false /* browserCompatibleWithSplashActivity */, launchIntent, H2OTransparentLauncherActivity.class, BROWSER_H2O_INCOMPATIBLE_VERSION);
H2OTransparentLauncherActivity.class);
Assert.assertTrue(launchedIntents.size() > 1); Assert.assertTrue(launchedIntents.size() > 1);
Intent browserLaunchIntent = launchedIntents.get(launchedIntents.size() - 1); Intent browserLaunchIntent = launchedIntents.get(launchedIntents.size() - 1);
...@@ -257,8 +264,8 @@ public final class LaunchTest { ...@@ -257,8 +264,8 @@ public final class LaunchTest {
ArrayList<Intent> launchedIntents = ArrayList<Intent> launchedIntents =
launchAndCheckBrowserLaunched(true /* opaqueMainActivityInitiallyEnabled */, launchAndCheckBrowserLaunched(true /* opaqueMainActivityInitiallyEnabled */,
true /* browserCompatibleWithSplashActivity */, launchIntent, launchIntent, H2OTransparentLauncherActivity.class,
H2OTransparentLauncherActivity.class); HostBrowserUtils.MINIMUM_REQUIRED_CHROMIUM_VERSION_NEW_SPLASH);
Assert.assertTrue(launchedIntents.size() > 1); Assert.assertTrue(launchedIntents.size() > 1);
Intent browserLaunchIntent = launchedIntents.get(launchedIntents.size() - 1); Intent browserLaunchIntent = launchedIntents.get(launchedIntents.size() - 1);
...@@ -284,8 +291,8 @@ public final class LaunchTest { ...@@ -284,8 +291,8 @@ public final class LaunchTest {
ArrayList<Intent> launchedIntents = ArrayList<Intent> launchedIntents =
launchAndCheckBrowserLaunched(true /* opaqueMainActivityInitiallyEnabled */, launchAndCheckBrowserLaunched(true /* opaqueMainActivityInitiallyEnabled */,
true /* browserCompatibleWithSplashActivity */, launchIntent, launchIntent, H2OTransparentLauncherActivity.class,
H2OTransparentLauncherActivity.class); HostBrowserUtils.MINIMUM_REQUIRED_CHROMIUM_VERSION_NEW_SPLASH);
Assert.assertTrue(launchedIntents.size() > 1); Assert.assertTrue(launchedIntents.size() > 1);
Intent browserLaunchIntent = launchedIntents.get(launchedIntents.size() - 1); Intent browserLaunchIntent = launchedIntents.get(launchedIntents.size() - 1);
...@@ -384,8 +391,8 @@ public final class LaunchTest { ...@@ -384,8 +391,8 @@ public final class LaunchTest {
ArrayList<Intent> launchedIntents = ArrayList<Intent> launchedIntents =
launchAndCheckBrowserLaunched(false /* opaqueMainActivityInitiallyEnabled */, launchAndCheckBrowserLaunched(false /* opaqueMainActivityInitiallyEnabled */,
true /* browserCompatibleWithSplashActivity */, launchIntent, launchIntent, H2OTransparentLauncherActivity.class,
H2OTransparentLauncherActivity.class); HostBrowserUtils.MINIMUM_REQUIRED_CHROMIUM_VERSION_NEW_SPLASH);
Assert.assertEquals(1, launchedIntents.size()); Assert.assertEquals(1, launchedIntents.size());
assertIntentIsForBrowserLaunch(launchedIntents.get(0), deepLinkUrl); assertIntentIsForBrowserLaunch(launchedIntents.get(0), deepLinkUrl);
assertOnlyEnabledMainIntentHandler(H2OMainActivity.class); assertOnlyEnabledMainIntentHandler(H2OMainActivity.class);
...@@ -404,10 +411,9 @@ public final class LaunchTest { ...@@ -404,10 +411,9 @@ public final class LaunchTest {
Intent launchIntent = new Intent(Intent.ACTION_MAIN); Intent launchIntent = new Intent(Intent.ACTION_MAIN);
launchIntent.setPackage(sWebApkPackageName); launchIntent.setPackage(sWebApkPackageName);
ArrayList<Intent> launchedIntents = ArrayList<Intent> launchedIntents = launchAndCheckBrowserLaunched(
launchAndCheckBrowserLaunched(false /* opaqueMainActivityInitiallyEnabled */, false /* opaqueMainActivityInitiallyEnabled */, launchIntent, H2OMainActivity.class,
true /* browserCompatibleWithSplashActivity */, launchIntent, HostBrowserUtils.MINIMUM_REQUIRED_CHROMIUM_VERSION_NEW_SPLASH);
H2OMainActivity.class);
Assert.assertEquals(1, launchedIntents.size()); Assert.assertEquals(1, launchedIntents.size());
assertIntentIsForBrowserLaunch(launchedIntents.get(0), DEFAULT_START_URL); assertIntentIsForBrowserLaunch(launchedIntents.get(0), DEFAULT_START_URL);
assertOnlyEnabledMainIntentHandler(H2OMainActivity.class); assertOnlyEnabledMainIntentHandler(H2OMainActivity.class);
...@@ -481,6 +487,76 @@ public final class LaunchTest { ...@@ -481,6 +487,76 @@ public final class LaunchTest {
RuntimeEnvironment.application, true /* isNewStyleWebApk */)); RuntimeEnvironment.application, true /* isNewStyleWebApk */));
} }
/**
* Tests that we add site settings shortcuts both when
* opaque main activity is enabled and when it is not enabled.
*/
@Test
@Config(sdk = Build.VERSION_CODES.N_MR1)
public void testAddsSiteSettings() {
registerWebApk(true /* isNewStyleWebApk */);
registerSiteSettingsCategory();
Intent launchIntent = new Intent(Intent.ACTION_MAIN);
launchIntent.setPackage(sWebApkPackageName);
launchAndCheckBrowserLaunched(false /* opaqueMainActivityInitiallyEnabled */, launchIntent,
H2OMainActivity.class, SITE_SETTINGS_COMPATIBLE_BROWSER_VERSION);
ShortcutManager shortcutManager = mAppContext.getSystemService(ShortcutManager.class);
assertTrue(containsSiteSettingsDynamicShortcut(shortcutManager));
shortcutManager.removeAllDynamicShortcuts();
launchAndCheckBrowserLaunched(true /* opaqueMainActivityInitiallyEnabled */, launchIntent,
H2OOpaqueMainActivity.class, SITE_SETTINGS_COMPATIBLE_BROWSER_VERSION);
assertTrue(containsSiteSettingsDynamicShortcut(shortcutManager));
}
/**
* Tests that we remove the shortcut in the case that it was previously
* added but the current version of Chrome no longer supports it.
*/
@Test
@Config(sdk = Build.VERSION_CODES.N_MR1)
public void testDoesNotAddSiteSettingsIfCategoryMissing() {
registerWebApk(true /* isNewStyleWebApk */);
Intent launchIntent = new Intent(Intent.ACTION_MAIN);
launchIntent.setPackage(sWebApkPackageName);
launchAndCheckBrowserLaunched(false /* opaqueMainActivityInitiallyEnabled */, launchIntent,
H2OMainActivity.class, SITE_SETTINGS_COMPATIBLE_BROWSER_VERSION);
ShortcutManager shortcutManager = mAppContext.getSystemService(ShortcutManager.class);
assertFalse(containsSiteSettingsDynamicShortcut(shortcutManager));
}
/** Tests that we do not attempt to add a shortcut on Android versions lower than N. */
@Test
@Config(sdk = Build.VERSION_CODES.LOLLIPOP)
public void testDoesNotAddSiteSettingsWhenSdkLow() {
registerWebApk(true /* isNewStyleWebApk */);
registerSiteSettingsCategory();
Intent launchIntent = new Intent(Intent.ACTION_MAIN);
launchIntent.setPackage(sWebApkPackageName);
launchAndCheckBrowserLaunched(false /* opaqueMainActivityInitiallyEnabled */, launchIntent,
H2OMainActivity.class, SITE_SETTINGS_COMPATIBLE_BROWSER_VERSION);
// There is no shortcut manager in Android M. Therefore if
// this test passes, then we did not attempt to add the shortcut.
}
private static boolean containsSiteSettingsDynamicShortcut(ShortcutManager shortcutManager) {
List<String> shortcutIDs = shortcutManager.getDynamicShortcuts()
.stream()
.map(ShortcutInfo::getId)
.collect(Collectors.toList());
return shortcutIDs.contains(SITE_SETTINGS_SHORTCUT_ID);
}
/** Checks the name of the intent's component class name. */ /** Checks the name of the intent's component class name. */
private static void assertIntentComponentClassNameEquals(Class expectedClass, Intent intent) { private static void assertIntentComponentClassNameEquals(Class expectedClass, Intent intent) {
Assert.assertEquals(expectedClass.getName(), intent.getComponent().getClassName()); Assert.assertEquals(expectedClass.getName(), intent.getComponent().getClassName());
...@@ -506,27 +582,31 @@ public final class LaunchTest { ...@@ -506,27 +582,31 @@ public final class LaunchTest {
WebApkTestHelper.registerWebApkWithMetaData(sWebApkPackageName, metadata, null); WebApkTestHelper.registerWebApkWithMetaData(sWebApkPackageName, metadata, null);
} }
private void registerSiteSettingsCategory() {
Intent intent =
new Intent().setAction("android.support.customtabs.action.CustomTabsService");
intent.setPackage(BROWSER_PACKAGE_NAME);
intent.addCategory(CATEGORY_LAUNCH_WEBAPK_SITE_SETTINGS);
mShadowPackageManager.addResolveInfoForIntent(intent, new ResolveInfo());
}
/** /**
* Launches WebAPK with the given intent and configuration. Tests that the host * Launches WebAPK with the given intent and configuration. Tests that the host
* browser is launched and which activities are enabled after the browser launch. * browser is launched and which activities are enabled after the browser launch.
* @param opaqueMainActivityInitiallyEnabled Whether H2OOpaqueActivity is enabled at the * @param opaqueMainActivityInitiallyEnabled Whether H2OOpaqueActivity is enabled at the
* beginning of the test case. * beginning of the test case.
* @param browserCompatibleWithSplashActivity Whether the host browser supports the ShellAPK
* showing the splash screen.
* @param launchIntent Intent to launch. * @param launchIntent Intent to launch.
* @param launchActivity Activity which should receive the launch intent. * @param launchActivity Activity which should receive the launch intent.
* @param browserVersion The version of the Chromium browser to install.
* @return List of launched activity intents (including the host browser launch intent). * @return List of launched activity intents (including the host browser launch intent).
*/ */
private ArrayList<Intent> launchAndCheckBrowserLaunched( private ArrayList<Intent> launchAndCheckBrowserLaunched(
boolean opaqueMainActivityInitiallyEnabled, boolean browserCompatibleWithSplashActivity, boolean opaqueMainActivityInitiallyEnabled, Intent launchIntent,
Intent launchIntent, Class<? extends Activity> launchActivity) { Class<? extends Activity> launchActivity, int browserVersion) {
changeEnabledActivity(opaqueMainActivityInitiallyEnabled ? H2OOpaqueMainActivity.class changeEnabledActivity(opaqueMainActivityInitiallyEnabled ? H2OOpaqueMainActivity.class
: H2OMainActivity.class); : H2OMainActivity.class);
installBrowser(BROWSER_PACKAGE_NAME, installBrowser(BROWSER_PACKAGE_NAME, browserVersion);
browserCompatibleWithSplashActivity
? HostBrowserUtils.MINIMUM_REQUIRED_CHROMIUM_VERSION_NEW_SPLASH
: BROWSER_H2O_INCOMPATIBLE_VERSION);
ArrayList<Intent> launchedIntents = ArrayList<Intent> launchedIntents =
runActivityChain(launchIntent, launchActivity, BROWSER_PACKAGE_NAME); runActivityChain(launchIntent, launchActivity, BROWSER_PACKAGE_NAME);
......
...@@ -34,8 +34,6 @@ public class HostBrowserLauncher { ...@@ -34,8 +34,6 @@ public class HostBrowserLauncher {
* Otherwise, launches the host browser in tabbed mode. * Otherwise, launches the host browser in tabbed mode.
*/ */
public static void launch(Activity activity, HostBrowserLauncherParams params) { public static void launch(Activity activity, HostBrowserLauncherParams params) {
Log.v(TAG, "WebAPK Launch URL: " + params.getStartUrl());
if (HostBrowserUtils.shouldLaunchInTab(params)) { if (HostBrowserUtils.shouldLaunchInTab(params)) {
launchInTab(activity.getApplicationContext(), params); launchInTab(activity.getApplicationContext(), params);
return; return;
...@@ -48,6 +46,8 @@ public class HostBrowserLauncher { ...@@ -48,6 +46,8 @@ public class HostBrowserLauncher {
/** Launches host browser in WebAPK mode. */ /** Launches host browser in WebAPK mode. */
public static void launchBrowserInWebApkMode(Activity activity, public static void launchBrowserInWebApkMode(Activity activity,
HostBrowserLauncherParams params, Bundle extraExtras, int flags, boolean expectResult) { HostBrowserLauncherParams params, Bundle extraExtras, int flags, boolean expectResult) {
ManageDataLauncherActivity.updateSiteSettingsShortcut(
activity.getApplicationContext(), params);
Intent intent = new Intent(); Intent intent = new Intent();
intent.setAction(ACTION_START_WEBAPK); intent.setAction(ACTION_START_WEBAPK);
intent.setPackage(params.getHostBrowserPackageName()); intent.setPackage(params.getHostBrowserPackageName());
...@@ -102,6 +102,7 @@ public class HostBrowserLauncher { ...@@ -102,6 +102,7 @@ public class HostBrowserLauncher {
/** Launches a WebAPK in its runtime host browser as a tab. */ /** Launches a WebAPK in its runtime host browser as a tab. */
private static void launchInTab(Context context, HostBrowserLauncherParams params) { private static void launchInTab(Context context, HostBrowserLauncherParams params) {
ManageDataLauncherActivity.updateSiteSettingsShortcut(context, params);
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(params.getStartUrl())); Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(params.getStartUrl()));
intent.setPackage(params.getHostBrowserPackageName()); intent.setPackage(params.getHostBrowserPackageName());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
......
// Copyright 2020 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.webapk.shell_apk;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import android.widget.Toast;
import org.chromium.webapk.lib.common.WebApkConstants;
import java.util.Collections;
import java.util.List;
/**
* Handles site settings shortcuts for WebApks. The shortcut opens the web
* browser's site settings for the start url associated with the WebApk.
*/
public class ManageDataLauncherActivity extends Activity {
public static final String ACTION_SITE_SETTINGS =
"android.support.customtabs.action.ACTION_MANAGE_TRUSTED_WEB_ACTIVITY_DATA";
public static final String SITE_SETTINGS_SHORTCUT_ID =
"android.support.customtabs.action.SITE_SETTINGS_SHORTCUT";
private static final String EXTRA_SITE_SETTINGS_URL = "SITE_SETTINGS_URL";
private static final String EXTRA_PROVIDER_PACKAGE = "PROVIDER_PACKAGE";
private static final String CATEGORY_LAUNCH_WEBAPK_SITE_SETTINGS =
"androidx.browser.trusted.category.LaunchWebApkSiteSettings";
private static final String ACTION_CUSTOM_TABS_CONNECTION =
"android.support.customtabs.action.CustomTabsService";
private String mProviderPackage;
/**
* The url of the page for which the settings will be shown. Must be provided as an intent
* extra to {@link ManageDataLauncherActivity}.
*/
private Uri mUrl;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mProviderPackage = getIntent().getStringExtra(EXTRA_PROVIDER_PACKAGE);
mUrl = Uri.parse(getIntent().getStringExtra(EXTRA_SITE_SETTINGS_URL));
if (!supportsLaunchSettings(this, mProviderPackage)) {
handleNoSupportForLaunchSettings();
return;
}
setContentView(createLoadingView());
launchSettings();
}
/**
* Returns a view with a loading spinner.
*/
private View createLoadingView() {
ProgressBar progressBar = new ProgressBar(this);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
params.gravity = Gravity.CENTER;
progressBar.setLayoutParams(params);
FrameLayout layout = new FrameLayout(this);
layout.addView(progressBar);
return layout;
}
/**
* Called if a provider doesn't support the launch settings feature. Shows a toast telling the
* user how to fix it, then finishes the activity.
*/
private void handleNoSupportForLaunchSettings() {
String appName;
try {
ApplicationInfo info = getPackageManager().getApplicationInfo(mProviderPackage, 0);
appName = getPackageManager().getApplicationLabel(info).toString();
} catch (PackageManager.NameNotFoundException e) {
appName = mProviderPackage;
}
Toast.makeText(this, getString(R.string.no_support_for_launch_settings, appName),
Toast.LENGTH_LONG)
.show();
finish();
}
@Override
protected void onStop() {
super.onStop();
finish();
}
private void launchSettings() {
Intent intent = new Intent();
intent.setAction(ACTION_SITE_SETTINGS);
intent.setPackage(mProviderPackage);
intent.setData(mUrl);
intent.putExtra(WebApkConstants.EXTRA_IS_WEBAPK, true);
try {
startActivityForResult(intent, 0 /* requestCode */);
finish();
} catch (ActivityNotFoundException e) {
handleNoSupportForLaunchSettings();
}
}
private static boolean supportsLaunchSettings(Context context, String providerPackage) {
Intent intent = new Intent(ACTION_CUSTOM_TABS_CONNECTION);
intent.addCategory(CATEGORY_LAUNCH_WEBAPK_SITE_SETTINGS);
intent.setPackage(providerPackage);
List<ResolveInfo> services = context.getPackageManager().queryIntentServices(
intent, PackageManager.GET_RESOLVED_FILTER);
return services.size() > 0;
}
/**
* Returns the {@link ShortcutInfo} for a dynamic shortcut into site settings,
* provided that {@link ManageDataLauncherActivity} is present in the manifest
* and an Intent for managing site settings is available.
*
* Otherwise returns null if {@link ManageDataLauncherActivity} is not launchable
* or if shortcuts are not supported by the Android SDK version.
*
* The shortcut returned does not specify an activity. Thus when the shortcut is added,
* the app's main activity will be used by default. This activity needs to define the
* MAIN action and LAUNCHER category in order to attach the shortcut.
*/
@TargetApi(Build.VERSION_CODES.N_MR1)
private static ShortcutInfo createSiteSettingsShortcutInfo(
Context context, String url, String providerPackage) {
Intent siteSettingsIntent = new Intent(context, ManageDataLauncherActivity.class);
// Intent needs to have an action set, we can set an arbitrary action.
siteSettingsIntent.setAction(ACTION_SITE_SETTINGS);
siteSettingsIntent.putExtra(EXTRA_SITE_SETTINGS_URL, url);
siteSettingsIntent.putExtra(EXTRA_PROVIDER_PACKAGE, providerPackage);
return new ShortcutInfo.Builder(context, SITE_SETTINGS_SHORTCUT_ID)
.setShortLabel(context.getString(R.string.site_settings_short_label))
.setLongLabel(context.getString(R.string.site_settings_long_label))
.setIcon(Icon.createWithResource(context, R.drawable.ic_site_settings))
.setIntent(siteSettingsIntent)
.build();
}
/**
* Adds dynamic shortcut to site settings if the provider and android version support it.
*
* Removes previously added site settings shortcut if it is no longer supported, e.g. the user
* changed their default browser.
*/
public static void updateSiteSettingsShortcut(
Context context, HostBrowserLauncherParams params) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) return;
ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
// Remove potentially existing shortcut if package does not support shortcuts.
if (!supportsLaunchSettings(context, params.getHostBrowserPackageName())) {
shortcutManager.removeDynamicShortcuts(Collections.singletonList(
ManageDataLauncherActivity.SITE_SETTINGS_SHORTCUT_ID));
return;
}
ShortcutInfo shortcut = createSiteSettingsShortcutInfo(
context, params.getStartUrl(), params.getHostBrowserPackageName());
shortcutManager.addDynamicShortcuts(Collections.singletonList(shortcut));
}
}
...@@ -167,6 +167,15 @@ ...@@ -167,6 +167,15 @@
</translations> </translations>
<release allow_pseudo="false" seq="1"> <release allow_pseudo="false" seq="1">
<messages fallback_to_english="true"> <messages fallback_to_english="true">
<message name="IDS_SITE_SETTINGS_LONG_LABEL" desc="Site settings Android app shortcut title to display on devices with larger screens. (ideally less than 25 characters)">
Site settings
</message>
<message name="IDS_SITE_SETTINGS_SHORT_LABEL" desc="Site settings Android app shortcut title. (ideally less than 10 characters)">
Site settings
</message>
<message name="IDS_NO_SUPPORT_FOR_LAUNCH_SETTINGS" desc="Text to show in a toast when a user clicks on a site settings shortcut but that feature is not supported by the browser.">
To open site settings, update or reinstall <ph name="BROWSER_NAME">%1$s<ex>Chrome</ex></ph>
</message>
<!-- Select host browser dialog --> <!-- Select host browser dialog -->
<message name="IDS_CHOOSE_HOST_BROWSER_DIALOG_TITLE" desc="Title for the host browser picker dialog, which is used to ask users to pick a browser to launch the installed WebAPK."> <message name="IDS_CHOOSE_HOST_BROWSER_DIALOG_TITLE" desc="Title for the host browser picker dialog, which is used to ask users to pick a browser to launch the installed WebAPK.">
<ph name="APP_NAME">%1$s<ex>Progressive Web Apps</ex></ph> requires a web browser <ph name="APP_NAME">%1$s<ex>Progressive Web Apps</ex></ph> requires a web browser
......
99afdcfb54eadd12dad55079e782f00e904764f0
\ No newline at end of file
f42d794cf577db2d062ce76e412b52351cfec17f
\ No newline at end of file
f42d794cf577db2d062ce76e412b52351cfec17f
\ No newline at end of file
...@@ -177,10 +177,16 @@ public class WebApkValidator { ...@@ -177,10 +177,16 @@ public class WebApkValidator {
*/ */
@SuppressLint("PackageManagerGetSignatures") @SuppressLint("PackageManagerGetSignatures")
public static boolean isValidWebApk(Context context, String webappPackageName) { public static boolean isValidWebApk(Context context, String webappPackageName) {
if (sOverrideValidationForTesting) {
if (DEBUG) {
Log.d(TAG, "WebApk validation is disabled for testing.");
}
return true;
}
if (sExpectedSignature == null || sCommentSignedPublicKeyBytes == null) { if (sExpectedSignature == null || sCommentSignedPublicKeyBytes == null) {
Log.wtf(TAG, Log.wtf(TAG,
"WebApk validation failure - expected signature not set." "WebApk validation failure - expected signature not set - "
+ "missing call to WebApkValidator.initWithBrowserHostSignature"); + "missing call to WebApkValidator.init");
return false; return false;
} }
PackageInfo packageInfo; PackageInfo packageInfo;
...@@ -197,12 +203,6 @@ public class WebApkValidator { ...@@ -197,12 +203,6 @@ public class WebApkValidator {
if (isNotWebApkQuick(packageInfo)) { if (isNotWebApkQuick(packageInfo)) {
return false; return false;
} }
if (sOverrideValidationForTesting) {
if (DEBUG) {
Log.d(TAG, "Ok! Looks like a WebApk (has start url) and validation is disabled.");
}
return true;
}
if (verifyV1WebApk(packageInfo, webappPackageName)) { if (verifyV1WebApk(packageInfo, webappPackageName)) {
return true; return true;
} }
......
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