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