Commit 6e196c29 authored by Peter Kotwicz's avatar Peter Kotwicz Committed by Commit Bot

[Android WebAPK] Enable launching non-Chrome browser from WebAPK

This CL changes the WebAPK host browser selection logic to launch a
non-Chrome browser if:
  - there is no installed Chrome browser
  AND
  - the non-Chrome browser is the default browser

BUG=1008716

Change-Id: Id14860628d6d170c0aee2e843ca719c1e94652d4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1828351
Commit-Queue: Peter Kotwicz <pkotwicz@chromium.org>
Reviewed-by: default avatarYaron Friedman <yfriedman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#701416}
parent 11e2fb46
......@@ -12,4 +12,4 @@
# //chrome/android/webapk/shell_apk:webapk is changed. This includes
# Java files, Android resource files and AndroidManifest.xml. Does not affect
# Chrome.apk
current_shell_apk_version = 108
current_shell_apk_version = 109
......@@ -193,6 +193,16 @@ public class HostBrowserUtilsTest {
setHostBrowserInSharedPreferences(null);
Assert.assertEquals(BROWSERS_SUPPORTING_WEBAPKS[0],
HostBrowserUtils.computeHostBrowserPackageClearCachedDataOnChange(mContext));
// Shared pref browser: Null
// Default browser: Does not support WebAPKs
// > 1 installed browsers
setInstalledBrowsersAndClearedCachedData(DefaultBrowserWebApkSupport.NO,
new String[] {
BROWSERS_NOT_SUPPORTING_WEBAPKS[0], BROWSERS_NOT_SUPPORTING_WEBAPKS[1]});
setHostBrowserInSharedPreferences(null);
Assert.assertEquals(DEFAULT_BROWSER_NOT_SUPPORTING_WEBAPKS,
HostBrowserUtils.computeHostBrowserPackageClearCachedDataOnChange(mContext));
}
/**
......
......@@ -32,13 +32,15 @@ import org.chromium.webapk.test.WebApkTestHelper;
@RunWith(LocalRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public final class MainActivityTest {
private PackageManager mPackageManager;
private static final String BROWSER_PACKAGE_NAME = "com.android.chrome";
private PackageManager mPackageManager;
private TestBrowserInstaller mBrowserInstaller = new TestBrowserInstaller();
@Before
public void setUp() {
mPackageManager = RuntimeEnvironment.application.getPackageManager();
new TestBrowserInstaller().installModernBrowser(BROWSER_PACKAGE_NAME);
mBrowserInstaller.installModernBrowser(BROWSER_PACKAGE_NAME);
}
/**
......@@ -177,14 +179,41 @@ public final class MainActivityTest {
assertWebApkLaunched(startedActivityIntent, expectedStartUrl);
}
/**
* Tests that if the only installed browser does not support WebAPKs that the browser is
* launched in tabbed mode.
*/
@Test
public void testShouldLaunchInTabNonChromeBrowser() throws Exception {
final String nonChromeBrowserPackageName = "com.crazy.browser";
mBrowserInstaller.setInstalledBrowserWithVersion(
nonChromeBrowserPackageName, "10000.0.000.0");
final String startUrl = "https://www.google.com/";
Bundle bundle = new Bundle();
bundle.putString(WebApkMetaDataKeys.START_URL, startUrl);
bundle.putString(WebApkMetaDataKeys.SCOPE, startUrl);
// Unbound WebAPK, no runtime host.
WebApkTestHelper.registerWebApkWithMetaData(
WebApkUtilsTest.WEBAPK_PACKAGE_NAME, bundle, null /* shareTargetMetaData */);
Intent launchIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(startUrl));
Robolectric.buildActivity(MainActivity.class, launchIntent).create();
Intent startedActivityIntent = ShadowApplication.getInstance().getNextStartedActivity();
assertTabbedBrowserLaunched(startedActivityIntent, nonChromeBrowserPackageName, startUrl);
}
/**
* Tests that a WebAPK should be launched as a tab if Chrome's version number is lower than
* {@link HostBrowserUtils#MINIMUM_REQUIRED_CHROME_VERSION}.
*/
@Test
public void testShouldLaunchInTabWhenChromeVersionIsTooLow() throws Exception {
mBrowserInstaller.setInstalledBrowserWithVersion(BROWSER_PACKAGE_NAME, "56.0.000.0");
final String startUrl = "https://www.google.com/";
final String oldVersionName = "56.0.000.0";
Bundle bundle = new Bundle();
bundle.putString(WebApkMetaDataKeys.START_URL, startUrl);
......@@ -193,15 +222,11 @@ public final class MainActivityTest {
WebApkTestHelper.registerWebApkWithMetaData(
WebApkUtilsTest.WEBAPK_PACKAGE_NAME, bundle, null /* shareTargetMetaData */);
mPackageManager.getPackageInfo(BROWSER_PACKAGE_NAME, 0).versionName = oldVersionName;
Intent launchIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(startUrl));
Robolectric.buildActivity(MainActivity.class, launchIntent).create();
Intent startedActivityIntent = ShadowApplication.getInstance().getNextStartedActivity();
Assert.assertEquals(BROWSER_PACKAGE_NAME, startedActivityIntent.getPackage());
Assert.assertEquals(Intent.ACTION_VIEW, startedActivityIntent.getAction());
Assert.assertEquals(startUrl, startedActivityIntent.getDataString());
assertTabbedBrowserLaunched(startedActivityIntent, BROWSER_PACKAGE_NAME, startUrl);
}
/**
......@@ -210,8 +235,9 @@ public final class MainActivityTest {
*/
@Test
public void testShouldNotLaunchInTabWithNewVersionOfChrome() throws Exception {
mBrowserInstaller.setInstalledBrowserWithVersion(BROWSER_PACKAGE_NAME, "57.0.000.0");
final String startUrl = "https://www.google.com/";
final String newVersionName = "57.0.000.0";
Bundle bundle = new Bundle();
bundle.putString(WebApkMetaDataKeys.START_URL, startUrl);
......@@ -220,8 +246,6 @@ public final class MainActivityTest {
WebApkTestHelper.registerWebApkWithMetaData(
WebApkUtilsTest.WEBAPK_PACKAGE_NAME, bundle, null /* shareTargetMetaData */);
mPackageManager.getPackageInfo(BROWSER_PACKAGE_NAME, 0).versionName = newVersionName;
Intent launchIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(startUrl));
Robolectric.buildActivity(MainActivity.class, launchIntent).create();
......@@ -266,6 +290,17 @@ public final class MainActivityTest {
}
}
/**
* Asserts that the passed-in intent is an intent to launch the passed-in browser package in
* tabbed mode.
*/
private void assertTabbedBrowserLaunched(
Intent intent, String browserPackageName, String expectedStartUrl) {
Assert.assertEquals(browserPackageName, intent.getPackage());
Assert.assertEquals(Intent.ACTION_VIEW, intent.getAction());
Assert.assertEquals(expectedStartUrl, intent.getDataString());
}
/**
* Asserts that the passed in intent is an intent to launch {@link BROWSER_PACKAGE_NAME} in
* WebAPK mode.
......
......@@ -28,9 +28,7 @@ public class TestBrowserInstaller {
* Changes the installed browsers to the passed-in list.
*/
public void setInstalledModernBrowsers(String defaultBrowserPackage, String[] newPackages) {
while (!mInstalledBrowsers.isEmpty()) {
uninstallBrowser(mInstalledBrowsers.iterator().next());
}
uninstallAllBrowsers();
installModernBrowser(defaultBrowserPackage);
if (newPackages != null) {
......@@ -40,6 +38,14 @@ public class TestBrowserInstaller {
}
}
/**
* Changes the installed browser to a browser with the passed-in package and version name.
*/
public void setInstalledBrowserWithVersion(String browser, String versionName) {
uninstallAllBrowsers();
installBrowserWithVersion(browser, versionName);
}
/**
* Installs browser with the passed-in package name and large version name.
*/
......@@ -48,7 +54,7 @@ public class TestBrowserInstaller {
}
/**
* Installs browser with the passed-in package name and version code.
* Installs browser with the passed-in package name and version name.
*/
public void installBrowserWithVersion(String packageName, String versionName) {
if (mInstalledBrowsers.contains(packageName)) return;
......@@ -61,6 +67,15 @@ public class TestBrowserInstaller {
mInstalledBrowsers.add(packageName);
}
/**
* Uninstalls all browsers.
*/
public void uninstallAllBrowsers() {
while (!mInstalledBrowsers.isEmpty()) {
uninstallBrowser(mInstalledBrowsers.iterator().next());
}
}
/**
* Uninstalls browser with the given package name.
*/
......
......@@ -26,9 +26,8 @@ import android.widget.TextView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Map;
/**
* Shows the dialog to choose a host browser to launch WebAPK. Calls the listener callback when the
......@@ -52,14 +51,14 @@ public class ChooseHostBrowserDialog {
private String mPackageName;
private CharSequence mApplicationLabel;
private Drawable mIcon;
private boolean mSupportsWebApks;
private boolean mEnable;
public BrowserItem(String packageName, CharSequence applicationLabel, Drawable icon,
boolean supportsWebApks) {
public BrowserItem(
String packageName, CharSequence applicationLabel, Drawable icon, boolean enable) {
mPackageName = packageName;
mApplicationLabel = applicationLabel;
mIcon = icon;
mSupportsWebApks = supportsWebApks;
mEnable = enable;
}
/** Returns the package name of a browser. */
......@@ -77,9 +76,9 @@ public class ChooseHostBrowserDialog {
return mIcon;
}
/** Returns whether the browser supports WebAPKs. */
public boolean supportsWebApks() {
return mSupportsWebApks;
/** Returns whether to enable the browser list entry. */
public boolean enable() {
return mEnable;
}
}
......@@ -87,11 +86,11 @@ public class ChooseHostBrowserDialog {
* Shows the dialog for choosing a host browser.
* @param context The current Context.
* @param listener The listener for the dialog.
* @param infos The set of ResolvedInfos of the browsers that are shown on the dialog.
* @param infos Browser-package-name->ResolveInfo mapping for all of the installed browsers.
* @param appName The name of the WebAPK for which the dialog is shown.
*/
public static void show(Context context, final DialogListener listener, Set<ResolveInfo> infos,
String appName) {
public static void show(Context context, final DialogListener listener,
Map<String, ResolveInfo> infos, String appName) {
final List<BrowserItem> browserItems =
getBrowserInfosForHostBrowserSelection(context.getPackageManager(), infos);
......@@ -128,7 +127,7 @@ public class ChooseHostBrowserDialog {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
BrowserItem browserItem = browserItems.get(position);
if (browserItem.supportsWebApks()) {
if (browserItem.enable()) {
onDismissCanceler.canceled = true;
listener.onHostBrowserSelected(browserItem.getPackageName());
dialog.cancel();
......@@ -149,18 +148,23 @@ public class ChooseHostBrowserDialog {
/** Returns a list of BrowserItem for all of the installed browsers. */
private static List<BrowserItem> getBrowserInfosForHostBrowserSelection(
PackageManager packageManager, Set<ResolveInfo> resolveInfos) {
List<BrowserItem> browsers = new ArrayList<>();
List<String> browsersSupportingWebApk = HostBrowserUtils.getBrowsersSupportingWebApk();
Set<String> packages = new HashSet<>();
for (ResolveInfo info : resolveInfos) {
if (packages.contains(info.activityInfo.packageName)) continue;
packages.add(info.activityInfo.packageName);
PackageManager packageManager, Map<String, ResolveInfo> resolveInfos) {
boolean hasBrowserSupportingWebApk = false;
for (String installedBrowserPackage : resolveInfos.keySet()) {
if (HostBrowserUtils.doesBrowserSupportWebApks(installedBrowserPackage)) {
hasBrowserSupportingWebApk = true;
break;
}
}
browsers.add(new BrowserItem(info.activityInfo.packageName,
info.loadLabel(packageManager), info.loadIcon(packageManager),
browsersSupportingWebApk.contains(info.activityInfo.packageName)));
List<BrowserItem> browsers = new ArrayList<>();
for (Map.Entry<String, ResolveInfo> entry : resolveInfos.entrySet()) {
String browserPackage = entry.getKey();
ResolveInfo info = entry.getValue();
boolean enable = !hasBrowserSupportingWebApk
|| HostBrowserUtils.doesBrowserSupportWebApks(browserPackage);
browsers.add(new BrowserItem(browserPackage, info.loadLabel(packageManager),
info.loadIcon(packageManager), enable));
}
if (browsers.size() <= 1) return browsers;
......@@ -168,10 +172,10 @@ public class ChooseHostBrowserDialog {
Collections.sort(browsers, new Comparator<BrowserItem>() {
@Override
public int compare(BrowserItem a, BrowserItem b) {
if (a.mSupportsWebApks == b.mSupportsWebApks) {
if (a.mEnable == b.mEnable) {
return a.getPackageName().compareTo(b.getPackageName());
}
return a.mSupportsWebApks ? -1 : 1;
return a.mEnable ? -1 : 1;
}
});
......@@ -205,8 +209,8 @@ public class ChooseHostBrowserDialog {
res.getDimensionPixelSize(R.dimen.list_column_padding), 0, 0, 0);
BrowserItem item = mBrowsers.get(position);
name.setEnabled(item.supportsWebApks());
if (item.supportsWebApks()) {
name.setEnabled(item.enable());
if (item.enable()) {
name.setText(item.getApplicationName());
name.setTextColor(WebApkUtils.getColor(res, R.color.black_alpha_87));
icon.setAlpha(SUPPORTED_ICON_OPACITY);
......@@ -224,13 +228,13 @@ public class ChooseHostBrowserDialog {
icon.setAlpha(UNSUPPORTED_ICON_OPACITY);
}
icon.setImageDrawable(item.getApplicationIcon());
icon.setEnabled(item.supportsWebApks());
icon.setEnabled(item.enable());
return convertView;
}
@Override
public boolean isEnabled(int position) {
return mBrowsers.get(position).supportsWebApks();
return mBrowsers.get(position).enable();
}
}
}
......@@ -15,9 +15,10 @@ import android.text.TextUtils;
import org.chromium.webapk.lib.common.WebApkMetaDataKeys;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Contains methods for getting information about host browser.
......@@ -39,8 +40,8 @@ public class HostBrowserUtils {
/**
* The package names of the browsers that support WebAPKs. The most preferred one comes first.
*/
private static List<String> sBrowsersSupportingWebApk =
new ArrayList<String>(Arrays.asList("com.google.android.apps.chrome",
private static Set<String> sBrowsersSupportingWebApk =
new HashSet<String>(Arrays.asList("com.google.android.apps.chrome",
"com.android.chrome", "com.chrome.beta", "com.chrome.dev", "com.chrome.canary",
"org.chromium.chrome", "org.chromium.chrome.tests", ARC_INTENT_HELPER_BROWSER));
......@@ -52,12 +53,9 @@ public class HostBrowserUtils {
sHostPackage = null;
}
/**
* Returns a list of browsers that support WebAPKs. TODO(hanxi): Replace this function once we
* figure out a better way to know which browser supports WebAPKs.
*/
public static List<String> getBrowsersSupportingWebApk() {
return sBrowsersSupportingWebApk;
/** Returns whether the passed-in browser package name supports WebAPKs. */
public static boolean doesBrowserSupportWebApks(String browserPackageName) {
return sBrowsersSupportingWebApk.contains(browserPackageName);
}
/**
......@@ -118,6 +116,10 @@ public class HostBrowserUtils {
/** Queries the given host browser's major version. */
public static int queryHostBrowserMajorChromiumVersion(
Context context, String hostBrowserPackageName) {
if (!doesBrowserSupportWebApks(hostBrowserPackageName)) {
return -1;
}
PackageInfo info;
try {
info = context.getPackageManager().getPackageInfo(hostBrowserPackageName, 0);
......@@ -145,7 +147,7 @@ public class HostBrowserUtils {
public static boolean shouldLaunchInTab(HostBrowserLauncherParams params) {
String hostBrowserPackageName = params.getHostBrowserPackageName();
int hostBrowserMajorChromiumVersion = params.getHostBrowserMajorChromiumVersion();
if (!sBrowsersSupportingWebApk.contains(hostBrowserPackageName)) {
if (!doesBrowserSupportWebApks(hostBrowserPackageName)) {
return true;
}
......@@ -193,26 +195,36 @@ public class HostBrowserUtils {
// Gets the package name of the default browser on the Android device.
// TODO(hanxi): Investigate the best way to know which browser supports WebAPKs.
String defaultBrowser = getDefaultBrowserPackageName(context.getPackageManager());
if (!TextUtils.isEmpty(defaultBrowser) && sBrowsersSupportingWebApk.contains(defaultBrowser)
String defaultBrowser = getDefaultBrowserPackageName(packageManager);
if (!TextUtils.isEmpty(defaultBrowser) && doesBrowserSupportWebApks(defaultBrowser)
&& WebApkUtils.isInstalled(packageManager, defaultBrowser)) {
return defaultBrowser;
}
Map<String, ResolveInfo> installedBrowsers =
WebApkUtils.getInstalledBrowserResolveInfos(packageManager);
if (installedBrowsers.size() == 1) {
return installedBrowsers.keySet().iterator().next();
}
// If there is only one browser supporting WebAPK, and we can't decide which browser to use
// by looking up cache, metadata and default browser, open with that browser.
int availableBrowserCounter = 0;
int numSupportedBrowsersInstalled = 0;
String lastSupportedBrowser = null;
for (String packageName : sBrowsersSupportingWebApk) {
if (availableBrowserCounter > 1) break;
if (WebApkUtils.isInstalled(packageManager, packageName)) {
availableBrowserCounter++;
lastSupportedBrowser = packageName;
for (String browserPackageName : installedBrowsers.keySet()) {
if (numSupportedBrowsersInstalled > 1) break;
if (doesBrowserSupportWebApks(browserPackageName)) {
numSupportedBrowsersInstalled++;
lastSupportedBrowser = browserPackageName;
}
}
if (availableBrowserCounter == 1) {
if (numSupportedBrowsersInstalled == 1) {
return lastSupportedBrowser;
}
if (numSupportedBrowsersInstalled == 0 && installedBrowsers.containsKey(defaultBrowser)) {
return defaultBrowser;
}
return null;
}
......@@ -227,9 +239,7 @@ public class HostBrowserUtils {
Intent browserIntent = WebApkUtils.getQueryInstalledBrowsersIntent();
ResolveInfo resolveInfo =
packageManager.resolveActivity(browserIntent, PackageManager.MATCH_DEFAULT_ONLY);
if (resolveInfo == null || resolveInfo.activityInfo == null) return null;
return resolveInfo.activityInfo.packageName;
return WebApkUtils.getPackageNameFromResolveInfo(resolveInfo);
}
/** Deletes the internal storage for the given context. */
......
......@@ -16,8 +16,7 @@ import android.util.Log;
import org.chromium.webapk.lib.common.WebApkMetaDataKeys;
import java.util.List;
import java.util.Set;
import java.util.Map;
/** Selects host browser to launch, showing a dialog to select browser if necessary. */
public class LaunchHostBrowserSelector {
......@@ -77,9 +76,9 @@ public class LaunchHostBrowserSelector {
return;
}
Set<ResolveInfo> infos =
Map<String, ResolveInfo> infos =
WebApkUtils.getInstalledBrowserResolveInfos(mContext.getPackageManager());
if (hasBrowserSupportingWebApks(infos)) {
if (!infos.isEmpty()) {
showChooseHostBrowserDialog(infos, selectCallback);
} else {
showInstallHostBrowserDialog(metadata, selectCallback);
......@@ -96,19 +95,9 @@ public class LaunchHostBrowserSelector {
}
}
/** Returns whether there is any installed browser supporting WebAPKs. */
private static boolean hasBrowserSupportingWebApks(Set<ResolveInfo> resolveInfos) {
List<String> browsersSupportingWebApk = HostBrowserUtils.getBrowsersSupportingWebApk();
for (ResolveInfo info : resolveInfos) {
if (browsersSupportingWebApk.contains(info.activityInfo.packageName)) {
return true;
}
}
return false;
}
/** Shows a dialog to choose the host browser. */
private void showChooseHostBrowserDialog(Set<ResolveInfo> infos, Callback selectCallback) {
private void showChooseHostBrowserDialog(
Map<String, ResolveInfo> infos, Callback selectCallback) {
ChooseHostBrowserDialog.DialogListener listener =
new ChooseHostBrowserDialog.DialogListener() {
@Override
......
......@@ -39,6 +39,10 @@ public class WebApkServiceFactory extends Service {
@Override
public IBinder onBind(Intent intent) {
final String hostBrowserPackage = HostBrowserUtils.getCachedHostBrowserPackage(this);
if (!HostBrowserUtils.doesBrowserSupportWebApks(hostBrowserPackage)) {
Log.w(TAG, "Host browser does not support WebAPKs.");
return null;
}
ClassLoader webApkClassLoader = HostBrowserClassLoader.getClassLoaderInstance(
this, hostBrowserPackage, WEBAPK_SERVICE_IMPL_CLASS_NAME);
if (webApkClassLoader == null) {
......
......@@ -34,9 +34,9 @@ import android.widget.TextView;
import org.chromium.webapk.lib.common.WebApkMetaDataKeys;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.Map;
/**
* Contains utility methods for interacting with WebAPKs.
......@@ -105,22 +105,31 @@ public class WebApkUtils {
return returnUrlBuilder.toString();
}
/** Returns a set of ResolveInfo for all of the installed browsers. */
public static Set<ResolveInfo> getInstalledBrowserResolveInfos(PackageManager packageManager) {
/** Returns a browser-package-name->ResolveInfo mapping for all of the installed browsers. */
public static Map<String, ResolveInfo> getInstalledBrowserResolveInfos(
PackageManager packageManager) {
Intent browserIntent = getQueryInstalledBrowsersIntent();
// Note: {@link PackageManager#queryIntentActivities()} does not return ResolveInfos for
// disabled browsers.
Set<ResolveInfo> result = new HashSet<>();
List<ResolveInfo> resolveInfosAll =
List<ResolveInfo> resolveInfos =
packageManager.queryIntentActivities(browserIntent, PackageManager.MATCH_ALL);
List<ResolveInfo> resolveInfosDefaultOnly = packageManager.queryIntentActivities(
browserIntent, PackageManager.MATCH_DEFAULT_ONLY);
resolveInfos.addAll(packageManager.queryIntentActivities(
browserIntent, PackageManager.MATCH_DEFAULT_ONLY));
result.addAll(resolveInfosAll);
result.addAll(resolveInfosDefaultOnly);
Map<String, ResolveInfo> result = new HashMap<>();
for (ResolveInfo resolveInfo : resolveInfos) {
result.put(getPackageNameFromResolveInfo(resolveInfo), resolveInfo);
}
return result;
}
/** Returns the package name for the passed-in ResolveInfo. */
public static String getPackageNameFromResolveInfo(ResolveInfo resolveInfo) {
return (resolveInfo != null && resolveInfo.activityInfo != null)
? resolveInfo.activityInfo.packageName
: null;
}
/** Builds a context for the passed in remote package name. */
public static Context fetchRemoteContext(Context context, String remotePackageName) {
try {
......
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