1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.settingslib.applications; 18 19 import android.app.Application; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.content.pm.ApplicationInfo; 25 import android.content.pm.Flags; 26 import android.content.pm.PackageInfo; 27 import android.content.pm.PackageManager; 28 import android.content.pm.ResolveInfo; 29 import android.graphics.drawable.Drawable; 30 import android.hardware.usb.IUsbManager; 31 import android.net.Uri; 32 import android.os.Environment; 33 import android.os.RemoteException; 34 import android.os.SystemProperties; 35 import android.os.UserHandle; 36 import android.os.UserManager; 37 import android.text.TextUtils; 38 import android.util.Log; 39 40 import com.android.settingslib.R; 41 import com.android.settingslib.Utils; 42 import com.android.settingslib.applications.instantapps.InstantAppDataProvider; 43 import com.android.settingslib.utils.ThreadUtils; 44 45 import java.util.ArrayList; 46 import java.util.List; 47 48 public class AppUtils { 49 private static final String TAG = "AppUtils"; 50 51 /** 52 * This should normally only be set in robolectric tests, to avoid getting a method not found 53 * exception when calling the isInstantApp method of the ApplicationInfo class, because 54 * robolectric does not yet have an implementation of it. 55 */ 56 private static InstantAppDataProvider sInstantAppDataProvider = null; 57 58 private static final Intent sBrowserIntent; 59 60 static { 61 sBrowserIntent = new Intent() 62 .setAction(Intent.ACTION_VIEW) 63 .addCategory(Intent.CATEGORY_BROWSABLE) 64 .setData(Uri.parse("http:")); 65 } 66 getLaunchByDefaultSummary(ApplicationsState.AppEntry appEntry, IUsbManager usbManager, PackageManager pm, Context context)67 public static CharSequence getLaunchByDefaultSummary(ApplicationsState.AppEntry appEntry, 68 IUsbManager usbManager, PackageManager pm, Context context) { 69 String packageName = appEntry.info.packageName; 70 boolean hasPreferred = hasPreferredActivities(pm, packageName) 71 || hasUsbDefaults(usbManager, packageName); 72 int status = pm.getIntentVerificationStatusAsUser(packageName, UserHandle.myUserId()); 73 // consider a visible current link-handling state to be any explicitly designated behavior 74 boolean hasDomainURLsPreference = 75 status != PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; 76 return context.getString(hasPreferred || hasDomainURLsPreference 77 ? R.string.launch_defaults_some 78 : R.string.launch_defaults_none); 79 } 80 hasUsbDefaults(IUsbManager usbManager, String packageName)81 public static boolean hasUsbDefaults(IUsbManager usbManager, String packageName) { 82 try { 83 if (usbManager != null) { 84 return usbManager.hasDefaults(packageName, UserHandle.myUserId()); 85 } 86 } catch (RemoteException e) { 87 Log.e(TAG, "mUsbManager.hasDefaults", e); 88 } 89 return false; 90 } 91 hasPreferredActivities(PackageManager pm, String packageName)92 public static boolean hasPreferredActivities(PackageManager pm, String packageName) { 93 // Get list of preferred activities 94 List<ComponentName> prefActList = new ArrayList<>(); 95 // Intent list cannot be null. so pass empty list 96 List<IntentFilter> intentList = new ArrayList<>(); 97 pm.getPreferredActivities(intentList, prefActList, packageName); 98 Log.d(TAG, "Have " + prefActList.size() + " number of activities in preferred list"); 99 return prefActList.size() > 0; 100 } 101 102 /** 103 * Returns a boolean indicating whether the given package should be considered an instant app 104 */ isInstant(ApplicationInfo info)105 public static boolean isInstant(ApplicationInfo info) { 106 if (sInstantAppDataProvider != null) { 107 if (sInstantAppDataProvider.isInstantApp(info)) { 108 return true; 109 } 110 } else if (info.isInstantApp()) { 111 return true; 112 } 113 114 // For debugging/testing, we support setting the following property to a comma-separated 115 // list of search terms (typically, but not necessarily, full package names) to match 116 // against the package names of the app. 117 String propVal = SystemProperties.get("settingsdebug.instant.packages"); 118 if (propVal != null && !propVal.isEmpty() && info.packageName != null) { 119 String[] searchTerms = propVal.split(","); 120 if (searchTerms != null) { 121 for (String term : searchTerms) { 122 if (info.packageName.contains(term)) { 123 return true; 124 } 125 } 126 } 127 } 128 return false; 129 } 130 131 /** Returns the label for a given package. */ getApplicationLabel( PackageManager packageManager, String packageName)132 public static CharSequence getApplicationLabel( 133 PackageManager packageManager, String packageName) { 134 return com.android.settingslib.utils.applications.AppUtils 135 .getApplicationLabel(packageManager, packageName); 136 } 137 138 /** 139 * Returns a boolean indicating whether the given package is a hidden system module 140 * TODO(b/382016780): to be removed after flag cleanup. 141 */ isHiddenSystemModule(Context context, String packageName)142 public static boolean isHiddenSystemModule(Context context, String packageName) { 143 return ApplicationsState.getInstance((Application) context.getApplicationContext()) 144 .isHiddenModule(packageName); 145 } 146 147 /** 148 * Returns a boolean indicating whether a given package is a system module. 149 */ isSystemModule(Context context, String packageName)150 public static boolean isSystemModule(Context context, String packageName) { 151 return ApplicationsState.getInstance((Application) context.getApplicationContext()) 152 .isSystemModule(packageName); 153 } 154 155 /** 156 * Returns a boolean indicating whether a given package is a mainline module. 157 */ isMainlineModule(PackageManager pm, String packageName)158 public static boolean isMainlineModule(PackageManager pm, String packageName) { 159 // Check if the package is listed among the system modules. 160 try { 161 pm.getModuleInfo(packageName, 0 /* flags */); 162 return true; 163 } catch (PackageManager.NameNotFoundException e) { 164 //pass 165 } 166 167 try { 168 final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */); 169 if (Flags.provideInfoOfApkInApex()) { 170 return pkg.getApexPackageName() != null; 171 } else { 172 // Check if the package is contained in an APEX. There is no public API to properly 173 // check whether a given APK package comes from an APEX registered as module. 174 // Therefore we conservatively assume that any package scanned from an /apex path is 175 // a system package. 176 return pkg.applicationInfo.sourceDir.startsWith( 177 Environment.getApexDirectory().getAbsolutePath()); 178 } 179 } catch (PackageManager.NameNotFoundException e) { 180 return false; 181 } 182 } 183 184 /** 185 * Returns a content description of an app name which distinguishes a personal app from a 186 * work app for accessibility purpose. 187 * If the app is in a work profile, then add a "work" prefix to the app name. 188 */ getAppContentDescription(Context context, String packageName, int userId)189 public static String getAppContentDescription(Context context, String packageName, 190 int userId) { 191 return com.android.settingslib.utils.applications.AppUtils.getAppContentDescription(context, 192 packageName, userId); 193 } 194 195 /** 196 * Returns a boolean indicating whether a given package is a browser app. 197 * 198 * An app is a "browser" if it has an activity resolution that wound up 199 * marked with the 'handleAllWebDataURI' flag. 200 */ isBrowserApp(Context context, String packageName, int userId)201 public static boolean isBrowserApp(Context context, String packageName, int userId) { 202 sBrowserIntent.setPackage(packageName); 203 final List<ResolveInfo> list = context.getPackageManager().queryIntentActivitiesAsUser( 204 sBrowserIntent, PackageManager.MATCH_ALL, userId); 205 for (ResolveInfo info : list) { 206 if (info.activityInfo != null && info.handleAllWebDataURI) { 207 return true; 208 } 209 } 210 return false; 211 } 212 213 /** 214 * Returns a boolean indicating whether a given package is a default browser. 215 * 216 * @param packageName a given package. 217 * @return true if the given package is default browser. 218 */ isDefaultBrowser(Context context, String packageName)219 public static boolean isDefaultBrowser(Context context, String packageName) { 220 final String defaultBrowserPackage = 221 context.getPackageManager().getDefaultBrowserPackageNameAsUser( 222 UserHandle.myUserId()); 223 return TextUtils.equals(packageName, defaultBrowserPackage); 224 } 225 226 /** 227 * Get the app icon by app entry. 228 * 229 * @param context caller's context 230 * @param appEntry AppEntry of ApplicationsState 231 * @return app icon of the app entry 232 */ getIcon(Context context, ApplicationsState.AppEntry appEntry)233 public static Drawable getIcon(Context context, ApplicationsState.AppEntry appEntry) { 234 if (appEntry == null || appEntry.info == null) { 235 return null; 236 } 237 238 final AppIconCacheManager appIconCacheManager = AppIconCacheManager.getInstance(); 239 final String packageName = appEntry.info.packageName; 240 final int uid = appEntry.info.uid; 241 242 Drawable icon = appIconCacheManager.get(packageName, uid); 243 if (icon == null) { 244 if (appEntry.apkFile != null && appEntry.apkFile.exists()) { 245 icon = Utils.getBadgedIcon(context, appEntry.info); 246 appIconCacheManager.put(packageName, uid, icon); 247 } else { 248 setAppEntryMounted(appEntry, /* mounted= */ false); 249 icon = context.getDrawable( 250 com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon); 251 } 252 } else if (!appEntry.mounted && appEntry.apkFile != null && appEntry.apkFile.exists()) { 253 // If the app wasn't mounted but is now mounted, reload its icon. 254 setAppEntryMounted(appEntry, /* mounted= */ true); 255 icon = Utils.getBadgedIcon(context, appEntry.info); 256 appIconCacheManager.put(packageName, uid, icon); 257 } 258 259 return icon; 260 } 261 262 /** 263 * Get the app icon from cache by app entry. 264 * 265 * @param appEntry AppEntry of ApplicationsState 266 * @return app icon of the app entry 267 */ getIconFromCache(ApplicationsState.AppEntry appEntry)268 public static Drawable getIconFromCache(ApplicationsState.AppEntry appEntry) { 269 return appEntry == null || appEntry.info == null ? null 270 : AppIconCacheManager.getInstance().get( 271 appEntry.info.packageName, 272 appEntry.info.uid); 273 } 274 275 /** 276 * Preload the top N icons of app entry list. 277 * 278 * @param context caller's context 279 * @param appEntries AppEntry list of ApplicationsState 280 * @param number the number of Top N icons of the appEntries 281 */ preloadTopIcons(Context context, ArrayList<ApplicationsState.AppEntry> appEntries, int number)282 public static void preloadTopIcons(Context context, 283 ArrayList<ApplicationsState.AppEntry> appEntries, int number) { 284 if (appEntries == null || appEntries.isEmpty() || number <= 0) { 285 return; 286 } 287 288 for (int i = 0; i < Math.min(appEntries.size(), number); i++) { 289 final ApplicationsState.AppEntry entry = appEntries.get(i); 290 var unused = ThreadUtils.getBackgroundExecutor().submit(() -> { 291 getIcon(context, entry); 292 }); 293 } 294 } 295 296 /** 297 * Returns a boolean indicating whether this app is installed or not. 298 * 299 * @param appEntry AppEntry of ApplicationsState. 300 * @return true if the app is in installed state. 301 */ isAppInstalled(ApplicationsState.AppEntry appEntry)302 public static boolean isAppInstalled(ApplicationsState.AppEntry appEntry) { 303 if (appEntry == null || appEntry.info == null) { 304 return false; 305 } 306 return (appEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) != 0; 307 } 308 setAppEntryMounted(ApplicationsState.AppEntry appEntry, boolean mounted)309 private static void setAppEntryMounted(ApplicationsState.AppEntry appEntry, boolean mounted) { 310 if (appEntry.mounted != mounted) { 311 synchronized (appEntry) { 312 appEntry.mounted = mounted; 313 } 314 } 315 } 316 317 /** 318 * Returns clone user profile id if present. Returns -1 if not present. 319 */ getCloneUserId(Context context)320 public static int getCloneUserId(Context context) { 321 UserManager userManager = context.getSystemService(UserManager.class); 322 for (UserHandle userHandle : userManager.getUserProfiles()) { 323 if (userManager.getUserInfo(userHandle.getIdentifier()).isCloneProfile()) { 324 return userHandle.getIdentifier(); 325 } 326 } 327 return -1; 328 } 329 } 330