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