1 /* 2 * Copyright (C) 2022 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.v31; 18 19 import static android.Manifest.permission.CAMERA; 20 import static android.Manifest.permission.RECORD_AUDIO; 21 import static android.app.AppOpsManager.OPSTR_PHONE_CALL_CAMERA; 22 import static android.app.AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE; 23 import static android.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP; 24 25 import static com.android.permissioncontroller.Constants.OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO; 26 import static com.android.permissioncontroller.permission.utils.Utils.isHealthPermissionUiEnabled; 27 28 import android.app.AppOpsManager; 29 import android.app.AppOpsManager.HistoricalOps; 30 import android.app.AppOpsManager.HistoricalOpsRequest; 31 import android.app.AppOpsManager.HistoricalPackageOps; 32 import android.app.AppOpsManager.HistoricalUidOps; 33 import android.app.AppOpsManager.PackageOps; 34 import android.app.LoaderManager; 35 import android.app.LoaderManager.LoaderCallbacks; 36 import android.content.AsyncTaskLoader; 37 import android.content.Context; 38 import android.content.Loader; 39 import android.content.pm.PackageInfo; 40 import android.media.AudioManager; 41 import android.media.AudioRecordingConfiguration; 42 import android.os.Build; 43 import android.os.Bundle; 44 import android.os.Process; 45 import android.util.ArrayMap; 46 import android.util.ArraySet; 47 import android.util.Pair; 48 import android.util.SparseArray; 49 50 import androidx.annotation.NonNull; 51 import androidx.annotation.Nullable; 52 import androidx.annotation.RequiresApi; 53 54 import com.android.modules.utils.build.SdkLevel; 55 import com.android.permissioncontroller.permission.model.AppPermissionGroup; 56 import com.android.permissioncontroller.permission.model.Permission; 57 import com.android.permissioncontroller.permission.model.legacy.PermissionApps.PermissionApp; 58 import com.android.permissioncontroller.permission.model.legacy.PermissionGroup; 59 import com.android.permissioncontroller.permission.model.legacy.PermissionGroups; 60 import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage.Builder; 61 import com.android.permissioncontroller.permission.utils.Utils; 62 63 import java.util.ArrayList; 64 import java.util.Arrays; 65 import java.util.Collections; 66 import java.util.List; 67 import java.util.concurrent.CountDownLatch; 68 import java.util.concurrent.TimeUnit; 69 import java.util.concurrent.atomic.AtomicReference; 70 71 /** 72 * Loads all permission usages for a set of apps and permission groups. 73 */ 74 @RequiresApi(Build.VERSION_CODES.S) 75 public final class PermissionUsages implements LoaderCallbacks<List<AppPermissionUsage>> { 76 public static final int USAGE_FLAG_LAST = 1 << 0; 77 public static final int USAGE_FLAG_HISTORICAL = 1 << 2; 78 79 private final ArrayList<AppPermissionUsage> mUsages = new ArrayList<>(); 80 private final @NonNull Context mContext; 81 82 private static final String KEY_FILTER_UID = "KEY_FILTER_UID"; 83 private static final String KEY_FILTER_PACKAGE_NAME = "KEY_FILTER_PACKAGE_NAME"; 84 private static final String KEY_FILTER_PERMISSION_GROUP = "KEY_FILTER_PERMISSION_GROUP"; 85 private static final String KEY_FILTER_BEGIN_TIME_MILLIS = "KEY_FILTER_BEGIN_TIME_MILLIS"; 86 private static final String KEY_FILTER_END_TIME_MILLIS = "KEY_FILTER_END_TIME_MILLIS"; 87 private static final String KEY_USAGE_FLAGS = "KEY_USAGE_FLAGS"; 88 private static final String KEY_GET_UI_INFO = "KEY_GET_UI_INFO"; 89 private static final String KEY_GET_NON_PLATFORM_PERMISSIONS = 90 "KEY_GET_NON_PLATFORM_PERMISSIONS"; 91 private static final String TELECOM_PACKAGE = "com.android.server.telecom"; 92 private static final int DEFAULT_REQUIRED_PERMISSION_FLAG = 3; 93 94 public static final int HISTORY_FLAG_GET_ATTRIBUTION_CHAINS = 1 << 2; 95 96 private @Nullable PermissionsUsagesChangeCallback mCallback; 97 98 /** 99 * Callback for when the permission usages has loaded or changed. 100 */ 101 public interface PermissionsUsagesChangeCallback { 102 /** 103 * Called when the permission usages have loaded or changed. 104 */ onPermissionUsagesChanged()105 void onPermissionUsagesChanged(); 106 } 107 108 /** 109 * Creates a new instance of {@link PermissionUsages}. 110 */ PermissionUsages(@onNull Context context)111 public PermissionUsages(@NonNull Context context) { 112 mContext = context; 113 } 114 115 /** 116 * Start the {@link Loader} to load the permission usages in the background. Loads without a uid 117 * filter. 118 */ load(@ullable String filterPackageName, @Nullable String[] filterPermissionGroups, long filterBeginTimeMillis, long filterEndTimeMillis, int usageFlags, @NonNull LoaderManager loaderManager, boolean getUiInfo, boolean getNonPlatformPermissions, @NonNull PermissionsUsagesChangeCallback callback, boolean sync)119 public void load(@Nullable String filterPackageName, 120 @Nullable String[] filterPermissionGroups, long filterBeginTimeMillis, 121 long filterEndTimeMillis, int usageFlags, @NonNull LoaderManager loaderManager, 122 boolean getUiInfo, boolean getNonPlatformPermissions, 123 @NonNull PermissionsUsagesChangeCallback callback, boolean sync) { 124 load(Process.INVALID_UID, filterPackageName, filterPermissionGroups, filterBeginTimeMillis, 125 filterEndTimeMillis, usageFlags, loaderManager, getUiInfo, 126 getNonPlatformPermissions, callback, sync); 127 } 128 129 /** 130 * Start the {@link Loader} to load the permission usages in the background. Loads only 131 * permissions for the specified {@code filterUid}. 132 */ load(int filterUid, @Nullable String filterPackageName, @Nullable String[] filterPermissionGroups, long filterBeginTimeMillis, long filterEndTimeMillis, int usageFlags, @NonNull LoaderManager loaderManager, boolean getUiInfo, boolean getNonPlatformPermissions, @NonNull PermissionsUsagesChangeCallback callback, boolean sync)133 public void load(int filterUid, @Nullable String filterPackageName, 134 @Nullable String[] filterPermissionGroups, long filterBeginTimeMillis, 135 long filterEndTimeMillis, int usageFlags, @NonNull LoaderManager loaderManager, 136 boolean getUiInfo, boolean getNonPlatformPermissions, 137 @NonNull PermissionsUsagesChangeCallback callback, boolean sync) { 138 mCallback = callback; 139 final Bundle args = new Bundle(); 140 args.putInt(KEY_FILTER_UID, filterUid); 141 args.putString(KEY_FILTER_PACKAGE_NAME, filterPackageName); 142 args.putStringArray(KEY_FILTER_PERMISSION_GROUP, filterPermissionGroups); 143 args.putLong(KEY_FILTER_BEGIN_TIME_MILLIS, filterBeginTimeMillis); 144 args.putLong(KEY_FILTER_END_TIME_MILLIS, filterEndTimeMillis); 145 args.putInt(KEY_USAGE_FLAGS, usageFlags); 146 args.putBoolean(KEY_GET_UI_INFO, getUiInfo); 147 args.putBoolean(KEY_GET_NON_PLATFORM_PERMISSIONS, getNonPlatformPermissions); 148 if (sync) { 149 final UsageLoader loader = new UsageLoader(mContext, args); 150 final List<AppPermissionUsage> usages = loader.loadInBackground(); 151 onLoadFinished(loader, usages); 152 } else { 153 loaderManager.restartLoader(1, args, this); 154 } 155 } 156 157 @Override onCreateLoader(int id, Bundle args)158 public Loader<List<AppPermissionUsage>> onCreateLoader(int id, Bundle args) { 159 return new UsageLoader(mContext, args); 160 } 161 162 @Override onLoadFinished(@onNull Loader<List<AppPermissionUsage>> loader, List<AppPermissionUsage> usages)163 public void onLoadFinished(@NonNull Loader<List<AppPermissionUsage>> loader, 164 List<AppPermissionUsage> usages) { 165 mUsages.clear(); 166 mUsages.addAll(usages); 167 if (mCallback != null) { 168 mCallback.onPermissionUsagesChanged(); 169 } 170 } 171 172 @Override onLoaderReset(@onNull Loader<List<AppPermissionUsage>> loader)173 public void onLoaderReset(@NonNull Loader<List<AppPermissionUsage>> loader) { 174 mUsages.clear(); 175 mCallback.onPermissionUsagesChanged(); 176 } 177 178 /** 179 * Return the usages that have already been loaded. 180 */ getUsages()181 public @NonNull List<AppPermissionUsage> getUsages() { 182 return mUsages; 183 } 184 185 /** 186 * Stop the {@link Loader} from loading the usages. 187 */ stopLoader(@onNull LoaderManager loaderManager)188 public void stopLoader(@NonNull LoaderManager loaderManager) { 189 loaderManager.destroyLoader(1); 190 } 191 192 private static final class UsageLoader extends AsyncTaskLoader<List<AppPermissionUsage>> { 193 private final int mFilterUid; 194 private @Nullable String mFilterPackageName; 195 private @Nullable String[] mFilterPermissionGroups; 196 private final long mFilterBeginTimeMillis; 197 private final long mFilterEndTimeMillis; 198 private final int mUsageFlags; 199 private final boolean mGetUiInfo; 200 private final boolean mGetNonPlatformPermissions; 201 UsageLoader(@onNull Context context, @NonNull Bundle args)202 UsageLoader(@NonNull Context context, @NonNull Bundle args) { 203 super(context); 204 mFilterUid = args.getInt(KEY_FILTER_UID); 205 mFilterPackageName = args.getString(KEY_FILTER_PACKAGE_NAME); 206 mFilterPermissionGroups = args.getStringArray(KEY_FILTER_PERMISSION_GROUP); 207 mFilterBeginTimeMillis = args.getLong(KEY_FILTER_BEGIN_TIME_MILLIS); 208 mFilterEndTimeMillis = args.getLong(KEY_FILTER_END_TIME_MILLIS); 209 mUsageFlags = args.getInt(KEY_USAGE_FLAGS); 210 mGetUiInfo = args.getBoolean(KEY_GET_UI_INFO); 211 mGetNonPlatformPermissions = args.getBoolean(KEY_GET_NON_PLATFORM_PERMISSIONS); 212 } 213 214 @Override onStartLoading()215 protected void onStartLoading() { 216 forceLoad(); 217 } 218 219 @Override loadInBackground()220 public @NonNull List<AppPermissionUsage> loadInBackground() { 221 final List<PermissionGroup> groups = PermissionGroups.getPermissionGroups( 222 getContext(), this::isLoadInBackgroundCanceled, mGetUiInfo, 223 mGetNonPlatformPermissions, mFilterPermissionGroups, mFilterPackageName); 224 if (groups.isEmpty()) { 225 return Collections.emptyList(); 226 } 227 228 final List<AppPermissionUsage> usages = new ArrayList<>(); 229 final ArraySet<String> opNames = new ArraySet<>(); 230 final ArrayMap<Pair<Integer, String>, AppPermissionUsage.Builder> usageBuilders = 231 new ArrayMap<>(); 232 233 final int groupCount = groups.size(); 234 boolean telecomMicAndCamAdded = false; 235 for (int groupIdx = 0; groupIdx < groupCount; groupIdx++) { 236 final PermissionGroup group = groups.get(groupIdx); 237 // Filter out third party permissions 238 if (!(group.getDeclaringPackage().equals(Utils.OS_PKG) 239 || (isHealthPermissionUiEnabled() && HEALTH_PERMISSION_GROUP.equals( 240 group.getName())))) { 241 continue; 242 } 243 244 groups.add(group); 245 246 final List<PermissionApp> permissionApps = group.getPermissionApps().getApps(); 247 final int appCount = permissionApps.size(); 248 for (int appIdx = 0; appIdx < appCount; appIdx++) { 249 final PermissionApp permissionApp = permissionApps.get(appIdx); 250 if (mFilterUid != Process.INVALID_UID 251 && permissionApp.getAppInfo().uid != mFilterUid) { 252 continue; 253 } 254 255 final AppPermissionGroup appPermGroup = permissionApp.getPermissionGroup(); 256 if (!Utils.shouldShowPermission(getContext(), appPermGroup)) { 257 continue; 258 } 259 final Pair<Integer, String> usageKey = Pair.create(permissionApp.getUid(), 260 permissionApp.getPackageName()); 261 AppPermissionUsage.Builder usageBuilder = usageBuilders.get(usageKey); 262 if (usageBuilder == null) { 263 usageBuilder = new Builder(permissionApp); 264 usageBuilders.put(usageKey, usageBuilder); 265 } 266 usageBuilder.addGroup(appPermGroup); 267 268 // Since PermissionGroups.getPermissionGroups doesn't return 269 // Telecom PermissionApp entity with Microphone and Camera permission groups, 270 // we have to manually add those entries here. 271 if (!telecomMicAndCamAdded 272 && permissionApp.getPackageName().equals(TELECOM_PACKAGE)) { 273 PackageInfo telecomPackageInfo = appPermGroup.getApp(); 274 275 String[] newReqPerms = Arrays.copyOf( 276 telecomPackageInfo.requestedPermissions, 277 telecomPackageInfo.requestedPermissions.length + 2); 278 newReqPerms[telecomPackageInfo.requestedPermissions.length] = RECORD_AUDIO; 279 newReqPerms[telecomPackageInfo.requestedPermissions.length + 1] = CAMERA; 280 telecomPackageInfo.requestedPermissions = newReqPerms; 281 282 int[] newReqPermsFlags = Arrays.copyOf( 283 telecomPackageInfo.requestedPermissionsFlags, 284 telecomPackageInfo.requestedPermissionsFlags.length + 2); 285 newReqPermsFlags[telecomPackageInfo.requestedPermissionsFlags.length] = 286 DEFAULT_REQUIRED_PERMISSION_FLAG; 287 newReqPermsFlags[telecomPackageInfo.requestedPermissionsFlags.length + 1] = 288 DEFAULT_REQUIRED_PERMISSION_FLAG; 289 telecomPackageInfo.requestedPermissionsFlags = newReqPermsFlags; 290 291 AppPermissionGroup micGroup = AppPermissionGroup.create(getContext(), 292 telecomPackageInfo, RECORD_AUDIO, false); 293 AppPermissionGroup camGroup = AppPermissionGroup.create(getContext(), 294 telecomPackageInfo, CAMERA, false); 295 296 if (micGroup != null) { 297 usageBuilder.addGroup(micGroup); 298 } 299 300 if (camGroup != null) { 301 usageBuilder.addGroup(camGroup); 302 } 303 304 telecomMicAndCamAdded = true; 305 } 306 307 final List<Permission> permissions = appPermGroup.getPermissions(); 308 final int permCount = permissions.size(); 309 for (int permIdx = 0; permIdx < permCount; permIdx++) { 310 final Permission permission = permissions.get(permIdx); 311 final String opName = permission.getAppOp(); 312 if (opName != null) { 313 opNames.add(opName); 314 } 315 } 316 } 317 } 318 319 if (usageBuilders.isEmpty()) { 320 return Collections.emptyList(); 321 } 322 323 final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class); 324 325 // Get last usage data and put in a map for a quick lookup. 326 final ArrayMap<Pair<Integer, String>, PackageOps> lastUsages = 327 new ArrayMap<>(usageBuilders.size()); 328 opNames.add(OPSTR_PHONE_CALL_MICROPHONE); 329 opNames.add(OPSTR_PHONE_CALL_CAMERA); 330 if (SdkLevel.isAtLeastT()) { 331 opNames.add(OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO); 332 } 333 final String[] opNamesArray = opNames.toArray(new String[opNames.size()]); 334 if ((mUsageFlags & USAGE_FLAG_LAST) != 0) { 335 final List<PackageOps> usageOps; 336 if (mFilterPackageName != null || mFilterUid != Process.INVALID_UID) { 337 usageOps = appOpsManager.getOpsForPackage(mFilterUid, mFilterPackageName, 338 opNamesArray); 339 } else { 340 usageOps = appOpsManager.getPackagesForOps(opNamesArray); 341 } 342 if (usageOps != null && !usageOps.isEmpty()) { 343 final int usageOpsCount = usageOps.size(); 344 for (int i = 0; i < usageOpsCount; i++) { 345 final PackageOps usageOp = usageOps.get(i); 346 lastUsages.put(Pair.create(usageOp.getUid(), usageOp.getPackageName()), 347 usageOp); 348 } 349 } 350 } 351 352 if (isLoadInBackgroundCanceled()) { 353 return Collections.emptyList(); 354 } 355 356 // Get historical usage data and put in a map for a quick lookup 357 final ArrayMap<Pair<Integer, String>, HistoricalPackageOps> historicalUsages = 358 new ArrayMap<>(usageBuilders.size()); 359 if ((mUsageFlags & USAGE_FLAG_HISTORICAL) != 0) { 360 final AtomicReference<HistoricalOps> historicalOpsRef = new AtomicReference<>(); 361 final CountDownLatch latch = new CountDownLatch(1); 362 363 // query for discrete timeline data for location, mic and camera 364 final HistoricalOpsRequest request = new HistoricalOpsRequest.Builder( 365 mFilterBeginTimeMillis, mFilterEndTimeMillis) 366 .setFlags(AppOpsManager.OP_FLAG_SELF 367 | AppOpsManager.OP_FLAG_TRUSTED_PROXIED) 368 .setHistoryFlags(AppOpsManager.HISTORY_FLAG_DISCRETE 369 | HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) 370 .build(); 371 appOpsManager.getHistoricalOps(request, Runnable::run, 372 (HistoricalOps ops) -> { 373 historicalOpsRef.set(ops); 374 latch.countDown(); 375 }); 376 try { 377 latch.await(5, TimeUnit.DAYS); 378 } catch (InterruptedException ignored) { } 379 380 final HistoricalOps historicalOps = historicalOpsRef.get(); 381 382 if (historicalOps != null) { 383 final int uidCount = historicalOps.getUidCount(); 384 for (int i = 0; i < uidCount; i++) { 385 final HistoricalUidOps uidOps = historicalOps.getUidOpsAt(i); 386 final int packageCount = uidOps.getPackageCount(); 387 for (int j = 0; j < packageCount; j++) { 388 final HistoricalPackageOps packageOps = uidOps.getPackageOpsAt(j); 389 historicalUsages.put( 390 Pair.create(uidOps.getUid(), packageOps.getPackageName()), 391 packageOps); 392 } 393 } 394 } 395 } 396 397 // Get audio recording config 398 List<AudioRecordingConfiguration> allRecordings = getContext() 399 .getSystemService(AudioManager.class).getActiveRecordingConfigurations(); 400 SparseArray<ArrayList<AudioRecordingConfiguration>> recordingsByUid = 401 new SparseArray<>(); 402 403 final int recordingsCount = allRecordings.size(); 404 for (int i = 0; i < recordingsCount; i++) { 405 AudioRecordingConfiguration recording = allRecordings.get(i); 406 407 ArrayList<AudioRecordingConfiguration> recordings = recordingsByUid.get( 408 recording.getClientUid()); 409 if (recordings == null) { 410 recordings = new ArrayList<>(); 411 recordingsByUid.put(recording.getClientUid(), recordings); 412 } 413 recordings.add(recording); 414 } 415 416 // Construct the historical usages based on data we fetched 417 final int builderCount = usageBuilders.size(); 418 for (int i = 0; i < builderCount; i++) { 419 final Pair<Integer, String> key = usageBuilders.keyAt(i); 420 final Builder usageBuilder = usageBuilders.valueAt(i); 421 final PackageOps lastUsage = lastUsages.get(key); 422 usageBuilder.setLastUsage(lastUsage); 423 final HistoricalPackageOps historicalUsage = historicalUsages.get(key); 424 425 usageBuilder.setHistoricalUsage(historicalUsage); 426 usageBuilder.setRecordingConfiguration(recordingsByUid.get(key.first)); 427 usages.add(usageBuilder.build()); 428 } 429 430 return usages; 431 } 432 } 433 } 434