1 /*
2  * Copyright (C) 2016 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.launcher3.util;
18 
19 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
20 
21 import android.app.AppOpsManager;
22 import android.content.ActivityNotFoundException;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.content.pm.ApplicationInfo;
28 import android.content.pm.LauncherActivityInfo;
29 import android.content.pm.LauncherApps;
30 import android.content.pm.PackageInfo;
31 import android.content.pm.PackageManager;
32 import android.content.pm.PackageManager.NameNotFoundException;
33 import android.content.pm.ResolveInfo;
34 import android.content.res.Resources;
35 import android.graphics.Rect;
36 import android.net.Uri;
37 import android.os.Build;
38 import android.os.Bundle;
39 import android.os.PatternMatcher;
40 import android.os.Process;
41 import android.os.UserHandle;
42 import android.text.TextUtils;
43 import android.util.Log;
44 import android.util.Pair;
45 import android.widget.Toast;
46 
47 import com.android.launcher3.PendingAddItemInfo;
48 import com.android.launcher3.R;
49 import com.android.launcher3.Utilities;
50 import com.android.launcher3.model.data.AppInfo;
51 import com.android.launcher3.model.data.ItemInfo;
52 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
53 import com.android.launcher3.model.data.PromiseAppInfo;
54 import com.android.launcher3.model.data.WorkspaceItemInfo;
55 
56 import java.net.URISyntaxException;
57 import java.util.List;
58 
59 /**
60  * Utility methods using package manager
61  */
62 public class PackageManagerHelper {
63 
64     private static final String TAG = "PackageManagerHelper";
65 
66     private final Context mContext;
67     private final PackageManager mPm;
68     private final LauncherApps mLauncherApps;
69 
PackageManagerHelper(Context context)70     public PackageManagerHelper(Context context) {
71         mContext = context;
72         mPm = context.getPackageManager();
73         mLauncherApps = context.getSystemService(LauncherApps.class);
74     }
75 
76     /**
77      * Returns true if the app can possibly be on the SDCard. This is just a workaround and doesn't
78      * guarantee that the app is on SD card.
79      */
isAppOnSdcard(String packageName, UserHandle user)80     public boolean isAppOnSdcard(String packageName, UserHandle user) {
81         ApplicationInfo info = getApplicationInfo(
82                 packageName, user, PackageManager.MATCH_UNINSTALLED_PACKAGES);
83         return info != null && (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
84     }
85 
86     /**
87      * Returns whether the target app is suspended for a given user as per
88      * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}.
89      */
isAppSuspended(String packageName, UserHandle user)90     public boolean isAppSuspended(String packageName, UserHandle user) {
91         ApplicationInfo info = getApplicationInfo(packageName, user, 0);
92         return info != null && isAppSuspended(info);
93     }
94 
95     /**
96      * Returns the application info for the provided package or null
97      */
getApplicationInfo(String packageName, UserHandle user, int flags)98     public ApplicationInfo getApplicationInfo(String packageName, UserHandle user, int flags) {
99         if (Utilities.ATLEAST_OREO) {
100             try {
101                 ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, flags, user);
102                 return (info.flags & ApplicationInfo.FLAG_INSTALLED) == 0 || !info.enabled
103                         ? null : info;
104             } catch (PackageManager.NameNotFoundException e) {
105                 return null;
106             }
107         } else {
108             final boolean isPrimaryUser = Process.myUserHandle().equals(user);
109             if (!isPrimaryUser && (flags == 0)) {
110                 // We are looking for an installed app on a secondary profile. Prior to O, the only
111                 // entry point for work profiles is through the LauncherActivity.
112                 List<LauncherActivityInfo> activityList =
113                         mLauncherApps.getActivityList(packageName, user);
114                 return activityList.size() > 0 ? activityList.get(0).getApplicationInfo() : null;
115             }
116             try {
117                 ApplicationInfo info = mPm.getApplicationInfo(packageName, flags);
118                 // There is no way to check if the app is installed for managed profile. But for
119                 // primary profile, we can still have this check.
120                 if (isPrimaryUser && ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0)
121                         || !info.enabled) {
122                     return null;
123                 }
124                 return info;
125             } catch (PackageManager.NameNotFoundException e) {
126                 // Package not found
127                 return null;
128             }
129         }
130     }
131 
isSafeMode()132     public boolean isSafeMode() {
133         return mContext.getPackageManager().isSafeMode();
134     }
135 
getAppLaunchIntent(String pkg, UserHandle user)136     public Intent getAppLaunchIntent(String pkg, UserHandle user) {
137         List<LauncherActivityInfo> activities = mLauncherApps.getActivityList(pkg, user);
138         return activities.isEmpty() ? null :
139                 AppInfo.makeLaunchIntent(activities.get(0));
140     }
141 
142     /**
143      * Returns whether an application is suspended as per
144      * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}.
145      */
isAppSuspended(ApplicationInfo info)146     public static boolean isAppSuspended(ApplicationInfo info) {
147         return (info.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
148     }
149 
150     /**
151      * Returns true if {@param srcPackage} has the permission required to start the activity from
152      * {@param intent}. If {@param srcPackage} is null, then the activity should not need
153      * any permissions
154      */
hasPermissionForActivity(Intent intent, String srcPackage)155     public boolean hasPermissionForActivity(Intent intent, String srcPackage) {
156         ResolveInfo target = mPm.resolveActivity(intent, 0);
157         if (target == null) {
158             // Not a valid target
159             return false;
160         }
161         if (TextUtils.isEmpty(target.activityInfo.permission)) {
162             // No permission is needed
163             return true;
164         }
165         if (TextUtils.isEmpty(srcPackage)) {
166             // The activity requires some permission but there is no source.
167             return false;
168         }
169 
170         // Source does not have sufficient permissions.
171         if(mPm.checkPermission(target.activityInfo.permission, srcPackage) !=
172                 PackageManager.PERMISSION_GRANTED) {
173             return false;
174         }
175 
176         // On M and above also check AppOpsManager for compatibility mode permissions.
177         if (TextUtils.isEmpty(AppOpsManager.permissionToOp(target.activityInfo.permission))) {
178             // There is no app-op for this permission, which could have been disabled.
179             return true;
180         }
181 
182         // There is no direct way to check if the app-op is allowed for a particular app. Since
183         // app-op is only enabled for apps running in compatibility mode, simply block such apps.
184 
185         try {
186             return mPm.getApplicationInfo(srcPackage, 0).targetSdkVersion >= Build.VERSION_CODES.M;
187         } catch (NameNotFoundException e) { }
188 
189         return false;
190     }
191 
getMarketIntent(String packageName)192     public Intent getMarketIntent(String packageName) {
193         return new Intent(Intent.ACTION_VIEW)
194                 .setData(new Uri.Builder()
195                         .scheme("market")
196                         .authority("details")
197                         .appendQueryParameter("id", packageName)
198                         .build())
199                 .putExtra(Intent.EXTRA_REFERRER, new Uri.Builder().scheme("android-app")
200                         .authority(mContext.getPackageName()).build());
201     }
202 
203     /**
204      * Creates a new market search intent.
205      */
getMarketSearchIntent(Context context, String query)206     public static Intent getMarketSearchIntent(Context context, String query) {
207         try {
208             Intent intent = Intent.parseUri(context.getString(R.string.market_search_intent), 0);
209             if (!TextUtils.isEmpty(query)) {
210                 intent.setData(
211                         intent.getData().buildUpon().appendQueryParameter("q", query).build());
212             }
213             return intent;
214         } catch (URISyntaxException e) {
215             throw new RuntimeException(e);
216         }
217     }
218 
getStyleWallpapersIntent(Context context)219     public static Intent getStyleWallpapersIntent(Context context) {
220         return new Intent(Intent.ACTION_SET_WALLPAPER).setComponent(
221                 new ComponentName(context.getString(R.string.wallpaper_picker_package),
222                 "com.android.customization.picker.CustomizationPickerActivity"));
223     }
224 
225     /**
226      * Starts the details activity for {@code info}
227      */
startDetailsActivityForInfo(ItemInfo info, Rect sourceBounds, Bundle opts)228     public void startDetailsActivityForInfo(ItemInfo info, Rect sourceBounds, Bundle opts) {
229         if (info instanceof PromiseAppInfo) {
230             PromiseAppInfo promiseAppInfo = (PromiseAppInfo) info;
231             mContext.startActivity(promiseAppInfo.getMarketIntent(mContext));
232             return;
233         }
234         ComponentName componentName = null;
235         if (info instanceof AppInfo) {
236             componentName = ((AppInfo) info).componentName;
237         } else if (info instanceof WorkspaceItemInfo) {
238             componentName = info.getTargetComponent();
239         } else if (info instanceof PendingAddItemInfo) {
240             componentName = ((PendingAddItemInfo) info).componentName;
241         } else if (info instanceof LauncherAppWidgetInfo) {
242             componentName = ((LauncherAppWidgetInfo) info).providerName;
243         }
244         if (componentName != null) {
245             try {
246                 mLauncherApps.startAppDetailsActivity(componentName, info.user, sourceBounds, opts);
247             } catch (SecurityException | ActivityNotFoundException e) {
248                 Toast.makeText(mContext, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
249                 Log.e(TAG, "Unable to launch settings", e);
250             }
251         }
252     }
253 
254     /**
255      * Creates an intent filter to listen for actions with a specific package in the data field.
256      */
getPackageFilter(String pkg, String... actions)257     public static IntentFilter getPackageFilter(String pkg, String... actions) {
258         IntentFilter packageFilter = new IntentFilter();
259         for (String action : actions) {
260             packageFilter.addAction(action);
261         }
262         packageFilter.addDataScheme("package");
263         packageFilter.addDataSchemeSpecificPart(pkg, PatternMatcher.PATTERN_LITERAL);
264         return packageFilter;
265     }
266 
isSystemApp(Context context, Intent intent)267     public static boolean isSystemApp(Context context, Intent intent) {
268         PackageManager pm = context.getPackageManager();
269         ComponentName cn = intent.getComponent();
270         String packageName = null;
271         if (cn == null) {
272             ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
273             if ((info != null) && (info.activityInfo != null)) {
274                 packageName = info.activityInfo.packageName;
275             }
276         } else {
277             packageName = cn.getPackageName();
278         }
279         if (packageName == null) {
280             packageName = intent.getPackage();
281         }
282         if (packageName != null) {
283             try {
284                 PackageInfo info = pm.getPackageInfo(packageName, 0);
285                 return (info != null) && (info.applicationInfo != null) &&
286                         ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
287             } catch (NameNotFoundException e) {
288                 return false;
289             }
290         } else {
291             return false;
292         }
293     }
294 
295     /**
296      * Finds a system apk which had a broadcast receiver listening to a particular action.
297      * @param action intent action used to find the apk
298      * @return a pair of apk package name and the resources.
299      */
findSystemApk(String action, PackageManager pm)300     public static Pair<String, Resources> findSystemApk(String action, PackageManager pm) {
301         final Intent intent = new Intent(action);
302         for (ResolveInfo info : pm.queryBroadcastReceivers(intent, MATCH_SYSTEM_ONLY)) {
303             final String packageName = info.activityInfo.packageName;
304             try {
305                 final Resources res = pm.getResourcesForApplication(packageName);
306                 return Pair.create(packageName, res);
307             } catch (NameNotFoundException e) {
308                 Log.w(TAG, "Failed to find resources for " + packageName);
309             }
310         }
311         return null;
312     }
313 
314     /**
315      * Returns true if the intent is a valid launch intent for a launcher activity of an app.
316      * This is used to identify shortcuts which are different from the ones exposed by the
317      * applications' manifest file.
318      *
319      * @param launchIntent The intent that will be launched when the shortcut is clicked.
320      */
isLauncherAppTarget(Intent launchIntent)321     public static boolean isLauncherAppTarget(Intent launchIntent) {
322         if (launchIntent != null
323                 && Intent.ACTION_MAIN.equals(launchIntent.getAction())
324                 && launchIntent.getComponent() != null
325                 && launchIntent.getCategories() != null
326                 && launchIntent.getCategories().size() == 1
327                 && launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER)
328                 && TextUtils.isEmpty(launchIntent.getDataString())) {
329             // An app target can either have no extra or have ItemInfo.EXTRA_PROFILE.
330             Bundle extras = launchIntent.getExtras();
331             return extras == null || extras.keySet().isEmpty();
332         }
333         return false;
334     }
335 
336     /**
337      * Returns true if Launcher has the permission to access shortcuts.
338      * @see LauncherApps#hasShortcutHostPermission()
339      */
hasShortcutsPermission(Context context)340     public static boolean hasShortcutsPermission(Context context) {
341         try {
342             return context.getSystemService(LauncherApps.class).hasShortcutHostPermission();
343         } catch (SecurityException | IllegalStateException e) {
344             Log.e(TAG, "Failed to make shortcut manager call", e);
345         }
346         return false;
347     }
348 }
349