1 /* 2 * Copyright (C) 2015 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 package com.android.packageinstaller.permission.model; 17 18 import android.content.Context; 19 import android.content.pm.ApplicationInfo; 20 import android.content.pm.PackageInfo; 21 import android.content.pm.PackageItemInfo; 22 import android.content.pm.PackageManager; 23 import android.content.pm.PackageManager.NameNotFoundException; 24 import android.content.pm.PermissionGroupInfo; 25 import android.content.pm.PermissionInfo; 26 import android.graphics.drawable.Drawable; 27 import android.os.AsyncTask; 28 import android.os.UserHandle; 29 import android.os.UserManager; 30 import android.text.TextUtils; 31 import android.util.ArrayMap; 32 import android.util.Log; 33 import android.util.Pair; 34 import android.util.SparseArray; 35 36 import androidx.annotation.NonNull; 37 import androidx.annotation.Nullable; 38 39 import com.android.packageinstaller.permission.utils.Utils; 40 import com.android.permissioncontroller.R; 41 42 import java.util.ArrayList; 43 import java.util.Collections; 44 import java.util.List; 45 46 public class PermissionApps { 47 private static final String LOG_TAG = "PermissionApps"; 48 49 private final Context mContext; 50 private final String mGroupName; 51 private final String mPackageName; 52 private final PackageManager mPm; 53 private final Callback mCallback; 54 55 private final @Nullable PmCache mPmCache; 56 private final @Nullable AppDataCache mAppDataCache; 57 58 private CharSequence mLabel; 59 private CharSequence mFullLabel; 60 private Drawable mIcon; 61 private @Nullable CharSequence mDescription; 62 private List<PermissionApp> mPermApps; 63 // Map (pkg|uid) -> AppPermission 64 private ArrayMap<String, PermissionApp> mAppLookup; 65 66 private boolean mSkipUi; 67 private boolean mRefreshing; 68 PermissionApps(Context context, String groupName, String packageName)69 public PermissionApps(Context context, String groupName, String packageName) { 70 this(context, groupName, packageName, null, null, null); 71 } 72 PermissionApps(Context context, String groupName, Callback callback)73 public PermissionApps(Context context, String groupName, Callback callback) { 74 this(context, groupName, null, callback, null, null); 75 } 76 PermissionApps(Context context, String groupName, String packageName, Callback callback, @Nullable PmCache pmCache, @Nullable AppDataCache appDataCache)77 public PermissionApps(Context context, String groupName, String packageName, 78 Callback callback, @Nullable PmCache pmCache, @Nullable AppDataCache appDataCache) { 79 mPmCache = pmCache; 80 mAppDataCache = appDataCache; 81 mContext = context; 82 mPm = mContext.getPackageManager(); 83 mGroupName = groupName; 84 mCallback = callback; 85 mPackageName = packageName; 86 loadGroupInfo(); 87 } 88 getGroupName()89 public String getGroupName() { 90 return mGroupName; 91 } 92 loadNowWithoutUi()93 public void loadNowWithoutUi() { 94 mSkipUi = true; 95 createMap(loadPermissionApps()); 96 } 97 98 /** 99 * Start an async refresh and call back the registered call back once done. 100 * 101 * @param getUiInfo If the UI info should be updated 102 */ refresh(boolean getUiInfo)103 public void refresh(boolean getUiInfo) { 104 if (!mRefreshing) { 105 mRefreshing = true; 106 mSkipUi = !getUiInfo; 107 new PermissionAppsLoader().execute(); 108 } 109 } 110 111 /** 112 * Refresh the state and do not return until it finishes. Should not be called while an {@link 113 * #refresh async referesh} is in progress. 114 */ refreshSync(boolean getUiInfo)115 public void refreshSync(boolean getUiInfo) { 116 mSkipUi = !getUiInfo; 117 createMap(loadPermissionApps()); 118 } 119 getGrantedCount()120 public int getGrantedCount() { 121 int count = 0; 122 for (PermissionApp app : mPermApps) { 123 if (!Utils.shouldShowPermission(mContext, app.getPermissionGroup())) { 124 continue; 125 } 126 if (!Utils.isGroupOrBgGroupUserSensitive(app.mAppPermissionGroup)) { 127 // We default to not showing system apps, so hide them from count. 128 continue; 129 } 130 if (app.areRuntimePermissionsGranted()) { 131 count++; 132 } 133 } 134 return count; 135 } 136 getTotalCount()137 public int getTotalCount() { 138 int count = 0; 139 for (PermissionApp app : mPermApps) { 140 if (!Utils.shouldShowPermission(mContext, app.getPermissionGroup())) { 141 continue; 142 } 143 if (!Utils.isGroupOrBgGroupUserSensitive(app.mAppPermissionGroup)) { 144 // We default to not showing system apps, so hide them from count. 145 continue; 146 } 147 count++; 148 } 149 return count; 150 } 151 getApps()152 public List<PermissionApp> getApps() { 153 return mPermApps; 154 } 155 getApp(String key)156 public PermissionApp getApp(String key) { 157 return mAppLookup.get(key); 158 } 159 getLabel()160 public CharSequence getLabel() { 161 return mLabel; 162 } 163 getFullLabel()164 public CharSequence getFullLabel() { 165 return mFullLabel; 166 } 167 getIcon()168 public Drawable getIcon() { 169 return mIcon; 170 } 171 getDescription()172 public CharSequence getDescription() { 173 return mDescription; 174 } 175 getPackageInfos(@onNull UserHandle user)176 private @NonNull List<PackageInfo> getPackageInfos(@NonNull UserHandle user) { 177 List<PackageInfo> apps = (mPmCache != null) ? mPmCache.getPackages( 178 user.getIdentifier()) : null; 179 if (apps != null) { 180 if (mPackageName != null) { 181 final int appCount = apps.size(); 182 for (int i = 0; i < appCount; i++) { 183 final PackageInfo app = apps.get(i); 184 if (mPackageName.equals(app.packageName)) { 185 apps = new ArrayList<>(1); 186 apps.add(app); 187 return apps; 188 } 189 } 190 } 191 return apps; 192 } 193 if (mPackageName == null) { 194 return mPm.getInstalledPackagesAsUser(PackageManager.GET_PERMISSIONS, 195 user.getIdentifier()); 196 } else { 197 try { 198 final PackageInfo packageInfo = mPm.getPackageInfo(mPackageName, 199 PackageManager.GET_PERMISSIONS); 200 apps = new ArrayList<>(1); 201 apps.add(packageInfo); 202 return apps; 203 } catch (NameNotFoundException e) { 204 return Collections.emptyList(); 205 } 206 } 207 } 208 loadPermissionApps()209 private List<PermissionApp> loadPermissionApps() { 210 PackageItemInfo groupInfo = Utils.getGroupInfo(mGroupName, mContext); 211 if (groupInfo == null) { 212 return Collections.emptyList(); 213 } 214 215 List<PermissionInfo> groupPermInfos = Utils.getGroupPermissionInfos(mGroupName, mContext); 216 if (groupPermInfos == null) { 217 return Collections.emptyList(); 218 } 219 List<PermissionInfo> targetPermInfos = new ArrayList<PermissionInfo>(groupPermInfos.size()); 220 for (int i = 0; i < groupPermInfos.size(); i++) { 221 PermissionInfo permInfo = groupPermInfos.get(i); 222 if ((permInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) 223 == PermissionInfo.PROTECTION_DANGEROUS 224 && (permInfo.flags & PermissionInfo.FLAG_INSTALLED) != 0 225 && (permInfo.flags & PermissionInfo.FLAG_REMOVED) == 0) { 226 targetPermInfos.add(permInfo); 227 } 228 } 229 230 PackageManager packageManager = mContext.getPackageManager(); 231 CharSequence groupLabel = groupInfo.loadLabel(packageManager); 232 CharSequence fullGroupLabel = groupInfo.loadSafeLabel(packageManager, 0, 233 TextUtils.SAFE_STRING_FLAG_TRIM | TextUtils.SAFE_STRING_FLAG_FIRST_LINE); 234 235 ArrayList<PermissionApp> permApps = new ArrayList<>(); 236 237 UserManager userManager = mContext.getSystemService(UserManager.class); 238 for (UserHandle user : userManager.getUserProfiles()) { 239 List<PackageInfo> apps = getPackageInfos(user); 240 final int N = apps.size(); 241 for (int i = 0; i < N; i++) { 242 PackageInfo app = apps.get(i); 243 if (app.requestedPermissions == null) { 244 continue; 245 } 246 247 for (int j = 0; j < app.requestedPermissions.length; j++) { 248 String requestedPerm = app.requestedPermissions[j]; 249 250 PermissionInfo requestedPermissionInfo = null; 251 252 for (PermissionInfo groupPermInfo : targetPermInfos) { 253 if (requestedPerm.equals(groupPermInfo.name)) { 254 requestedPermissionInfo = groupPermInfo; 255 break; 256 } 257 } 258 259 if (requestedPermissionInfo == null) { 260 continue; 261 } 262 263 AppPermissionGroup group = AppPermissionGroup.create(mContext, 264 app, groupInfo, groupPermInfos, groupLabel, fullGroupLabel, false); 265 266 if (group == null) { 267 continue; 268 } 269 270 Pair<String, Drawable> appData = null; 271 if (mAppDataCache != null && !mSkipUi) { 272 appData = mAppDataCache.getAppData(user.getIdentifier(), 273 app.applicationInfo); 274 } 275 276 String label; 277 if (mSkipUi) { 278 label = app.packageName; 279 } else if (appData != null) { 280 label = appData.first; 281 } else { 282 label = app.applicationInfo.loadLabel(mPm).toString(); 283 } 284 285 Drawable icon = null; 286 if (!mSkipUi) { 287 if (appData != null) { 288 icon = appData.second; 289 } else { 290 icon = Utils.getBadgedIcon(mContext, app.applicationInfo); 291 } 292 } 293 294 PermissionApp permApp = new PermissionApp(app.packageName, group, label, icon, 295 app.applicationInfo); 296 297 permApps.add(permApp); 298 break; // move to the next app. 299 } 300 } 301 } 302 303 Collections.sort(permApps); 304 305 return permApps; 306 } 307 createMap(List<PermissionApp> result)308 private void createMap(List<PermissionApp> result) { 309 mAppLookup = new ArrayMap<>(); 310 for (PermissionApp app : result) { 311 mAppLookup.put(app.getKey(), app); 312 } 313 mPermApps = result; 314 } 315 loadGroupInfo()316 private void loadGroupInfo() { 317 PackageItemInfo info; 318 try { 319 info = mPm.getPermissionGroupInfo(mGroupName, 0); 320 } catch (PackageManager.NameNotFoundException e) { 321 try { 322 PermissionInfo permInfo = mPm.getPermissionInfo(mGroupName, 0); 323 if ((permInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) 324 != PermissionInfo.PROTECTION_DANGEROUS) { 325 Log.w(LOG_TAG, mGroupName + " is not a runtime permission"); 326 return; 327 } 328 info = permInfo; 329 } catch (NameNotFoundException reallyNotFound) { 330 Log.w(LOG_TAG, "Can't find permission: " + mGroupName, reallyNotFound); 331 return; 332 } 333 } 334 mLabel = info.loadLabel(mPm); 335 mFullLabel = info.loadSafeLabel(mPm, 0, 336 TextUtils.SAFE_STRING_FLAG_TRIM | TextUtils.SAFE_STRING_FLAG_FIRST_LINE); 337 if (info.icon != 0) { 338 mIcon = info.loadUnbadgedIcon(mPm); 339 } else { 340 mIcon = mContext.getDrawable(R.drawable.ic_perm_device_info); 341 } 342 mIcon = Utils.applyTint(mContext, mIcon, android.R.attr.colorControlNormal); 343 if (info instanceof PermissionGroupInfo) { 344 mDescription = ((PermissionGroupInfo) info).loadDescription(mPm); 345 } else if (info instanceof PermissionInfo) { 346 mDescription = ((PermissionInfo) info).loadDescription(mPm); 347 } 348 } 349 350 public static class PermissionApp implements Comparable<PermissionApp> { 351 private final String mPackageName; 352 private final AppPermissionGroup mAppPermissionGroup; 353 private String mLabel; 354 private Drawable mIcon; 355 private final ApplicationInfo mInfo; 356 PermissionApp(String packageName, AppPermissionGroup appPermissionGroup, String label, Drawable icon, ApplicationInfo info)357 public PermissionApp(String packageName, AppPermissionGroup appPermissionGroup, 358 String label, Drawable icon, ApplicationInfo info) { 359 mPackageName = packageName; 360 mAppPermissionGroup = appPermissionGroup; 361 mLabel = label; 362 mIcon = icon; 363 mInfo = info; 364 } 365 getAppInfo()366 public ApplicationInfo getAppInfo() { 367 return mInfo; 368 } 369 getKey()370 public String getKey() { 371 return mPackageName + getUid(); 372 } 373 getLabel()374 public String getLabel() { 375 return mLabel; 376 } 377 getIcon()378 public Drawable getIcon() { 379 return mIcon; 380 } 381 areRuntimePermissionsGranted()382 public boolean areRuntimePermissionsGranted() { 383 return mAppPermissionGroup.areRuntimePermissionsGranted(); 384 } 385 isReviewRequired()386 public boolean isReviewRequired() { 387 return mAppPermissionGroup.isReviewRequired(); 388 } 389 grantRuntimePermissions()390 public void grantRuntimePermissions() { 391 mAppPermissionGroup.grantRuntimePermissions(false); 392 } 393 revokeRuntimePermissions()394 public void revokeRuntimePermissions() { 395 mAppPermissionGroup.revokeRuntimePermissions(false); 396 } 397 isPolicyFixed()398 public boolean isPolicyFixed() { 399 return mAppPermissionGroup.isPolicyFixed(); 400 } 401 isSystemFixed()402 public boolean isSystemFixed() { 403 return mAppPermissionGroup.isSystemFixed(); 404 } 405 hasGrantedByDefaultPermissions()406 public boolean hasGrantedByDefaultPermissions() { 407 return mAppPermissionGroup.hasGrantedByDefaultPermission(); 408 } 409 doesSupportRuntimePermissions()410 public boolean doesSupportRuntimePermissions() { 411 return mAppPermissionGroup.doesSupportRuntimePermissions(); 412 } 413 getPackageName()414 public String getPackageName() { 415 return mPackageName; 416 } 417 getPermissionGroup()418 public AppPermissionGroup getPermissionGroup() { 419 return mAppPermissionGroup; 420 } 421 422 /** 423 * Load this app's label and icon if they were not previously loaded. 424 * 425 * @param appDataCache the cache of already-loaded labels and icons. 426 */ loadLabelAndIcon(@onNull AppDataCache appDataCache)427 public void loadLabelAndIcon(@NonNull AppDataCache appDataCache) { 428 if (mInfo.packageName.equals(mLabel) || mIcon == null) { 429 Pair<String, Drawable> appData = appDataCache.getAppData(getUid(), mInfo); 430 mLabel = appData.first; 431 mIcon = appData.second; 432 } 433 } 434 435 @Override compareTo(PermissionApp another)436 public int compareTo(PermissionApp another) { 437 final int result = mLabel.compareTo(another.mLabel); 438 if (result == 0) { 439 // Unbadged before badged. 440 return getKey().compareTo(another.getKey()); 441 } 442 return result; 443 } 444 getUid()445 public int getUid() { 446 return mAppPermissionGroup.getApp().applicationInfo.uid; 447 } 448 } 449 450 private class PermissionAppsLoader extends AsyncTask<Void, Void, List<PermissionApp>> { 451 452 @Override doInBackground(Void... args)453 protected List<PermissionApp> doInBackground(Void... args) { 454 return loadPermissionApps(); 455 } 456 457 @Override onPostExecute(List<PermissionApp> result)458 protected void onPostExecute(List<PermissionApp> result) { 459 mRefreshing = false; 460 createMap(result); 461 if (mCallback != null) { 462 mCallback.onPermissionsLoaded(PermissionApps.this); 463 } 464 } 465 } 466 467 /** 468 * Class used to reduce the number of calls to the package manager. 469 * This caches app information so it should only be used across parallel PermissionApps 470 * instances, and should not be retained across UI refresh. 471 */ 472 public static class PmCache { 473 private final SparseArray<List<PackageInfo>> mPackageInfoCache = new SparseArray<>(); 474 private final PackageManager mPm; 475 PmCache(PackageManager pm)476 public PmCache(PackageManager pm) { 477 mPm = pm; 478 } 479 getPackages(int userId)480 public synchronized List<PackageInfo> getPackages(int userId) { 481 List<PackageInfo> ret = mPackageInfoCache.get(userId); 482 if (ret == null) { 483 ret = mPm.getInstalledPackagesAsUser(PackageManager.GET_PERMISSIONS, userId); 484 mPackageInfoCache.put(userId, ret); 485 } 486 return ret; 487 } 488 } 489 490 /** 491 * Class used to reduce the number of calls to loading labels and icons. 492 * This caches app information so it should only be used across parallel PermissionApps 493 * instances, and should not be retained across UI refresh. 494 */ 495 public static class AppDataCache { 496 private final @NonNull SparseArray<ArrayMap<String, Pair<String, Drawable>>> mCache = 497 new SparseArray<>(); 498 private final @NonNull PackageManager mPm; 499 private final @NonNull Context mContext; 500 AppDataCache(@onNull PackageManager pm, @NonNull Context context)501 public AppDataCache(@NonNull PackageManager pm, @NonNull Context context) { 502 mPm = pm; 503 mContext = context; 504 } 505 506 /** 507 * Get the label and icon for the given app. 508 * 509 * @param userId the user id. 510 * @param app The app 511 * 512 * @return a pair of the label and icon. 513 */ getAppData(int userId, @NonNull ApplicationInfo app)514 public @NonNull Pair<String, Drawable> getAppData(int userId, 515 @NonNull ApplicationInfo app) { 516 ArrayMap<String, Pair<String, Drawable>> dataForUser = mCache.get(userId); 517 if (dataForUser == null) { 518 dataForUser = new ArrayMap<>(); 519 mCache.put(userId, dataForUser); 520 } 521 Pair<String, Drawable> data = dataForUser.get(app.packageName); 522 if (data == null) { 523 data = Pair.create(app.loadLabel(mPm).toString(), 524 Utils.getBadgedIcon(mContext, app)); 525 dataForUser.put(app.packageName, data); 526 } 527 return data; 528 } 529 } 530 531 public interface Callback { onPermissionsLoaded(PermissionApps permissionApps)532 void onPermissionsLoaded(PermissionApps permissionApps); 533 } 534 535 /** 536 * Class used to asyncronously load apps' labels and icons. 537 */ 538 public static class AppDataLoader extends AsyncTask<PermissionApp, Void, Void> { 539 540 private final Context mContext; 541 private final Runnable mCallback; 542 AppDataLoader(Context context, Runnable callback)543 public AppDataLoader(Context context, Runnable callback) { 544 mContext = context; 545 mCallback = callback; 546 } 547 548 @Override doInBackground(PermissionApp... args)549 protected Void doInBackground(PermissionApp... args) { 550 AppDataCache appDataCache = new AppDataCache(mContext.getPackageManager(), mContext); 551 int numArgs = args.length; 552 for (int i = 0; i < numArgs; i++) { 553 args[i].loadLabelAndIcon(appDataCache); 554 } 555 return null; 556 } 557 558 @Override onPostExecute(Void result)559 protected void onPostExecute(Void result) { 560 mCallback.run(); 561 } 562 } 563 } 564