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