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