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.PermissionInfo; 25 import android.graphics.drawable.Drawable; 26 import android.os.AsyncTask; 27 import android.os.UserHandle; 28 import android.os.UserManager; 29 import android.util.ArrayMap; 30 import android.util.ArraySet; 31 import android.util.Log; 32 import android.util.SparseArray; 33 34 import com.android.packageinstaller.R; 35 import com.android.packageinstaller.permission.utils.Utils; 36 37 import java.util.ArrayList; 38 import java.util.Collections; 39 import java.util.List; 40 41 public class PermissionApps { 42 private static final String LOG_TAG = "PermissionApps"; 43 44 private final Context mContext; 45 private final String mGroupName; 46 private final PackageManager mPm; 47 private final Callback mCallback; 48 49 private final PmCache mCache; 50 51 private CharSequence mLabel; 52 private Drawable mIcon; 53 private List<PermissionApp> mPermApps; 54 // Map (pkg|uid) -> AppPermission 55 private ArrayMap<String, PermissionApp> mAppLookup; 56 57 private boolean mSkipUi; 58 private boolean mRefreshing; 59 PermissionApps(Context context, String groupName, Callback callback)60 public PermissionApps(Context context, String groupName, Callback callback) { 61 this(context, groupName, callback, null); 62 } 63 PermissionApps(Context context, String groupName, Callback callback, PmCache cache)64 public PermissionApps(Context context, String groupName, Callback callback, PmCache cache) { 65 mCache = cache; 66 mContext = context; 67 mPm = mContext.getPackageManager(); 68 mGroupName = groupName; 69 mCallback = callback; 70 loadGroupInfo(); 71 } 72 getGroupName()73 public String getGroupName() { 74 return mGroupName; 75 } 76 loadNowWithoutUi()77 public void loadNowWithoutUi() { 78 mSkipUi = true; 79 createMap(loadPermissionApps()); 80 } 81 82 /** 83 * Start an async refresh and call back the registered call back once done. 84 * 85 * @param getUiInfo If the UI info should be updated 86 */ refresh(boolean getUiInfo)87 public void refresh(boolean getUiInfo) { 88 if (mCallback == null) { 89 throw new IllegalStateException("callback needs to be set"); 90 } 91 92 if (!mRefreshing) { 93 mRefreshing = true; 94 mSkipUi = !getUiInfo; 95 new PermissionAppsLoader().execute(); 96 } 97 } 98 99 /** 100 * Refresh the state and do not return until it finishes. Should not be called while an {@link 101 * #refresh async referesh} is in progress. 102 */ refreshSync()103 public void refreshSync() { 104 mSkipUi = true; 105 createMap(loadPermissionApps()); 106 } 107 getGrantedCount(ArraySet<String> launcherPkgs)108 public int getGrantedCount(ArraySet<String> launcherPkgs) { 109 int count = 0; 110 for (PermissionApp app : mPermApps) { 111 if (!Utils.shouldShowPermission(app)) { 112 continue; 113 } 114 if (Utils.isSystem(app, launcherPkgs)) { 115 // We default to not showing system apps, so hide them from count. 116 continue; 117 } 118 if (app.areRuntimePermissionsGranted()) { 119 count++; 120 } 121 } 122 return count; 123 } 124 getTotalCount(ArraySet<String> launcherPkgs)125 public int getTotalCount(ArraySet<String> launcherPkgs) { 126 int count = 0; 127 for (PermissionApp app : mPermApps) { 128 if (!Utils.shouldShowPermission(app)) { 129 continue; 130 } 131 if (Utils.isSystem(app, launcherPkgs)) { 132 // We default to not showing system apps, so hide them from count. 133 continue; 134 } 135 count++; 136 } 137 return count; 138 } 139 getApps()140 public List<PermissionApp> getApps() { 141 return mPermApps; 142 } 143 getApp(String key)144 public PermissionApp getApp(String key) { 145 return mAppLookup.get(key); 146 } 147 getLabel()148 public CharSequence getLabel() { 149 return mLabel; 150 } 151 getIcon()152 public Drawable getIcon() { 153 return mIcon; 154 } 155 loadPermissionApps()156 private List<PermissionApp> loadPermissionApps() { 157 PackageItemInfo groupInfo = getGroupInfo(mGroupName); 158 if (groupInfo == null) { 159 return Collections.emptyList(); 160 } 161 162 List<PermissionInfo> groupPermInfos = getGroupPermissionInfos(mGroupName); 163 if (groupPermInfos == null) { 164 return Collections.emptyList(); 165 } 166 167 ArrayList<PermissionApp> permApps = new ArrayList<>(); 168 169 UserManager userManager = mContext.getSystemService(UserManager.class); 170 for (UserHandle user : userManager.getUserProfiles()) { 171 List<PackageInfo> apps = mCache != null ? mCache.getPackages(user.getIdentifier()) 172 : mPm.getInstalledPackagesAsUser(PackageManager.GET_PERMISSIONS, 173 user.getIdentifier()); 174 175 final int N = apps.size(); 176 for (int i = 0; i < N; i++) { 177 PackageInfo app = apps.get(i); 178 if (app.requestedPermissions == null) { 179 continue; 180 } 181 182 for (int j = 0; j < app.requestedPermissions.length; j++) { 183 String requestedPerm = app.requestedPermissions[j]; 184 185 PermissionInfo requestedPermissionInfo = null; 186 187 for (PermissionInfo groupPermInfo : groupPermInfos) { 188 if (requestedPerm.equals(groupPermInfo.name)) { 189 requestedPermissionInfo = groupPermInfo; 190 break; 191 } 192 } 193 194 if (requestedPermissionInfo == null) { 195 continue; 196 } 197 198 if ((requestedPermissionInfo.protectionLevel 199 & PermissionInfo.PROTECTION_MASK_BASE) 200 != PermissionInfo.PROTECTION_DANGEROUS 201 || (requestedPermissionInfo.flags 202 & PermissionInfo.FLAG_INSTALLED) == 0 203 || (requestedPermissionInfo.flags 204 & PermissionInfo.FLAG_REMOVED) != 0) { 205 continue; 206 } 207 208 AppPermissionGroup group = AppPermissionGroup.create(mContext, 209 app, groupInfo, groupPermInfos, user); 210 211 if (group == null) { 212 continue; 213 } 214 215 String label = mSkipUi ? app.packageName 216 : app.applicationInfo.loadLabel(mPm).toString(); 217 218 Drawable icon = null; 219 if (!mSkipUi) { 220 UserHandle userHandle = new UserHandle( 221 UserHandle.getUserId(group.getApp().applicationInfo.uid)); 222 223 icon = mPm.getUserBadgedIcon( 224 mPm.loadUnbadgedItemIcon(app.applicationInfo, app.applicationInfo), 225 userHandle); 226 } 227 228 PermissionApp permApp = new PermissionApp(app.packageName, group, label, icon, 229 app.applicationInfo); 230 231 permApps.add(permApp); 232 break; // move to the next app. 233 } 234 } 235 } 236 237 Collections.sort(permApps); 238 239 return permApps; 240 } 241 createMap(List<PermissionApp> result)242 private void createMap(List<PermissionApp> result) { 243 mAppLookup = new ArrayMap<>(); 244 for (PermissionApp app : result) { 245 mAppLookup.put(app.getKey(), app); 246 } 247 mPermApps = result; 248 } 249 getGroupInfo(String groupName)250 private PackageItemInfo getGroupInfo(String groupName) { 251 try { 252 return mContext.getPackageManager().getPermissionGroupInfo(groupName, 0); 253 } catch (NameNotFoundException e) { 254 /* ignore */ 255 } 256 try { 257 return mContext.getPackageManager().getPermissionInfo(groupName, 0); 258 } catch (NameNotFoundException e2) { 259 /* ignore */ 260 } 261 return null; 262 } 263 getGroupPermissionInfos(String groupName)264 private List<PermissionInfo> getGroupPermissionInfos(String groupName) { 265 try { 266 return mContext.getPackageManager().queryPermissionsByGroup(groupName, 0); 267 } catch (NameNotFoundException e) { 268 /* ignore */ 269 } 270 try { 271 PermissionInfo permissionInfo = mContext.getPackageManager() 272 .getPermissionInfo(groupName, 0); 273 List<PermissionInfo> permissions = new ArrayList<>(); 274 permissions.add(permissionInfo); 275 return permissions; 276 } catch (NameNotFoundException e2) { 277 /* ignore */ 278 } 279 return null; 280 } 281 loadGroupInfo()282 private void loadGroupInfo() { 283 PackageItemInfo info; 284 try { 285 info = mPm.getPermissionGroupInfo(mGroupName, 0); 286 } catch (PackageManager.NameNotFoundException e) { 287 try { 288 PermissionInfo permInfo = mPm.getPermissionInfo(mGroupName, 0); 289 if ((permInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) 290 != PermissionInfo.PROTECTION_DANGEROUS) { 291 Log.w(LOG_TAG, mGroupName + " is not a runtime permission"); 292 return; 293 } 294 info = permInfo; 295 } catch (NameNotFoundException reallyNotFound) { 296 Log.w(LOG_TAG, "Can't find permission: " + mGroupName, reallyNotFound); 297 return; 298 } 299 } 300 mLabel = info.loadLabel(mPm); 301 if (info.icon != 0) { 302 mIcon = info.loadUnbadgedIcon(mPm); 303 } else { 304 mIcon = mContext.getDrawable(R.drawable.ic_perm_device_info); 305 } 306 mIcon = Utils.applyTint(mContext, mIcon, android.R.attr.colorControlNormal); 307 } 308 309 public static class PermissionApp implements Comparable<PermissionApp> { 310 private final String mPackageName; 311 private final AppPermissionGroup mAppPermissionGroup; 312 private final String mLabel; 313 private final Drawable mIcon; 314 private final ApplicationInfo mInfo; 315 PermissionApp(String packageName, AppPermissionGroup appPermissionGroup, String label, Drawable icon, ApplicationInfo info)316 public PermissionApp(String packageName, AppPermissionGroup appPermissionGroup, 317 String label, Drawable icon, ApplicationInfo info) { 318 mPackageName = packageName; 319 mAppPermissionGroup = appPermissionGroup; 320 mLabel = label; 321 mIcon = icon; 322 mInfo = info; 323 } 324 getAppInfo()325 public ApplicationInfo getAppInfo() { 326 return mInfo; 327 } 328 getKey()329 public String getKey() { 330 return mPackageName + getUid(); 331 } 332 getLabel()333 public String getLabel() { 334 return mLabel; 335 } 336 getIcon()337 public Drawable getIcon() { 338 return mIcon; 339 } 340 areRuntimePermissionsGranted()341 public boolean areRuntimePermissionsGranted() { 342 return mAppPermissionGroup.areRuntimePermissionsGranted(); 343 } 344 isReviewRequired()345 public boolean isReviewRequired() { 346 return mAppPermissionGroup.isReviewRequired(); 347 } 348 grantRuntimePermissions()349 public void grantRuntimePermissions() { 350 mAppPermissionGroup.grantRuntimePermissions(false); 351 } 352 revokeRuntimePermissions()353 public void revokeRuntimePermissions() { 354 mAppPermissionGroup.revokeRuntimePermissions(false); 355 } 356 isPolicyFixed()357 public boolean isPolicyFixed() { 358 return mAppPermissionGroup.isPolicyFixed(); 359 } 360 isSystemFixed()361 public boolean isSystemFixed() { 362 return mAppPermissionGroup.isSystemFixed(); 363 } 364 hasGrantedByDefaultPermissions()365 public boolean hasGrantedByDefaultPermissions() { 366 return mAppPermissionGroup.hasGrantedByDefaultPermission(); 367 } 368 doesSupportRuntimePermissions()369 public boolean doesSupportRuntimePermissions() { 370 return mAppPermissionGroup.doesSupportRuntimePermissions(); 371 } 372 getUserId()373 public int getUserId() { 374 return mAppPermissionGroup.getUserId(); 375 } 376 getPackageName()377 public String getPackageName() { 378 return mPackageName; 379 } 380 getPermissionGroup()381 public AppPermissionGroup getPermissionGroup() { 382 return mAppPermissionGroup; 383 } 384 385 @Override compareTo(PermissionApp another)386 public int compareTo(PermissionApp another) { 387 final int result = mLabel.compareTo(another.mLabel); 388 if (result == 0) { 389 // Unbadged before badged. 390 return getKey().compareTo(another.getKey()); 391 } 392 return result; 393 } 394 getUid()395 public int getUid() { 396 return mAppPermissionGroup.getApp().applicationInfo.uid; 397 } 398 } 399 400 private class PermissionAppsLoader extends AsyncTask<Void, Void, List<PermissionApp>> { 401 402 @Override doInBackground(Void... args)403 protected List<PermissionApp> doInBackground(Void... args) { 404 return loadPermissionApps(); 405 } 406 407 @Override onPostExecute(List<PermissionApp> result)408 protected void onPostExecute(List<PermissionApp> result) { 409 mRefreshing = false; 410 createMap(result); 411 if (mCallback != null) { 412 mCallback.onPermissionsLoaded(PermissionApps.this); 413 } 414 } 415 } 416 417 /** 418 * Class used to reduce the number of calls to the package manager. 419 * This caches app information so it should only be used across parallel PermissionApps 420 * instances, and should not be retained across UI refresh. 421 */ 422 public static class PmCache { 423 private final SparseArray<List<PackageInfo>> mPackageInfoCache = new SparseArray<>(); 424 private final PackageManager mPm; 425 PmCache(PackageManager pm)426 public PmCache(PackageManager pm) { 427 mPm = pm; 428 } 429 getPackages(int userId)430 public synchronized List<PackageInfo> getPackages(int userId) { 431 List<PackageInfo> ret = mPackageInfoCache.get(userId); 432 if (ret == null) { 433 ret = mPm.getInstalledPackagesAsUser(PackageManager.GET_PERMISSIONS, userId); 434 mPackageInfoCache.put(userId, ret); 435 } 436 return ret; 437 } 438 } 439 440 public interface Callback { onPermissionsLoaded(PermissionApps permissionApps)441 void onPermissionsLoaded(PermissionApps permissionApps); 442 } 443 } 444