Chromium Code Reviews| Index: chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/WebApkUtils.java | 
| diff --git a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/WebApkUtils.java b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/WebApkUtils.java | 
| index a9b5424f22948798560bdbc803c658000c769658..6935f3b4c68cc7e78fab6a122803a2804fc3b7d9 100644 | 
| --- a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/WebApkUtils.java | 
| +++ b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/WebApkUtils.java | 
| @@ -5,24 +5,94 @@ | 
| package org.chromium.webapk.shell_apk; | 
| import android.content.Context; | 
| +import android.content.Intent; | 
| +import android.content.SharedPreferences; | 
| import android.content.pm.ApplicationInfo; | 
| import android.content.pm.PackageManager; | 
| import android.content.pm.PackageManager.NameNotFoundException; | 
| +import android.content.pm.ResolveInfo; | 
| +import android.graphics.drawable.Drawable; | 
| +import android.net.Uri; | 
| import android.os.Bundle; | 
| +import android.text.TextUtils; | 
| +import org.chromium.webapk.lib.common.WebApkConstants; | 
| import org.chromium.webapk.lib.common.WebApkMetaDataKeys; | 
| +import java.util.ArrayList; | 
| +import java.util.Arrays; | 
| +import java.util.HashSet; | 
| +import java.util.List; | 
| +import java.util.Set; | 
| + | 
| /** | 
| * Contains utility methods for interacting with WebAPKs. | 
| */ | 
| public class WebApkUtils { | 
| + public static final String SHARED_PREF_RUNTIME_HOST = "runtime_host"; | 
| + | 
| + // The package names of the channels of Chrome that support WebAPKs. The most preferred one | 
| + // comes first. | 
| + private static List<String> sBrowsersSupportingWebApk = new ArrayList<String>( | 
| + Arrays.asList("com.google.android.apps.chrome", "com.android.chrome", "com.chrome.beta", | 
| + "com.chrome.dev", "com.chrome.canary")); | 
| + | 
| + /** | 
| + * Description of a single item in the list of browsers that user can choose. The list is owned | 
| + * by a dialog to choose the host browser. | 
| + */ | 
| 
 
pkotwicz
2017/05/24 04:19:30
How about: "Stores information about a potential h
 
Xi Han
2017/05/24 16:52:37
Thanks!
 
 | 
| + public static class BrowserItem { | 
| + private String mPackageName; | 
| + private CharSequence mApplicationLabel; | 
| + private Drawable mIcon; | 
| + private boolean mIsEnabled; | 
| + | 
| + public BrowserItem(String packageName, CharSequence applicationLabel, Drawable icon, | 
| + boolean isEnabled) { | 
| + mPackageName = packageName; | 
| + mApplicationLabel = applicationLabel; | 
| + mIcon = icon; | 
| + mIsEnabled = isEnabled; | 
| + } | 
| + | 
| + // Returns the package name of a browser. | 
| + public String getPackageName() { | 
| + return mPackageName; | 
| + } | 
| + | 
| + // Returns the application name of a browser. | 
| + public CharSequence getApplicationName() { | 
| + return mApplicationLabel; | 
| + } | 
| + | 
| + // Returns a drawable of the browser icon. | 
| + public Drawable getApplicationIcon() { | 
| + return mIcon; | 
| + } | 
| + | 
| + // Returns whether this BrowserItem is clickable. An BrowserItem is clickable only if the | 
| + // browser it represents supporting WebAPKs. | 
| + public boolean isEnabled() { | 
| + return mIsEnabled; | 
| + } | 
| + } | 
| /** | 
| - * Caches the value read from Application Metadata which specifies the host browser's package | 
| - * name. | 
| + * Caches the package name of the host browser. {@link sHostPackage} might refer to a browser | 
| + * which has been uninstalled. A notification can keep the WebAPK process alive after the host | 
| + * browser has been uninstalled. | 
| */ | 
| private static String sHostPackage; | 
| + // For testing only. | 
| + public static void resetCachedHostPackageForTesting() { | 
| + sHostPackage = null; | 
| + } | 
| + | 
| + public static void setBrowsersSupportingWebApkForTesting(List<String> browsers) { | 
| + sBrowsersSupportingWebApk = browsers; | 
| + } | 
| + | 
| /** | 
| * Returns a Context for the host browser that was specified when building the WebAPK. | 
| * @param context A context. | 
| @@ -41,27 +111,95 @@ public class WebApkUtils { | 
| } | 
| /** | 
| - * Returns the package name for the host browser that was specified when building the WebAPK. | 
| + * Returns the package name of the host browser to launch the WebAPK. Also caches the package | 
| + * name in the SharedPreference if it is not null. | 
| * @param context A context. | 
| * @return The package name. Returns null on an error. | 
| */ | 
| public static String getHostBrowserPackageName(Context context) { | 
| - if (sHostPackage != null) return sHostPackage; | 
| - String hostPackage = null; | 
| - try { | 
| - ApplicationInfo ai = context.getPackageManager().getApplicationInfo( | 
| - context.getPackageName(), PackageManager.GET_META_DATA); | 
| - Bundle bundle = ai.metaData; | 
| - hostPackage = bundle.getString(WebApkMetaDataKeys.RUNTIME_HOST); | 
| - } catch (NameNotFoundException e) { | 
| - e.printStackTrace(); | 
| + if (sHostPackage == null) { | 
| + sHostPackage = getHostBrowserPackageNameInternal(context); | 
| + if (sHostPackage != null) { | 
| + writeHostBrowserToSharedPref(context, sHostPackage); | 
| + } | 
| } | 
| - // Set {@link sHostPackage} to a non-null value so that the value is computed only once. | 
| - sHostPackage = hostPackage != null ? hostPackage : ""; | 
| + | 
| return sHostPackage; | 
| } | 
| /** | 
| + * Returns the package name of the host browser to launch the WebAPK, or null if not find one. | 
| + */ | 
| 
 
pkotwicz
2017/05/24 04:19:30
Nit: "or null if not find one." -> "or null if one
 
Xi Han
2017/05/24 16:52:37
Sounds better, thanks!
 
 | 
| + private static String getHostBrowserPackageNameInternal(Context context) { | 
| + Set<String> installedBrowsers = getInstalledBrowsers(context.getPackageManager()); | 
| + if (installedBrowsers.isEmpty()) { | 
| + return null; | 
| + } | 
| + | 
| 
 
pkotwicz
2017/05/24 04:19:30
Nit: You can probably move this below line 155 lik
 
Xi Han
2017/05/24 16:52:37
I had a discussion with Yaron about the order, and
 
pkotwicz
2017/05/25 02:59:08
I don't have a strong preference.
I would add an
 
Xi Han
2017/05/25 19:29:32
I updated to your suggested solution, since sticki
 
 | 
| + // Gets the package name of the host browser if it is specified in AndroidManifest.xml. | 
| + String hostBrowserFromManifest = getHostBrowserFromAndroidManifest( | 
| + context.getPackageManager(), context.getPackageName()); | 
| + boolean hasHostBrowserSpecified = false; | 
| + if (!TextUtils.isEmpty(hostBrowserFromManifest)) { | 
| + hasHostBrowserSpecified = true; | 
| + if (installedBrowsers.contains(hostBrowserFromManifest)) { | 
| + return hostBrowserFromManifest; | 
| + } | 
| + } | 
| + | 
| + // Gets the package name of the host browser if it is stored in the SharedPreference. | 
| + String cachedHostBrowser = getHostBrowserFromSharedPreference(context); | 
| + if (!TextUtils.isEmpty(cachedHostBrowser) | 
| + && installedBrowsers.contains(cachedHostBrowser)) { | 
| + return cachedHostBrowser; | 
| + } else if (hasHostBrowserSpecified) { | 
| + return null; | 
| + } | 
| + | 
| + deleteSharedPref(context); | 
| 
 
pkotwicz
2017/05/24 04:19:30
This omits the data cached as a result of DexOptim
 
Xi Han
2017/05/24 16:52:37
I have been thinking about this. From my understan
 
pkotwicz
2017/05/25 02:59:09
Sorry for the confusion. Deleting the SharedPref i
 
Xi Han
2017/05/25 19:29:32
I see. Yes, that part will be in a follow up CL.
 
 | 
| + | 
| + // 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) && installedBrowsers.contains(defaultBrowser) | 
| + && sBrowsersSupportingWebApk.contains(defaultBrowser)) { | 
| + return defaultBrowser; | 
| + } | 
| + | 
| + return null; | 
| + } | 
| + | 
| + /** | 
| + * Returns a list of browsers to choose host browser. The list includes all the installed | 
| 
 
pkotwicz
2017/05/24 04:19:30
Nit: "to choose host browser" -> "to choose host b
 
Xi Han
2017/05/24 16:52:37
Done.
 
 | 
| + * browser, and if none of the installed browser supports WebAPKs, Chrome will be added to the | 
| 
 
pkotwicz
2017/05/24 04:19:30
"all of the installed browser" -> "all of the inst
 
Xi Han
2017/05/24 16:52:37
Acknowledged.
 
 | 
| + * list as well. | 
| + */ | 
| + public static List<BrowserItem> getBrowserInfosForHostBrowserSelection( | 
| + PackageManager packageManager) { | 
| + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://")); | 
| + List<ResolveInfo> resolvedActivityList = packageManager.queryIntentActivities( | 
| + browserIntent, PackageManager.MATCH_DEFAULT_ONLY); | 
| + | 
| + boolean hasBrowserSupportingWebApks = false; | 
| + List<BrowserItem> browsers = new ArrayList<>(); | 
| + for (ResolveInfo info : resolvedActivityList) { | 
| + boolean supportWebApk = false; | 
| + if (sBrowsersSupportingWebApk.contains(info.activityInfo.packageName)) { | 
| + supportWebApk = true; | 
| 
 
pkotwicz
2017/05/24 04:19:30
Nit: supportWebApk -> supportsWebApk
The browser
 
Xi Han
2017/05/24 16:52:37
Done.
 
 | 
| + hasBrowserSupportingWebApks = true; | 
| + } | 
| + browsers.add(new BrowserItem(info.activityInfo.packageName, | 
| + info.loadLabel(packageManager), info.loadIcon(packageManager), supportWebApk)); | 
| + } | 
| + | 
| + if (hasBrowserSupportingWebApks) return browsers; | 
| + | 
| + // TODO(hanxi): add Chrome's icon to WebAPKs. | 
| + browsers.add(new BrowserItem("com.android.chrome", "Chrome", null, true)); | 
| + return browsers; | 
| + } | 
| + | 
| + /** | 
| * Returns the uid for the host browser that was specified when building the WebAPK. | 
| * @param context A context. | 
| * @return The application uid. Returns -1 on an error. | 
| @@ -81,4 +219,71 @@ public class WebApkUtils { | 
| } | 
| return -1; | 
| } | 
| + | 
| + /** Returns the package name of the host browser cached in the SharedPreferences. */ | 
| + private static String getHostBrowserFromSharedPreference(Context context) { | 
| + SharedPreferences sharedPref = | 
| + context.getSharedPreferences(WebApkConstants.PREF_PACKAGE, Context.MODE_PRIVATE); | 
| + return sharedPref.getString(SHARED_PREF_RUNTIME_HOST, null); | 
| + } | 
| + | 
| + /** Returns a set of package names of all the installed browsers on the device. */ | 
| + public static Set<String> getInstalledBrowsers(PackageManager packageManager) { | 
| + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://")); | 
| + List<ResolveInfo> resolvedActivityList = packageManager.queryIntentActivities( | 
| + browserIntent, PackageManager.MATCH_DEFAULT_ONLY); | 
| + | 
| + Set<String> packagesSupportingWebApks = new HashSet<String>(); | 
| + for (ResolveInfo info : resolvedActivityList) { | 
| + packagesSupportingWebApks.add(info.activityInfo.packageName); | 
| + } | 
| + return packagesSupportingWebApks; | 
| + } | 
| + | 
| + /** Returns the package name of the "runtime host" in the AndroidManifest.xml. */ | 
| + public static String getHostBrowserFromAndroidManifest( | 
| + PackageManager packageManager, String webApkPackageName) { | 
| + try { | 
| + ApplicationInfo ai = packageManager.getApplicationInfo( | 
| + webApkPackageName, PackageManager.GET_META_DATA); | 
| + Bundle bundle = ai.metaData; | 
| + return bundle.getString(WebApkMetaDataKeys.RUNTIME_HOST); | 
| + } catch (NameNotFoundException e) { | 
| + return null; | 
| + } | 
| + } | 
| + | 
| + /** Returns the package name of the default browser on the Android device. */ | 
| + private static String getDefaultBrowserPackageName(PackageManager packageManager) { | 
| + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://")); | 
| + ResolveInfo resolveInfo = | 
| + packageManager.resolveActivity(browserIntent, PackageManager.MATCH_DEFAULT_ONLY); | 
| + if (resolveInfo == null || resolveInfo.activityInfo == null) return null; | 
| + | 
| + return resolveInfo.activityInfo.packageName; | 
| + } | 
| + | 
| + /** | 
| + * Writes the package name of the host browser to the SharedPreferences. If the host browser is | 
| + * different than the previous one stored, delete the SharedPreference before storing the new | 
| + * host browser. | 
| + */ | 
| + public static void writeHostBrowserToSharedPref(Context context, String hostPackage) { | 
| + if (TextUtils.isEmpty(hostPackage)) return; | 
| + | 
| + SharedPreferences sharedPref = | 
| + context.getSharedPreferences(WebApkConstants.PREF_PACKAGE, Context.MODE_PRIVATE); | 
| + SharedPreferences.Editor editor = sharedPref.edit(); | 
| + editor.putString(SHARED_PREF_RUNTIME_HOST, hostPackage); | 
| + editor.apply(); | 
| + } | 
| + | 
| + /** Deletes the SharedPreferences. */ | 
| + private static void deleteSharedPref(Context context) { | 
| + SharedPreferences sharedPref = | 
| + context.getSharedPreferences(WebApkConstants.PREF_PACKAGE, Context.MODE_PRIVATE); | 
| + SharedPreferences.Editor editor = sharedPref.edit(); | 
| + editor.clear(); | 
| + editor.commit(); | 
| + } | 
| } |