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