| // Copyright 2017 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; |
| |
| 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.net.Uri; |
| import android.support.annotation.IntDef; |
| import android.text.TextUtils; |
| |
| import org.chromium.base.BuildInfo; |
| import org.chromium.base.ContextUtils; |
| import org.chromium.base.library_loader.LibraryProcessType; |
| import org.chromium.base.metrics.RecordHistogram; |
| import org.chromium.base.task.AsyncTask; |
| import org.chromium.base.task.BackgroundOnlyAsyncTask; |
| import org.chromium.chrome.R; |
| import org.chromium.chrome.browser.preferences.ChromePreferenceManager; |
| import org.chromium.content_public.browser.BrowserStartupController; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.RejectedExecutionException; |
| |
| /** |
| * A utility class for querying information about the default browser setting. |
| */ |
| public final class DefaultBrowserInfo { |
| private static final String SAMPLE_URL = "https://www.madeupdomainforcheck123.com/"; |
| |
| /** |
| * A list of potential default browser states. To add a type to this list please update |
| * MobileDefaultBrowserState in histograms.xml and make sure to keep this list in sync. |
| * Additions should be treated as APPEND ONLY to keep the UMA metric semantics the same over |
| * time. |
| */ |
| @IntDef({MobileDefaultBrowserState.NO_DEFAULT, MobileDefaultBrowserState.CHROME_SYSTEM_DEFAULT, |
| MobileDefaultBrowserState.CHROME_INSTALLED_DEFAULT, |
| MobileDefaultBrowserState.OTHER_SYSTEM_DEFAULT, |
| MobileDefaultBrowserState.OTHER_INSTALLED_DEFAULT}) |
| @Retention(RetentionPolicy.SOURCE) |
| private @interface MobileDefaultBrowserState { |
| int NO_DEFAULT = 0; |
| int CHROME_SYSTEM_DEFAULT = 1; |
| int CHROME_INSTALLED_DEFAULT = 2; |
| int OTHER_SYSTEM_DEFAULT = 3; |
| int OTHER_INSTALLED_DEFAULT = 4; |
| int NUM_ENTRIES = 5; |
| } |
| |
| /** |
| * Helper class for passing information about the system's default browser settings back from a |
| * worker task. |
| */ |
| private static class DefaultInfo { |
| public boolean isChromeSystem; |
| public boolean isChromeDefault; |
| public boolean isDefaultSystem; |
| public boolean hasDefault; |
| public int browserCount; |
| public int systemCount; |
| } |
| |
| /** A lock to synchronize background tasks to retrieve browser information. */ |
| private static final Object sDirCreationLock = new Object(); |
| |
| private static AsyncTask<ArrayList<String>> sDefaultBrowserFetcher; |
| |
| /** Don't instantiate me. */ |
| private DefaultBrowserInfo() {} |
| |
| /** |
| * Initialize an AsyncTask for getting menu title of opening a link in default browser. |
| */ |
| public static void initBrowserFetcher() { |
| synchronized (sDirCreationLock) { |
| if (sDefaultBrowserFetcher == null) { |
| sDefaultBrowserFetcher = new BackgroundOnlyAsyncTask<ArrayList<String>>() { |
| @Override |
| protected ArrayList<String> doInBackground() { |
| Context context = ContextUtils.getApplicationContext(); |
| ArrayList<String> menuTitles = new ArrayList<String>(2); |
| // Store the package label of current application. |
| menuTitles.add(getTitleFromPackageLabel( |
| context, BuildInfo.getInstance().hostPackageLabel)); |
| |
| PackageManager pm = context.getPackageManager(); |
| ResolveInfo info = getResolveInfoForViewIntent(pm); |
| |
| // Caches whether Chrome is set as a default browser on the device. |
| boolean isDefault = info != null && info.match != 0 |
| && TextUtils.equals( |
| context.getPackageName(), info.activityInfo.packageName); |
| ChromePreferenceManager.getInstance().writeBoolean( |
| ChromePreferenceManager.CHROME_DEFAULT_BROWSER, isDefault); |
| |
| // Check if there is a default handler for the Intent. If so, store its |
| // label. |
| String packageLabel = null; |
| if (info != null && info.match != 0 && info.loadLabel(pm) != null) { |
| packageLabel = info.loadLabel(pm).toString(); |
| } |
| menuTitles.add(getTitleFromPackageLabel(context, packageLabel)); |
| return menuTitles; |
| } |
| }; |
| sDefaultBrowserFetcher.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
| } |
| } |
| } |
| |
| private static String getTitleFromPackageLabel(Context context, String packageLabel) { |
| return packageLabel == null |
| ? context.getString(R.string.menu_open_in_product_default) |
| : context.getString(R.string.menu_open_in_product, packageLabel); |
| } |
| |
| /** |
| * @return Default ResolveInfo to handle a VIEW intent for a url. |
| * @param pm The PackageManager of current context. |
| */ |
| private static ResolveInfo getResolveInfoForViewIntent(PackageManager pm) { |
| Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(SAMPLE_URL)); |
| try { |
| return pm.resolveActivity(intent, 0); |
| } catch (NullPointerException e) { |
| return null; |
| } |
| } |
| |
| private static List<ResolveInfo> getResolveInfoListForViewIntent(PackageManager pm) { |
| Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(SAMPLE_URL)); |
| try { |
| return pm.queryIntentActivities(intent, PackageManager.MATCH_ALL); |
| } catch (NullPointerException ex) { |
| return null; |
| } |
| } |
| |
| /** |
| * @return Title of the menu item for opening a link in the default browser. |
| * @param forceChromeAsDefault Whether the Custom Tab is created by Chrome. |
| */ |
| public static String getTitleOpenInDefaultBrowser(final boolean forceChromeAsDefault) { |
| if (sDefaultBrowserFetcher == null) { |
| initBrowserFetcher(); |
| } |
| try { |
| // If the Custom Tab was created by Chrome, Chrome should handle the action for the |
| // overflow menu. |
| return forceChromeAsDefault ? sDefaultBrowserFetcher.get().get(0) |
| : sDefaultBrowserFetcher.get().get(1); |
| } catch (InterruptedException | ExecutionException e) { |
| return ContextUtils.getApplicationContext().getString( |
| R.string.menu_open_in_product_default); |
| } |
| } |
| |
| /** |
| * Log statistics about the current default browser to UMA. |
| */ |
| public static void logDefaultBrowserStats() { |
| assert BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER) |
| .isStartupSuccessfullyCompleted(); |
| |
| try { |
| new AsyncTask<DefaultInfo>() { |
| @Override |
| protected DefaultInfo doInBackground() { |
| Context context = ContextUtils.getApplicationContext(); |
| |
| PackageManager pm = context.getPackageManager(); |
| |
| DefaultInfo info = new DefaultInfo(); |
| |
| // Query the default handler first. |
| ResolveInfo defaultRi = getResolveInfoForViewIntent(pm); |
| if (defaultRi != null && defaultRi.match != 0) { |
| info.hasDefault = true; |
| info.isChromeDefault = isSamePackage(context, defaultRi); |
| info.isDefaultSystem = isSystemPackage(defaultRi); |
| } |
| |
| // Query all other intent handlers. |
| Set<String> uniquePackages = new HashSet<>(); |
| List<ResolveInfo> ris = getResolveInfoListForViewIntent(pm); |
| if (ris != null) { |
| for (ResolveInfo ri : ris) { |
| String packageName = ri.activityInfo.applicationInfo.packageName; |
| if (!uniquePackages.add(packageName)) continue; |
| |
| if (isSystemPackage(ri)) { |
| if (isSamePackage(context, ri)) info.isChromeSystem = true; |
| info.systemCount++; |
| } |
| } |
| } |
| |
| info.browserCount = uniquePackages.size(); |
| |
| return info; |
| } |
| |
| @Override |
| protected void onPostExecute(DefaultInfo info) { |
| if (info == null) return; |
| |
| RecordHistogram.recordCount100Histogram( |
| getSystemBrowserCountUmaName(info), info.systemCount); |
| RecordHistogram.recordCount100Histogram( |
| getDefaultBrowserCountUmaName(info), info.browserCount); |
| RecordHistogram.recordEnumeratedHistogram("Mobile.DefaultBrowser.State", |
| getDefaultBrowserUmaState(info), MobileDefaultBrowserState.NUM_ENTRIES); |
| } |
| } |
| .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
| } catch (RejectedExecutionException ex) { |
| // Fail silently here since this is not a critical task. |
| } |
| } |
| |
| private static String getSystemBrowserCountUmaName(DefaultInfo info) { |
| if (info.isChromeSystem) return "Mobile.DefaultBrowser.SystemBrowserCount.ChromeSystem"; |
| return "Mobile.DefaultBrowser.SystemBrowserCount.ChromeNotSystem"; |
| } |
| |
| private static String getDefaultBrowserCountUmaName(DefaultInfo info) { |
| if (!info.hasDefault) return "Mobile.DefaultBrowser.BrowserCount.NoDefault"; |
| if (info.isChromeDefault) return "Mobile.DefaultBrowser.BrowserCount.ChromeDefault"; |
| return "Mobile.DefaultBrowser.BrowserCount.OtherDefault"; |
| } |
| |
| private static @MobileDefaultBrowserState int getDefaultBrowserUmaState(DefaultInfo info) { |
| if (!info.hasDefault) return MobileDefaultBrowserState.NO_DEFAULT; |
| |
| if (info.isChromeDefault) { |
| if (info.isDefaultSystem) return MobileDefaultBrowserState.CHROME_SYSTEM_DEFAULT; |
| return MobileDefaultBrowserState.CHROME_INSTALLED_DEFAULT; |
| } |
| |
| if (info.isDefaultSystem) return MobileDefaultBrowserState.OTHER_SYSTEM_DEFAULT; |
| return MobileDefaultBrowserState.OTHER_INSTALLED_DEFAULT; |
| } |
| |
| private static boolean isSamePackage(Context context, ResolveInfo info) { |
| return TextUtils.equals( |
| context.getPackageName(), info.activityInfo.applicationInfo.packageName); |
| } |
| |
| private static boolean isSystemPackage(ResolveInfo info) { |
| return (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; |
| } |
| } |