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