1 // Copyright 2019 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base; 6 7 import android.content.Intent; 8 import android.content.pm.PackageManager; 9 import android.content.pm.ResolveInfo; 10 import android.net.Uri; 11 import android.os.TransactionTooLargeException; 12 13 import java.util.Collections; 14 import java.util.List; 15 16 /** This class provides Android PackageManager related utility methods. */ 17 public class PackageManagerUtils { 18 private static final String TAG = "PackageManagerUtils"; 19 20 // This is the intent Android uses internally to detect browser apps. 21 // See 22 // https://cs.android.com/android/_/android/platform/packages/modules/Permission/+/android12-release:PermissionController/src/com/android/permissioncontroller/role/model/BrowserRoleBehavior.java;drc=86fa7d5dfa43f66b170f93ade4f59b9a770be32f;l=50 23 public static final Intent BROWSER_INTENT = 24 new Intent() 25 .setAction(Intent.ACTION_VIEW) 26 .addCategory(Intent.CATEGORY_BROWSABLE) 27 .setData(Uri.fromParts("http", "", null)); 28 29 /** 30 * Retrieve information about the Activity that will handle the given Intent. 31 * 32 * Note: This function is expensive on KK and below and should not be called from main thread 33 * when avoidable. 34 * 35 * @param intent Intent to resolve. 36 * @param flags The PackageManager flags to pass to resolveActivity(). 37 * @return ResolveInfo of the Activity that will handle the Intent, or null if it failed. 38 */ resolveActivity(Intent intent, int flags)39 public static ResolveInfo resolveActivity(Intent intent, int flags) { 40 // On KitKat, calling PackageManager#resolveActivity() causes disk reads and 41 // writes. Temporarily allow this while resolving the intent. 42 try (StrictModeContext ignored = StrictModeContext.allowDiskWrites()) { 43 PackageManager pm = ContextUtils.getApplicationContext().getPackageManager(); 44 return pm.resolveActivity(intent, flags); 45 } catch (RuntimeException e) { 46 handleExpectedExceptionsOrRethrow(e, intent); 47 } 48 return null; 49 } 50 51 /** 52 * Get the list of component name of activities which can resolve |intent|. If the request 53 * fails, an empty list will be returned. 54 * 55 * See {@link PackageManager#queryIntentActivities(Intent, int)} 56 */ queryIntentActivities(Intent intent, int flags)57 public static List<ResolveInfo> queryIntentActivities(Intent intent, int flags) { 58 // Allowlist for Samsung. See http://crbug.com/613977 and https://crbug.com/894160 for more 59 // context. 60 try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) { 61 PackageManager pm = ContextUtils.getApplicationContext().getPackageManager(); 62 return pm.queryIntentActivities(intent, flags); 63 } catch (RuntimeException e) { 64 handleExpectedExceptionsOrRethrow(e, intent); 65 } 66 return Collections.emptyList(); 67 } 68 69 /** 70 * Check if the given Intent can be resolved by any Activities on the system. 71 * 72 * See {@link PackageManagerUtils#queryIntentActivities(Intent, int)} 73 */ canResolveActivity(Intent intent, int flags)74 public static boolean canResolveActivity(Intent intent, int flags) { 75 return !queryIntentActivities(intent, flags).isEmpty(); 76 } 77 78 /** 79 * Check if the given Intent can be resolved by any Activities on the system. 80 * 81 * See {@link PackageManagerUtils#canResolveActivity(Intent, int)} 82 */ canResolveActivity(Intent intent)83 public static boolean canResolveActivity(Intent intent) { 84 return canResolveActivity(intent, 0); 85 } 86 87 /** 88 * @return Intent to query a list of installed home launchers. 89 */ getQueryInstalledHomeLaunchersIntent()90 public static Intent getQueryInstalledHomeLaunchersIntent() { 91 return new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME); 92 } 93 94 /** 95 * @return Default ResolveInfo to handle a VIEW intent for a url. 96 */ resolveDefaultWebBrowserActivity()97 public static ResolveInfo resolveDefaultWebBrowserActivity() { 98 return resolveActivity(BROWSER_INTENT, PackageManager.MATCH_DEFAULT_ONLY); 99 } 100 101 /** 102 * @return The list of names of web browser applications available in the system. A browser 103 * may appear twice if it has multiple intent handlers. 104 */ queryAllWebBrowsersInfo()105 public static List<ResolveInfo> queryAllWebBrowsersInfo() { 106 // Copying these flags from Android source for detecting the list of installed browsers. 107 // Apparently MATCH_ALL doesn't include MATCH_DIRECT_BOOT_*. 108 // See 109 // https://cs.android.com/android/_/android/platform/packages/modules/Permission/+/android12-release:PermissionController/src/com/android/permissioncontroller/role/model/BrowserRoleBehavior.java;drc=86fa7d5dfa43f66b170f93ade4f59b9a770be32f;l=114 110 int flags = 111 PackageManager.MATCH_ALL 112 | PackageManager.MATCH_DIRECT_BOOT_AWARE 113 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE 114 | PackageManager.MATCH_DEFAULT_ONLY; 115 return queryIntentActivities(BROWSER_INTENT, flags); 116 } 117 118 /** 119 * @return The list of names of system launcher applications available in the system. 120 */ queryAllLaunchersInfo()121 public static List<ResolveInfo> queryAllLaunchersInfo() { 122 return queryIntentActivities( 123 getQueryInstalledHomeLaunchersIntent(), PackageManager.MATCH_ALL); 124 } 125 126 // See https://crbug.com/700505 and https://crbug.com/369574. handleExpectedExceptionsOrRethrow(RuntimeException e, Intent intent)127 private static void handleExpectedExceptionsOrRethrow(RuntimeException e, Intent intent) { 128 if (e instanceof NullPointerException 129 || e.getCause() instanceof TransactionTooLargeException) { 130 Log.e(TAG, "Could not resolve Activity for intent " + intent.toString(), e); 131 } else { 132 throw e; 133 } 134 } 135 } 136