| // 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.pm.ApplicationInfo; |
| import android.content.pm.ResolveInfo; |
| import android.text.TextUtils; |
| |
| import androidx.annotation.VisibleForTesting; |
| |
| import org.chromium.base.Callback; |
| import org.chromium.base.ContextUtils; |
| import org.chromium.base.ObserverList; |
| import org.chromium.base.PackageManagerUtils; |
| import org.chromium.base.ThreadUtils; |
| import org.chromium.base.task.AsyncTask; |
| import org.chromium.base.task.PostTask; |
| import org.chromium.content_public.browser.UiThreadTaskTraits; |
| |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.RejectedExecutionException; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| /** |
| * A utility class for querying information about the default browser setting. |
| * TODO(crbug.com/1112519): Remove DefaultBrowserInfo and replace with this. |
| */ |
| public final class DefaultBrowserInfo2 { |
| /** Contains all status related to the default browser state on the device. */ |
| public static class DefaultInfo { |
| /** Whether or not Chrome is the system browser. */ |
| public final boolean isChromeSystem; |
| |
| /** Whether or not Chrome is the default browser. */ |
| public final boolean isChromeDefault; |
| |
| /** Whether or not the default browser is the system browser. */ |
| public final boolean isDefaultSystem; |
| |
| /** Whether or not the user has set a default browser. */ |
| public final boolean hasDefault; |
| |
| /** The number of browsers installed on this device. */ |
| public final int browserCount; |
| |
| /** The number of system browsers installed on this device. */ |
| public final int systemCount; |
| |
| /** Creates an instance of the {@link DefaultInfo} class. */ |
| public DefaultInfo(boolean isChromeSystem, boolean isChromeDefault, boolean isDefaultSystem, |
| boolean hasDefault, int browserCount, int systemCount) { |
| this.isChromeSystem = isChromeSystem; |
| this.isChromeDefault = isChromeDefault; |
| this.isDefaultSystem = isDefaultSystem; |
| this.hasDefault = hasDefault; |
| this.browserCount = browserCount; |
| this.systemCount = systemCount; |
| } |
| } |
| |
| private static DefaultInfoTask sDefaultInfoTask; |
| |
| /** Don't instantiate me. */ |
| private DefaultBrowserInfo2() {} |
| |
| /** |
| * Determines various information about browsers on the system. |
| * @param callback To be called with a {@link DefaultInfo} instance if possible. Can be {@code |
| * null}. |
| * @see DefaultInfo |
| */ |
| public static void getDefaultBrowserInfo(Callback<DefaultInfo> callback) { |
| ThreadUtils.checkUiThread(); |
| if (sDefaultInfoTask == null) sDefaultInfoTask = new DefaultInfoTask(); |
| sDefaultInfoTask.get(callback); |
| } |
| |
| @VisibleForTesting |
| public static void setDefaultInfoForTests(DefaultInfo info) { |
| DefaultInfoTask.setDefaultInfoForTests(info); |
| } |
| |
| @VisibleForTesting |
| public static void clearDefaultInfoForTests() { |
| DefaultInfoTask.clearDefaultInfoForTests(); |
| } |
| |
| private static class DefaultInfoTask extends AsyncTask<DefaultInfo> { |
| private static AtomicReference<DefaultInfo> sTestInfo; |
| |
| private final ObserverList<Callback<DefaultInfo>> mObservers = new ObserverList<>(); |
| |
| @VisibleForTesting |
| public static void setDefaultInfoForTests(DefaultInfo info) { |
| sTestInfo = new AtomicReference<DefaultInfo>(info); |
| } |
| |
| public static void clearDefaultInfoForTests() { |
| sTestInfo = null; |
| } |
| |
| /** |
| * Queues up {@code callback} to be notified of the result of this {@link AsyncTask}. If |
| * the task has not been started, this will start it. If the task is finished, this will |
| * send the result. If the task is running this will queue the callback up until the task |
| * is done. |
| * |
| * @param callback The {@link Callback} to notify with the right {@link DefaultInfo}. |
| */ |
| public void get(Callback<DefaultInfo> callback) { |
| ThreadUtils.checkUiThread(); |
| |
| if (getStatus() == Status.FINISHED) { |
| DefaultInfo info = null; |
| try { |
| info = sTestInfo == null ? get() : sTestInfo.get(); |
| } catch (InterruptedException | ExecutionException e) { |
| // Fail silently here since this is not a critical task. |
| } |
| |
| final DefaultInfo postInfo = info; |
| PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> callback.onResult(postInfo)); |
| } else { |
| if (getStatus() == Status.PENDING) { |
| try { |
| executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
| } catch (RejectedExecutionException e) { |
| // Fail silently here since this is not a critical task. |
| PostTask.postTask( |
| UiThreadTaskTraits.DEFAULT, () -> callback.onResult(null)); |
| return; |
| } |
| } |
| mObservers.addObserver(callback); |
| } |
| } |
| |
| @Override |
| protected DefaultInfo doInBackground() { |
| Context context = ContextUtils.getApplicationContext(); |
| |
| boolean isChromeSystem = false; |
| boolean isChromeDefault = false; |
| boolean isDefaultSystem = false; |
| boolean hasDefault = false; |
| int browserCount = 0; |
| int systemCount = 0; |
| |
| // Query the default handler first. |
| ResolveInfo defaultRi = PackageManagerUtils.resolveDefaultWebBrowserActivity(); |
| if (defaultRi != null && defaultRi.match != 0) { |
| hasDefault = true; |
| isChromeDefault = isSamePackage(context, defaultRi); |
| isDefaultSystem = isSystemPackage(defaultRi); |
| } |
| |
| // Query all other intent handlers. |
| Set<String> uniquePackages = new HashSet<>(); |
| List<ResolveInfo> ris = PackageManagerUtils.queryAllWebBrowsersInfo(); |
| 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)) isChromeSystem = true; |
| systemCount++; |
| } |
| } |
| } |
| |
| browserCount = uniquePackages.size(); |
| |
| return new DefaultInfo(isChromeSystem, isChromeDefault, isDefaultSystem, hasDefault, |
| browserCount, systemCount); |
| } |
| |
| @Override |
| protected void onPostExecute(DefaultInfo defaultInfo) { |
| flushCallbacks(sTestInfo == null ? defaultInfo : sTestInfo.get()); |
| } |
| |
| @Override |
| protected void onCancelled() { |
| flushCallbacks(null); |
| } |
| |
| private void flushCallbacks(DefaultInfo info) { |
| for (Callback<DefaultInfo> callback : mObservers) callback.onResult(info); |
| mObservers.clear(); |
| } |
| } |
| |
| 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; |
| } |
| } |