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.content.ActivityNotFoundException; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.pm.ApplicationInfo; 24 import android.content.pm.LauncherActivityInfo; 25 import android.content.pm.LauncherApps; 26 import android.content.pm.PackageInfo; 27 import android.content.pm.PackageManager; 28 import android.content.pm.PackageManager.NameNotFoundException; 29 import android.content.pm.ResolveInfo; 30 import android.graphics.Rect; 31 import android.net.Uri; 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 androidx.annotation.NonNull; 39 import androidx.annotation.Nullable; 40 41 import com.android.launcher3.PendingAddItemInfo; 42 import com.android.launcher3.R; 43 import com.android.launcher3.Utilities; 44 import com.android.launcher3.model.data.AppInfo; 45 import com.android.launcher3.model.data.ItemInfo; 46 import com.android.launcher3.model.data.ItemInfoWithIcon; 47 import com.android.launcher3.model.data.LauncherAppWidgetInfo; 48 import com.android.launcher3.model.data.WorkspaceItemInfo; 49 50 import java.net.URISyntaxException; 51 import java.util.List; 52 import java.util.Objects; 53 54 /** 55 * Utility methods using package manager 56 */ 57 public class PackageManagerHelper { 58 59 private static final String TAG = "PackageManagerHelper"; 60 61 @NonNull 62 private final Context mContext; 63 64 @NonNull 65 private final PackageManager mPm; 66 67 @NonNull 68 private final LauncherApps mLauncherApps; 69 PackageManagerHelper(@onNull final Context context)70 public PackageManagerHelper(@NonNull final Context context) { 71 mContext = context; 72 mPm = context.getPackageManager(); 73 mLauncherApps = Objects.requireNonNull(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(@onNull final String packageName, @NonNull final UserHandle user)80 public boolean isAppOnSdcard(@NonNull final String packageName, 81 @NonNull final UserHandle user) { 82 final ApplicationInfo info = getApplicationInfo( 83 packageName, user, PackageManager.MATCH_UNINSTALLED_PACKAGES); 84 return info != null && (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0; 85 } 86 87 /** 88 * Returns whether the target app is suspended for a given user as per 89 * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}. 90 */ isAppSuspended(@onNull final String packageName, @NonNull final UserHandle user)91 public boolean isAppSuspended(@NonNull final String packageName, 92 @NonNull final UserHandle user) { 93 final ApplicationInfo info = getApplicationInfo(packageName, user, 0); 94 return info != null && isAppSuspended(info); 95 } 96 97 /** 98 * Returns whether the target app is installed for a given user 99 */ isAppInstalled(@onNull final String packageName, @NonNull final UserHandle user)100 public boolean isAppInstalled(@NonNull final String packageName, 101 @NonNull final UserHandle user) { 102 final ApplicationInfo info = getApplicationInfo(packageName, user, 0); 103 return info != null; 104 } 105 106 /** 107 * Returns the application info for the provided package or null 108 */ 109 @Nullable getApplicationInfo(@onNull final String packageName, @NonNull final UserHandle user, final int flags)110 public ApplicationInfo getApplicationInfo(@NonNull final String packageName, 111 @NonNull final UserHandle user, final int flags) { 112 try { 113 ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, flags, user); 114 return (info.flags & ApplicationInfo.FLAG_INSTALLED) == 0 || !info.enabled 115 ? null : info; 116 } catch (PackageManager.NameNotFoundException e) { 117 return null; 118 } 119 } 120 isSafeMode()121 public boolean isSafeMode() { 122 return mPm.isSafeMode(); 123 } 124 125 @Nullable getAppLaunchIntent(@ullable final String pkg, @NonNull final UserHandle user)126 public Intent getAppLaunchIntent(@Nullable final String pkg, @NonNull final UserHandle user) { 127 List<LauncherActivityInfo> activities = mLauncherApps.getActivityList(pkg, user); 128 return activities.isEmpty() ? null : 129 AppInfo.makeLaunchIntent(activities.get(0)); 130 } 131 132 /** 133 * Returns whether an application is suspended as per 134 * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}. 135 */ isAppSuspended(ApplicationInfo info)136 public static boolean isAppSuspended(ApplicationInfo info) { 137 return (info.flags & ApplicationInfo.FLAG_SUSPENDED) != 0; 138 } 139 getMarketIntent(String packageName)140 public Intent getMarketIntent(String packageName) { 141 return new Intent(Intent.ACTION_VIEW) 142 .setData(new Uri.Builder() 143 .scheme("market") 144 .authority("details") 145 .appendQueryParameter("id", packageName) 146 .build()) 147 .putExtra(Intent.EXTRA_REFERRER, new Uri.Builder().scheme("android-app") 148 .authority(mContext.getPackageName()).build()); 149 } 150 151 /** 152 * Creates a new market search intent. 153 */ getMarketSearchIntent(Context context, String query)154 public static Intent getMarketSearchIntent(Context context, String query) { 155 try { 156 Intent intent = Intent.parseUri(context.getString(R.string.market_search_intent), 0); 157 if (!TextUtils.isEmpty(query)) { 158 intent.setData( 159 intent.getData().buildUpon().appendQueryParameter("q", query).build()); 160 } 161 return intent; 162 } catch (URISyntaxException e) { 163 throw new RuntimeException(e); 164 } 165 } 166 167 /** 168 * Starts the details activity for {@code info} 169 */ startDetailsActivityForInfo(ItemInfo info, Rect sourceBounds, Bundle opts)170 public void startDetailsActivityForInfo(ItemInfo info, Rect sourceBounds, Bundle opts) { 171 if (info instanceof ItemInfoWithIcon 172 && (((ItemInfoWithIcon) info).runtimeStatusFlags 173 & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) { 174 ItemInfoWithIcon appInfo = (ItemInfoWithIcon) info; 175 mContext.startActivity(new PackageManagerHelper(mContext) 176 .getMarketIntent(appInfo.getTargetComponent().getPackageName())); 177 return; 178 } 179 ComponentName componentName = null; 180 if (info instanceof AppInfo) { 181 componentName = ((AppInfo) info).componentName; 182 } else if (info instanceof WorkspaceItemInfo) { 183 componentName = info.getTargetComponent(); 184 } else if (info instanceof PendingAddItemInfo) { 185 componentName = ((PendingAddItemInfo) info).componentName; 186 } else if (info instanceof LauncherAppWidgetInfo) { 187 componentName = ((LauncherAppWidgetInfo) info).providerName; 188 } 189 if (componentName != null) { 190 try { 191 mLauncherApps.startAppDetailsActivity(componentName, info.user, sourceBounds, opts); 192 } catch (SecurityException | ActivityNotFoundException e) { 193 Toast.makeText(mContext, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); 194 Log.e(TAG, "Unable to launch settings", e); 195 } 196 } 197 } 198 isSystemApp(@onNull final Context context, @NonNull final Intent intent)199 public static boolean isSystemApp(@NonNull final Context context, 200 @NonNull final Intent intent) { 201 PackageManager pm = context.getPackageManager(); 202 ComponentName cn = intent.getComponent(); 203 String packageName = null; 204 if (cn == null) { 205 ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); 206 if ((info != null) && (info.activityInfo != null)) { 207 packageName = info.activityInfo.packageName; 208 } 209 } else { 210 packageName = cn.getPackageName(); 211 } 212 if (packageName == null) { 213 packageName = intent.getPackage(); 214 } 215 if (packageName != null) { 216 try { 217 PackageInfo info = pm.getPackageInfo(packageName, 0); 218 return (info != null) && (info.applicationInfo != null) && 219 ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0); 220 } catch (NameNotFoundException e) { 221 return false; 222 } 223 } else { 224 return false; 225 } 226 } 227 228 /** 229 * Returns true if the intent is a valid launch intent for a launcher activity of an app. 230 * This is used to identify shortcuts which are different from the ones exposed by the 231 * applications' manifest file. 232 * 233 * @param launchIntent The intent that will be launched when the shortcut is clicked. 234 */ isLauncherAppTarget(Intent launchIntent)235 public static boolean isLauncherAppTarget(Intent launchIntent) { 236 if (launchIntent != null 237 && Intent.ACTION_MAIN.equals(launchIntent.getAction()) 238 && launchIntent.getComponent() != null 239 && launchIntent.getCategories() != null 240 && launchIntent.getCategories().size() == 1 241 && launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER) 242 && TextUtils.isEmpty(launchIntent.getDataString())) { 243 // An app target can either have no extra or have ItemInfo.EXTRA_PROFILE. 244 Bundle extras = launchIntent.getExtras(); 245 return extras == null || extras.keySet().isEmpty(); 246 } 247 return false; 248 } 249 250 /** 251 * Returns true if Launcher has the permission to access shortcuts. 252 * @see LauncherApps#hasShortcutHostPermission() 253 */ hasShortcutsPermission(Context context)254 public static boolean hasShortcutsPermission(Context context) { 255 try { 256 return context.getSystemService(LauncherApps.class).hasShortcutHostPermission(); 257 } catch (SecurityException | IllegalStateException e) { 258 Log.e(TAG, "Failed to make shortcut manager call", e); 259 } 260 return false; 261 } 262 263 /** Returns the incremental download progress for the given shortcut's app. */ getLoadingProgress(LauncherActivityInfo info)264 public static int getLoadingProgress(LauncherActivityInfo info) { 265 if (Utilities.ATLEAST_S) { 266 return (int) (100 * info.getLoadingProgress()); 267 } 268 return 100; 269 } 270 } 271