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.PackageManager; 27 import android.content.pm.PackageManager.NameNotFoundException; 28 import android.content.pm.ResolveInfo; 29 import android.graphics.Rect; 30 import android.net.Uri; 31 import android.os.Build; 32 import android.os.Bundle; 33 import android.os.UserHandle; 34 import android.text.TextUtils; 35 import android.util.Log; 36 import android.widget.Toast; 37 38 import com.android.launcher3.AppInfo; 39 import com.android.launcher3.ItemInfo; 40 import com.android.launcher3.Launcher; 41 import com.android.launcher3.LauncherAppWidgetInfo; 42 import com.android.launcher3.PendingAddItemInfo; 43 import com.android.launcher3.PromiseAppInfo; 44 import com.android.launcher3.R; 45 import com.android.launcher3.ShortcutInfo; 46 import com.android.launcher3.Utilities; 47 import com.android.launcher3.compat.LauncherAppsCompat; 48 49 import java.net.URISyntaxException; 50 import java.util.List; 51 52 /** 53 * Utility methods using package manager 54 */ 55 public class PackageManagerHelper { 56 57 private static final String TAG = "PackageManagerHelper"; 58 59 private final Context mContext; 60 private final PackageManager mPm; 61 private final LauncherAppsCompat mLauncherApps; 62 63 public PackageManagerHelper(Context context) { 64 mContext = context; 65 mPm = context.getPackageManager(); 66 mLauncherApps = LauncherAppsCompat.getInstance(context); 67 } 68 69 /** 70 * Returns true if the app can possibly be on the SDCard. This is just a workaround and doesn't 71 * guarantee that the app is on SD card. 72 */ 73 public boolean isAppOnSdcard(String packageName, UserHandle user) { 74 ApplicationInfo info = mLauncherApps.getApplicationInfo( 75 packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, user); 76 return info != null && (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0; 77 } 78 79 /** 80 * Returns whether the target app is suspended for a given user as per 81 * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}. 82 */ 83 public boolean isAppSuspended(String packageName, UserHandle user) { 84 ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, 0, user); 85 return info != null && isAppSuspended(info); 86 } 87 88 public boolean isSafeMode() { 89 return mContext.getPackageManager().isSafeMode(); 90 } 91 92 public Intent getAppLaunchIntent(String pkg, UserHandle user) { 93 List<LauncherActivityInfo> activities = mLauncherApps.getActivityList(pkg, user); 94 return activities.isEmpty() ? null : 95 AppInfo.makeLaunchIntent(activities.get(0)); 96 } 97 98 /** 99 * Returns whether an application is suspended as per 100 * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}. 101 */ 102 public static boolean isAppSuspended(ApplicationInfo info) { 103 // The value of FLAG_SUSPENDED was reused by a hidden constant 104 // ApplicationInfo.FLAG_PRIVILEGED prior to N, so only check for suspended flag on N 105 // or later. 106 if (Utilities.ATLEAST_NOUGAT) { 107 return (info.flags & ApplicationInfo.FLAG_SUSPENDED) != 0; 108 } else { 109 return false; 110 } 111 } 112 113 /** 114 * Returns true if {@param srcPackage} has the permission required to start the activity from 115 * {@param intent}. If {@param srcPackage} is null, then the activity should not need 116 * any permissions 117 */ 118 public boolean hasPermissionForActivity(Intent intent, String srcPackage) { 119 ResolveInfo target = mPm.resolveActivity(intent, 0); 120 if (target == null) { 121 // Not a valid target 122 return false; 123 } 124 if (TextUtils.isEmpty(target.activityInfo.permission)) { 125 // No permission is needed 126 return true; 127 } 128 if (TextUtils.isEmpty(srcPackage)) { 129 // The activity requires some permission but there is no source. 130 return false; 131 } 132 133 // Source does not have sufficient permissions. 134 if(mPm.checkPermission(target.activityInfo.permission, srcPackage) != 135 PackageManager.PERMISSION_GRANTED) { 136 return false; 137 } 138 139 if (!Utilities.ATLEAST_MARSHMALLOW) { 140 // These checks are sufficient for below M devices. 141 return true; 142 } 143 144 // On M and above also check AppOpsManager for compatibility mode permissions. 145 if (TextUtils.isEmpty(AppOpsManager.permissionToOp(target.activityInfo.permission))) { 146 // There is no app-op for this permission, which could have been disabled. 147 return true; 148 } 149 150 // There is no direct way to check if the app-op is allowed for a particular app. Since 151 // app-op is only enabled for apps running in compatibility mode, simply block such apps. 152 153 try { 154 return mPm.getApplicationInfo(srcPackage, 0).targetSdkVersion >= Build.VERSION_CODES.M; 155 } catch (NameNotFoundException e) { } 156 157 return false; 158 } 159 160 public Intent getMarketIntent(String packageName) { 161 return new Intent(Intent.ACTION_VIEW) 162 .setData(new Uri.Builder() 163 .scheme("market") 164 .authority("details") 165 .appendQueryParameter("id", packageName) 166 .build()) 167 .putExtra(Intent.EXTRA_REFERRER, new Uri.Builder().scheme("android-app") 168 .authority(mContext.getPackageName()).build()); 169 } 170 171 /** 172 * Creates a new market search intent. 173 */ 174 public static Intent getMarketSearchIntent(Context context, String query) { 175 try { 176 Intent intent = Intent.parseUri(context.getString(R.string.market_search_intent), 0); 177 if (!TextUtils.isEmpty(query)) { 178 intent.setData( 179 intent.getData().buildUpon().appendQueryParameter("q", query).build()); 180 } 181 return intent; 182 } catch (URISyntaxException e) { 183 throw new RuntimeException(e); 184 } 185 } 186 187 188 /** 189 * Starts the details activity for {@code info} 190 */ 191 public void startDetailsActivityForInfo(ItemInfo info, Rect sourceBounds, Bundle opts) { 192 if (info instanceof PromiseAppInfo) { 193 PromiseAppInfo promiseAppInfo = (PromiseAppInfo) info; 194 mContext.startActivity(promiseAppInfo.getMarketIntent(mContext)); 195 return; 196 } 197 ComponentName componentName = null; 198 if (info instanceof AppInfo) { 199 componentName = ((AppInfo) info).componentName; 200 } else if (info instanceof ShortcutInfo) { 201 componentName = info.getTargetComponent(); 202 } else if (info instanceof PendingAddItemInfo) { 203 componentName = ((PendingAddItemInfo) info).componentName; 204 } else if (info instanceof LauncherAppWidgetInfo) { 205 componentName = ((LauncherAppWidgetInfo) info).providerName; 206 } 207 if (componentName != null) { 208 try { 209 mLauncherApps.showAppDetailsForProfile( 210 componentName, info.user, sourceBounds, opts); 211 } catch (SecurityException | ActivityNotFoundException e) { 212 Toast.makeText(mContext, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); 213 Log.e(TAG, "Unable to launch settings", e); 214 } 215 } 216 } 217 } 218