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 17 package com.android.packageinstaller.permission.model; 18 19 import static android.text.TextUtils.SAFE_STRING_FLAG_FIRST_LINE; 20 import static android.text.TextUtils.SAFE_STRING_FLAG_TRIM; 21 22 import android.Manifest; 23 import android.app.LoaderManager; 24 import android.app.LoaderManager.LoaderCallbacks; 25 import android.content.AsyncTaskLoader; 26 import android.content.Context; 27 import android.content.Loader; 28 import android.content.pm.PackageInfo; 29 import android.content.pm.PackageItemInfo; 30 import android.content.pm.PackageManager; 31 import android.content.pm.PermissionGroupInfo; 32 import android.content.pm.PermissionInfo; 33 import android.graphics.drawable.Drawable; 34 import android.os.Bundle; 35 import android.os.Handler; 36 import android.util.ArraySet; 37 38 import androidx.annotation.NonNull; 39 import androidx.annotation.Nullable; 40 41 import com.android.packageinstaller.permission.utils.Utils; 42 import com.android.permissioncontroller.R; 43 44 import java.util.ArrayList; 45 import java.util.Collections; 46 import java.util.List; 47 import java.util.Set; 48 import java.util.function.Supplier; 49 50 /** 51 * All {@link PermissionGroup permission groups} defined by any app. 52 */ 53 public final class PermissionGroups implements LoaderCallbacks<List<PermissionGroup>> { 54 private final ArrayList<PermissionGroup> mGroups = new ArrayList<>(); 55 private final Context mContext; 56 private final PermissionsGroupsChangeCallback mCallback; 57 private final boolean mGetAppUiInfo; 58 private final boolean mGetNonPlatformPermissions; 59 60 public interface PermissionsGroupsChangeCallback { onPermissionGroupsChanged()61 public void onPermissionGroupsChanged(); 62 } 63 PermissionGroups(Context context, LoaderManager loaderManager, PermissionsGroupsChangeCallback callback, boolean getAppUiInfo, boolean getNonPlatformPermissions)64 public PermissionGroups(Context context, LoaderManager loaderManager, 65 PermissionsGroupsChangeCallback callback, boolean getAppUiInfo, 66 boolean getNonPlatformPermissions) { 67 mContext = context; 68 mCallback = callback; 69 mGetAppUiInfo = getAppUiInfo; 70 mGetNonPlatformPermissions = getNonPlatformPermissions; 71 72 // Don't update immediately as otherwise we can get a callback before this object is 73 // initialized. 74 (new Handler()).post(() -> loaderManager.initLoader(0, null, this)); 75 } 76 77 @Override onCreateLoader(int id, Bundle args)78 public Loader<List<PermissionGroup>> onCreateLoader(int id, Bundle args) { 79 return new PermissionsLoader(mContext, mGetAppUiInfo, mGetNonPlatformPermissions); 80 } 81 82 @Override onLoadFinished(Loader<List<PermissionGroup>> loader, List<PermissionGroup> groups)83 public void onLoadFinished(Loader<List<PermissionGroup>> loader, 84 List<PermissionGroup> groups) { 85 if (mGroups.equals(groups)) { 86 return; 87 } 88 mGroups.clear(); 89 mGroups.addAll(groups); 90 mCallback.onPermissionGroupsChanged(); 91 } 92 93 @Override onLoaderReset(Loader<List<PermissionGroup>> loader)94 public void onLoaderReset(Loader<List<PermissionGroup>> loader) { 95 mGroups.clear(); 96 mCallback.onPermissionGroupsChanged(); 97 } 98 getGroups()99 public List<PermissionGroup> getGroups() { 100 return mGroups; 101 } 102 getGroup(String name)103 public PermissionGroup getGroup(String name) { 104 for (PermissionGroup group : mGroups) { 105 if (group.getName().equals(name)) { 106 return group; 107 } 108 } 109 return null; 110 } 111 loadItemInfoLabel(@onNull Context context, @NonNull PackageItemInfo itemInfo)112 private static @NonNull CharSequence loadItemInfoLabel(@NonNull Context context, 113 @NonNull PackageItemInfo itemInfo) { 114 CharSequence label = itemInfo.loadSafeLabel(context.getPackageManager(), 0, 115 SAFE_STRING_FLAG_FIRST_LINE | SAFE_STRING_FLAG_TRIM); 116 if (label == null) { 117 label = itemInfo.name; 118 } 119 return label; 120 } 121 loadItemInfoIcon(@onNull Context context, @NonNull PackageItemInfo itemInfo)122 private static @NonNull Drawable loadItemInfoIcon(@NonNull Context context, 123 @NonNull PackageItemInfo itemInfo) { 124 Drawable icon = null; 125 if (itemInfo.icon > 0) { 126 icon = Utils.loadDrawable(context.getPackageManager(), 127 itemInfo.packageName, itemInfo.icon); 128 } 129 if (icon == null) { 130 icon = context.getDrawable(R.drawable.ic_perm_device_info); 131 } 132 return icon; 133 } 134 135 /** 136 * Return all permission groups in the system. 137 * 138 * @param context Context to use 139 * @param isCanceled callback checked if the group resolution should be aborted 140 * @param getAppUiInfo If the UI info for apps should be updated 141 * @param getNonPlatformPermissions If we should get non-platform permission groups 142 * 143 * @return the list of all groups int the system 144 */ getAllPermissionGroups(@onNull Context context, @Nullable Supplier<Boolean> isCanceled, boolean getAppUiInfo, boolean getNonPlatformPermissions)145 public static @NonNull List<PermissionGroup> getAllPermissionGroups(@NonNull Context context, 146 @Nullable Supplier<Boolean> isCanceled, boolean getAppUiInfo, 147 boolean getNonPlatformPermissions) { 148 return getPermissionGroups(context, isCanceled, getAppUiInfo, getNonPlatformPermissions, 149 null, null); 150 } 151 152 /** 153 * Return all permission groups in the system. 154 * 155 * @param context Context to use 156 * @param isCanceled callback checked if the group resolution should be aborted 157 * @param getAppUiInfo If the UI info for apps should be updated 158 * @param getNonPlatformPermissions If we should get non-platform permission groups 159 * @param groupNames Optional groups to filter for. 160 * @param packageName Optional package to filter for. 161 * 162 * @return the list of all groups int the system 163 */ getPermissionGroups(@onNull Context context, @Nullable Supplier<Boolean> isCanceled, boolean getAppUiInfo, boolean getNonPlatformPermissions, @Nullable String[] groupNames, @Nullable String packageName)164 public static @NonNull List<PermissionGroup> getPermissionGroups(@NonNull Context context, 165 @Nullable Supplier<Boolean> isCanceled, boolean getAppUiInfo, 166 boolean getNonPlatformPermissions, @Nullable String[] groupNames, 167 @Nullable String packageName) { 168 PermissionApps.PmCache pmCache = new PermissionApps.PmCache( 169 context.getPackageManager()); 170 PermissionApps.AppDataCache appDataCache = new PermissionApps.AppDataCache( 171 context.getPackageManager(), context); 172 173 List<PermissionGroup> groups = new ArrayList<>(); 174 Set<String> seenPermissions = new ArraySet<>(); 175 176 PackageManager packageManager = context.getPackageManager(); 177 List<PermissionGroupInfo> groupInfos = getPermissionGroupInfos(context, groupNames); 178 179 for (PermissionGroupInfo groupInfo : groupInfos) { 180 // Mare sure we respond to cancellation. 181 if (isCanceled != null && isCanceled.get()) { 182 return Collections.emptyList(); 183 } 184 185 // Ignore non-platform permissions and the UNDEFINED group. 186 if (!getNonPlatformPermissions && !Utils.isModernPermissionGroup(groupInfo.name)) { 187 continue; 188 } 189 190 // Get the permissions in this group. 191 final List<PermissionInfo> groupPermissions; 192 try { 193 groupPermissions = Utils.getPermissionInfosForGroup(packageManager, groupInfo.name); 194 } catch (PackageManager.NameNotFoundException e) { 195 continue; 196 } 197 198 boolean hasRuntimePermissions = false; 199 200 // Cache seen permissions and see if group has runtime permissions. 201 for (PermissionInfo groupPermission : groupPermissions) { 202 seenPermissions.add(groupPermission.name); 203 if (groupPermission.getProtection() == PermissionInfo.PROTECTION_DANGEROUS 204 && (groupPermission.flags & PermissionInfo.FLAG_INSTALLED) != 0 205 && (groupPermission.flags & PermissionInfo.FLAG_REMOVED) == 0) { 206 hasRuntimePermissions = true; 207 } 208 } 209 210 // No runtime permissions - not interesting for us. 211 if (!hasRuntimePermissions) { 212 continue; 213 } 214 215 CharSequence label = loadItemInfoLabel(context, groupInfo); 216 Drawable icon = loadItemInfoIcon(context, groupInfo); 217 218 PermissionApps permApps = new PermissionApps(context, groupInfo.name, packageName, 219 null, pmCache, appDataCache); 220 permApps.refreshSync(getAppUiInfo); 221 222 // Create the group and add to the list. 223 PermissionGroup group = new PermissionGroup(groupInfo.name, 224 groupInfo.packageName, label, icon, permApps.getTotalCount(), 225 permApps.getGrantedCount(), permApps); 226 groups.add(group); 227 } 228 229 230 // Make sure we add groups for lone runtime permissions. 231 List<PackageInfo> installedPackages = context.getPackageManager() 232 .getInstalledPackages(PackageManager.GET_PERMISSIONS); 233 234 235 // We will filter out permissions that no package requests. 236 Set<String> requestedPermissions = new ArraySet<>(); 237 for (PackageInfo installedPackage : installedPackages) { 238 if (installedPackage.requestedPermissions == null) { 239 continue; 240 } 241 for (String permission : installedPackage.requestedPermissions) { 242 requestedPermissions.add(permission); 243 } 244 } 245 246 for (PackageInfo installedPackage : installedPackages) { 247 if (installedPackage.permissions == null) { 248 continue; 249 } 250 251 for (PermissionInfo permissionInfo : installedPackage.permissions) { 252 // If we have handled this permission, no more work to do. 253 if (!seenPermissions.add(permissionInfo.name)) { 254 continue; 255 } 256 257 // We care only about installed runtime permissions. 258 if (permissionInfo.getProtection() != PermissionInfo.PROTECTION_DANGEROUS 259 || (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) == 0) { 260 continue; 261 } 262 263 // Ignore non-platform permissions and the UNDEFINED group. 264 if (!getNonPlatformPermissions && !Utils.isModernPermissionGroup( 265 permissionInfo.name)) { 266 continue; 267 } 268 269 // If no app uses this permission, 270 if (!requestedPermissions.contains(permissionInfo.name)) { 271 continue; 272 } 273 274 CharSequence label = loadItemInfoLabel(context, permissionInfo); 275 Drawable icon = loadItemInfoIcon(context, permissionInfo); 276 277 PermissionApps permApps = new PermissionApps(context, permissionInfo.name, 278 packageName, null, pmCache, appDataCache); 279 permApps.refreshSync(getAppUiInfo); 280 281 // Create the group and add to the list. 282 PermissionGroup group = new PermissionGroup(permissionInfo.name, 283 permissionInfo.packageName, label, icon, 284 permApps.getTotalCount(), 285 permApps.getGrantedCount(), permApps); 286 groups.add(group); 287 } 288 } 289 290 // Hide undefined group if no 3rd party permissions are in it 291 int numGroups = groups.size(); 292 for (int i = 0; i < numGroups; i++) { 293 PermissionGroup group = groups.get(i); 294 if (group.getName().equals(Manifest.permission_group.UNDEFINED) 295 && group.getTotal() == 0) { 296 groups.remove(i); 297 break; 298 } 299 } 300 301 Collections.sort(groups); 302 return groups; 303 } 304 getPermissionGroupInfos( @onNull Context context, @Nullable String[] groupNames)305 private static @NonNull List<PermissionGroupInfo> getPermissionGroupInfos( 306 @NonNull Context context, @Nullable String[] groupNames) { 307 if (groupNames == null) { 308 return context.getPackageManager().getAllPermissionGroups(0); 309 } 310 try { 311 final List<PermissionGroupInfo> groupInfos = new ArrayList<>(groupNames.length); 312 for (int i = 0; i < groupNames.length; i++) { 313 final PermissionGroupInfo groupInfo = context.getPackageManager() 314 .getPermissionGroupInfo(groupNames[i], 0); 315 groupInfos.add(groupInfo); 316 } 317 return groupInfos; 318 } catch (PackageManager.NameNotFoundException e) { 319 return Collections.emptyList(); 320 } 321 } 322 323 private static final class PermissionsLoader extends AsyncTaskLoader<List<PermissionGroup>> 324 implements PackageManager.OnPermissionsChangedListener { 325 private final boolean mGetAppUiInfo; 326 private final boolean mGetNonPlatformPermissions; 327 PermissionsLoader(Context context, boolean getAppUiInfo, boolean getNonPlatformPermissions)328 PermissionsLoader(Context context, boolean getAppUiInfo, 329 boolean getNonPlatformPermissions) { 330 super(context); 331 mGetAppUiInfo = getAppUiInfo; 332 mGetNonPlatformPermissions = getNonPlatformPermissions; 333 } 334 335 @Override onStartLoading()336 protected void onStartLoading() { 337 getContext().getPackageManager().addOnPermissionsChangeListener(this); 338 forceLoad(); 339 } 340 341 @Override onStopLoading()342 protected void onStopLoading() { 343 getContext().getPackageManager().removeOnPermissionsChangeListener(this); 344 } 345 346 @Override loadInBackground()347 public List<PermissionGroup> loadInBackground() { 348 return getAllPermissionGroups(getContext(), this::isLoadInBackgroundCanceled, 349 mGetAppUiInfo, mGetNonPlatformPermissions); 350 } 351 352 @Override onPermissionsChanged(int uid)353 public void onPermissionsChanged(int uid) { 354 forceLoad(); 355 } 356 } 357 } 358