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