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