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