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