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 */ isHiddenSystemModule(Context context, String packageName)141 public static boolean isHiddenSystemModule(Context context, String packageName) { 142 return ApplicationsState.getInstance((Application) context.getApplicationContext()) 143 .isHiddenModule(packageName); 144 } 145 146 /** 147 * Returns a boolean indicating whether a given package is a system module. 148 */ isSystemModule(Context context, String packageName)149 public static boolean isSystemModule(Context context, String packageName) { 150 return ApplicationsState.getInstance((Application) context.getApplicationContext()) 151 .isSystemModule(packageName); 152 } 153 154 /** 155 * Returns a boolean indicating whether a given package is a mainline module. 156 */ isMainlineModule(PackageManager pm, String packageName)157 public static boolean isMainlineModule(PackageManager pm, String packageName) { 158 // Check if the package is listed among the system modules. 159 try { 160 pm.getModuleInfo(packageName, 0 /* flags */); 161 return true; 162 } catch (PackageManager.NameNotFoundException e) { 163 //pass 164 } 165 166 try { 167 final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */); 168 if (Flags.provideInfoOfApkInApex()) { 169 return pkg.getApexPackageName() != null; 170 } else { 171 // Check if the package is contained in an APEX. There is no public API to properly 172 // check whether a given APK package comes from an APEX registered as module. 173 // Therefore we conservatively assume that any package scanned from an /apex path is 174 // a system package. 175 return pkg.applicationInfo.sourceDir.startsWith( 176 Environment.getApexDirectory().getAbsolutePath()); 177 } 178 } catch (PackageManager.NameNotFoundException e) { 179 return false; 180 } 181 } 182 183 /** 184 * Returns a content description of an app name which distinguishes a personal app from a 185 * work app for accessibility purpose. 186 * If the app is in a work profile, then add a "work" prefix to the app name. 187 */ getAppContentDescription(Context context, String packageName, int userId)188 public static String getAppContentDescription(Context context, String packageName, 189 int userId) { 190 return com.android.settingslib.utils.applications.AppUtils.getAppContentDescription(context, 191 packageName, userId); 192 } 193 194 /** 195 * Returns a boolean indicating whether a given package is a browser app. 196 * 197 * An app is a "browser" if it has an activity resolution that wound up 198 * marked with the 'handleAllWebDataURI' flag. 199 */ isBrowserApp(Context context, String packageName, int userId)200 public static boolean isBrowserApp(Context context, String packageName, int userId) { 201 sBrowserIntent.setPackage(packageName); 202 final List<ResolveInfo> list = context.getPackageManager().queryIntentActivitiesAsUser( 203 sBrowserIntent, PackageManager.MATCH_ALL, userId); 204 for (ResolveInfo info : list) { 205 if (info.activityInfo != null && info.handleAllWebDataURI) { 206 return true; 207 } 208 } 209 return false; 210 } 211 212 /** 213 * Returns a boolean indicating whether a given package is a default browser. 214 * 215 * @param packageName a given package. 216 * @return true if the given package is default browser. 217 */ isDefaultBrowser(Context context, String packageName)218 public static boolean isDefaultBrowser(Context context, String packageName) { 219 final String defaultBrowserPackage = 220 context.getPackageManager().getDefaultBrowserPackageNameAsUser( 221 UserHandle.myUserId()); 222 return TextUtils.equals(packageName, defaultBrowserPackage); 223 } 224 225 /** 226 * Get the app icon by app entry. 227 * 228 * @param context caller's context 229 * @param appEntry AppEntry of ApplicationsState 230 * @return app icon of the app entry 231 */ getIcon(Context context, ApplicationsState.AppEntry appEntry)232 public static Drawable getIcon(Context context, ApplicationsState.AppEntry appEntry) { 233 if (appEntry == null || appEntry.info == null) { 234 return null; 235 } 236 237 final AppIconCacheManager appIconCacheManager = AppIconCacheManager.getInstance(); 238 final String packageName = appEntry.info.packageName; 239 final int uid = appEntry.info.uid; 240 241 Drawable icon = appIconCacheManager.get(packageName, uid); 242 if (icon == null) { 243 if (appEntry.apkFile != null && appEntry.apkFile.exists()) { 244 icon = Utils.getBadgedIcon(context, appEntry.info); 245 appIconCacheManager.put(packageName, uid, icon); 246 } else { 247 setAppEntryMounted(appEntry, /* mounted= */ false); 248 icon = context.getDrawable( 249 com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon); 250 } 251 } else if (!appEntry.mounted && appEntry.apkFile != null && appEntry.apkFile.exists()) { 252 // If the app wasn't mounted but is now mounted, reload its icon. 253 setAppEntryMounted(appEntry, /* mounted= */ true); 254 icon = Utils.getBadgedIcon(context, appEntry.info); 255 appIconCacheManager.put(packageName, uid, icon); 256 } 257 258 return icon; 259 } 260 261 /** 262 * Get the app icon from cache by app entry. 263 * 264 * @param appEntry AppEntry of ApplicationsState 265 * @return app icon of the app entry 266 */ getIconFromCache(ApplicationsState.AppEntry appEntry)267 public static Drawable getIconFromCache(ApplicationsState.AppEntry appEntry) { 268 return appEntry == null || appEntry.info == null ? null 269 : AppIconCacheManager.getInstance().get( 270 appEntry.info.packageName, 271 appEntry.info.uid); 272 } 273 274 /** 275 * Preload the top N icons of app entry list. 276 * 277 * @param context caller's context 278 * @param appEntries AppEntry list of ApplicationsState 279 * @param number the number of Top N icons of the appEntries 280 */ preloadTopIcons(Context context, ArrayList<ApplicationsState.AppEntry> appEntries, int number)281 public static void preloadTopIcons(Context context, 282 ArrayList<ApplicationsState.AppEntry> appEntries, int number) { 283 if (appEntries == null || appEntries.isEmpty() || number <= 0) { 284 return; 285 } 286 287 for (int i = 0; i < Math.min(appEntries.size(), number); i++) { 288 final ApplicationsState.AppEntry entry = appEntries.get(i); 289 var unused = ThreadUtils.getBackgroundExecutor().submit(() -> { 290 getIcon(context, entry); 291 }); 292 } 293 } 294 295 /** 296 * Returns a boolean indicating whether this app is installed or not. 297 * 298 * @param appEntry AppEntry of ApplicationsState. 299 * @return true if the app is in installed state. 300 */ isAppInstalled(ApplicationsState.AppEntry appEntry)301 public static boolean isAppInstalled(ApplicationsState.AppEntry appEntry) { 302 if (appEntry == null || appEntry.info == null) { 303 return false; 304 } 305 return (appEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) != 0; 306 } 307 setAppEntryMounted(ApplicationsState.AppEntry appEntry, boolean mounted)308 private static void setAppEntryMounted(ApplicationsState.AppEntry appEntry, boolean mounted) { 309 if (appEntry.mounted != mounted) { 310 synchronized (appEntry) { 311 appEntry.mounted = mounted; 312 } 313 } 314 } 315 316 /** 317 * Returns clone user profile id if present. Returns -1 if not present. 318 */ getCloneUserId(Context context)319 public static int getCloneUserId(Context context) { 320 UserManager userManager = context.getSystemService(UserManager.class); 321 for (UserHandle userHandle : userManager.getUserProfiles()) { 322 if (userManager.getUserInfo(userHandle.getIdentifier()).isCloneProfile()) { 323 return userHandle.getIdentifier(); 324 } 325 } 326 return -1; 327 } 328 } 329