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_group.MICROPHONE; 20 import static android.app.AppOpsManager.OPSTR_PHONE_CALL_CAMERA; 21 import static android.app.AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE; 22 23 import static com.android.permissioncontroller.Constants.OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO; 24 25 import android.Manifest; 26 import android.app.AppOpsManager; 27 import android.app.AppOpsManager.AttributedHistoricalOps; 28 import android.app.AppOpsManager.AttributedOpEntry; 29 import android.app.AppOpsManager.HistoricalOp; 30 import android.app.AppOpsManager.HistoricalPackageOps; 31 import android.app.AppOpsManager.OpEntry; 32 import android.app.AppOpsManager.OpEventProxyInfo; 33 import android.app.AppOpsManager.PackageOps; 34 import android.content.pm.Attribution; 35 import android.content.res.Resources; 36 import android.media.AudioRecordingConfiguration; 37 import android.os.Build; 38 39 import androidx.annotation.NonNull; 40 import androidx.annotation.Nullable; 41 import androidx.annotation.RequiresApi; 42 43 import com.android.modules.utils.build.SdkLevel; 44 import com.android.permissioncontroller.permission.model.AppPermissionGroup; 45 import com.android.permissioncontroller.permission.model.Permission; 46 import com.android.permissioncontroller.permission.model.legacy.PermissionApps.PermissionApp; 47 48 import java.util.ArrayList; 49 import java.util.HashMap; 50 import java.util.HashSet; 51 import java.util.List; 52 import java.util.Map; 53 import java.util.Set; 54 import java.util.function.Function; 55 import java.util.stream.Collectors; 56 57 import kotlin.Triple; 58 59 /** 60 * Stats for permission usage of an app. This data is for a given time period, 61 * i.e. does not contain the full history. 62 */ 63 @RequiresApi(Build.VERSION_CODES.S) 64 public final class AppPermissionUsage { 65 private final @NonNull List<GroupUsage> mGroupUsages = new ArrayList<>(); 66 private final @NonNull PermissionApp mPermissionApp; 67 68 private static final int PRIVACY_HUB_FLAGS = AppOpsManager.OP_FLAG_SELF 69 | AppOpsManager.OP_FLAG_TRUSTED_PROXIED | AppOpsManager.OP_FLAG_TRUSTED_PROXY; 70 AppPermissionUsage(@onNull PermissionApp permissionApp, @NonNull List<AppPermissionGroup> groups, @Nullable PackageOps lastUsage, @Nullable HistoricalPackageOps historicalUsage, @Nullable ArrayList<AudioRecordingConfiguration> recordings)71 private AppPermissionUsage(@NonNull PermissionApp permissionApp, 72 @NonNull List<AppPermissionGroup> groups, @Nullable PackageOps lastUsage, 73 @Nullable HistoricalPackageOps historicalUsage, 74 @Nullable ArrayList<AudioRecordingConfiguration> recordings) { 75 mPermissionApp = permissionApp; 76 final int groupCount = groups.size(); 77 for (int i = 0; i < groupCount; i++) { 78 final AppPermissionGroup group = groups.get(i); 79 80 /** 81 * TODO: HACK HACK HACK. 82 * 83 * Exclude for the UIDs that are currently silenced. This happens if an app keeps 84 * recording while in the background for more than a few seconds. 85 */ 86 if (recordings != null && group.getName().equals(MICROPHONE)) { 87 boolean isSilenced = false; 88 int recordingsCount = recordings.size(); 89 for (int recordingNum = 0; recordingNum < recordingsCount; recordingNum++) { 90 AudioRecordingConfiguration recording = recordings.get(recordingNum); 91 if (recording.isClientSilenced()) { 92 isSilenced = true; 93 break; 94 } 95 } 96 97 if (isSilenced) { 98 continue; 99 } 100 } 101 102 mGroupUsages.add(new GroupUsage(group, lastUsage, historicalUsage)); 103 } 104 } 105 getApp()106 public @NonNull PermissionApp getApp() { 107 return mPermissionApp; 108 } 109 getPackageName()110 public @NonNull String getPackageName() { 111 return mPermissionApp.getPackageName(); 112 } 113 getUid()114 public int getUid() { 115 return mPermissionApp.getUid(); 116 } 117 getLastAccessTime()118 public long getLastAccessTime() { 119 long lastAccessTime = 0; 120 final int permissionCount = mGroupUsages.size(); 121 for (int i = 0; i < permissionCount; i++) { 122 final GroupUsage groupUsage = mGroupUsages.get(i); 123 lastAccessTime = Math.max(lastAccessTime, groupUsage.getLastAccessTime()); 124 } 125 return lastAccessTime; 126 } 127 getGroupUsages()128 public @NonNull List<GroupUsage> getGroupUsages() { 129 return mGroupUsages; 130 } 131 132 /** 133 * Stats for permission usage of a permission group. This data is for a 134 * given time period, i.e. does not contain the full history. 135 */ 136 public static class GroupUsage implements TimelineUsage { 137 private final @NonNull AppPermissionGroup mGroup; 138 private final @Nullable PackageOps mLastUsage; 139 private final @Nullable HistoricalPackageOps mHistoricalUsage; 140 GroupUsage(@onNull AppPermissionGroup group, @Nullable PackageOps lastUsage, @Nullable HistoricalPackageOps historicalUsage)141 public GroupUsage(@NonNull AppPermissionGroup group, @Nullable PackageOps lastUsage, 142 @Nullable HistoricalPackageOps historicalUsage) { 143 mGroup = group; 144 mLastUsage = lastUsage; 145 mHistoricalUsage = historicalUsage; 146 } 147 getLastAccessTime()148 public long getLastAccessTime() { 149 if (mLastUsage == null) { 150 return 0; 151 } 152 153 return lastAccessAggregate((op) -> op.getLastAccessTime(PRIVACY_HUB_FLAGS)); 154 } 155 156 /** 157 * Get the access duration. 158 */ getAccessDuration()159 public long getAccessDuration() { 160 if (mHistoricalUsage == null) { 161 return 0; 162 } 163 return extractAggregate((HistoricalOp op) -> 164 op.getForegroundAccessDuration(AppOpsManager.OP_FLAGS_ALL_TRUSTED) 165 + op.getBackgroundAccessDuration(AppOpsManager.OP_FLAGS_ALL_TRUSTED) 166 ); 167 } 168 169 170 @Override hasDiscreteData()171 public boolean hasDiscreteData() { 172 if (mHistoricalUsage == null) { 173 return false; 174 } 175 176 Set<String> allOps = getAllOps(mGroup); 177 for (String opName : allOps) { 178 final HistoricalOp historicalOp = mHistoricalUsage.getOp(opName); 179 if (historicalOp != null && historicalOp.getDiscreteAccessCount() > 0) { 180 return true; 181 } 182 } 183 return false; 184 } 185 186 @Override getAllDiscreteAccessTime()187 public List<Triple<Long, Long, OpEventProxyInfo>> getAllDiscreteAccessTime() { 188 List<Triple<Long, Long, OpEventProxyInfo>> allDiscreteAccessTime = new ArrayList<>(); 189 if (!hasDiscreteData()) { 190 return allDiscreteAccessTime; 191 } 192 193 Set<String> allOps = getAllOps(mGroup); 194 for (String opName : allOps) { 195 final HistoricalOp historicalOp = mHistoricalUsage.getOp(opName); 196 if (historicalOp == null) { 197 continue; 198 } 199 200 int discreteAccessCount = historicalOp.getDiscreteAccessCount(); 201 for (int j = 0; j < discreteAccessCount; j++) { 202 AppOpsManager.AttributedOpEntry opEntry = historicalOp.getDiscreteAccessAt(j); 203 allDiscreteAccessTime.add(new Triple<>( 204 opEntry.getLastAccessTime(PRIVACY_HUB_FLAGS), 205 opEntry.getLastDuration(PRIVACY_HUB_FLAGS), 206 opEntry.getLastProxyInfo(PRIVACY_HUB_FLAGS))); 207 } 208 } 209 210 return allDiscreteAccessTime; 211 } 212 isRunning()213 public boolean isRunning() { 214 if (mLastUsage == null) { 215 return false; 216 } 217 218 Set<String> allOps = getAllOps(mGroup); 219 final List<OpEntry> ops = mLastUsage.getOps(); 220 final int opCount = ops.size(); 221 for (int j = 0; j < opCount; j++) { 222 final OpEntry op = ops.get(j); 223 if (allOps.contains(op.getOpStr()) && op.isRunning()) { 224 return true; 225 } 226 } 227 228 return false; 229 } 230 extractAggregate(@onNull Function<HistoricalOp, Long> extractor)231 private long extractAggregate(@NonNull Function<HistoricalOp, Long> extractor) { 232 long aggregate = 0; 233 234 Set<String> allOps = getAllOps(mGroup); 235 for (String opName : allOps) { 236 final HistoricalOp historicalOp = mHistoricalUsage.getOp(opName); 237 if (historicalOp != null) { 238 aggregate += extractor.apply(historicalOp); 239 } 240 } 241 242 return aggregate; 243 } 244 lastAccessAggregate(@onNull Function<OpEntry, Long> extractor)245 private long lastAccessAggregate(@NonNull Function<OpEntry, Long> extractor) { 246 long aggregate = 0; 247 248 Set<String> allOps = getAllOps(mGroup); 249 final List<OpEntry> ops = mLastUsage.getOps(); 250 final int opCount = ops.size(); 251 252 for (int opNum = 0; opNum < opCount; opNum++) { 253 final OpEntry op = ops.get(opNum); 254 if (allOps.contains(op.getOpStr())) { 255 aggregate = Math.max(aggregate, extractor.apply(op)); 256 } 257 } 258 259 return aggregate; 260 } 261 getAllOps(AppPermissionGroup appPermissionGroup)262 private static Set<String> getAllOps(AppPermissionGroup appPermissionGroup) { 263 Set<String> allOps = new HashSet<>(); 264 List<Permission> permissions = appPermissionGroup.getPermissions(); 265 final int permissionCount = permissions.size(); 266 for (int permissionNum = 0; permissionNum < permissionCount; permissionNum++) { 267 final Permission permission = permissions.get(permissionNum); 268 final String opName = permission.getAppOp(); 269 if (opName != null) { 270 allOps.add(opName); 271 } 272 } 273 274 if (appPermissionGroup.getName().equals(Manifest.permission_group.MICROPHONE)) { 275 allOps.add(OPSTR_PHONE_CALL_MICROPHONE); 276 if (SdkLevel.isAtLeastT()) { 277 allOps.add(OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO); 278 } 279 } 280 281 if (appPermissionGroup.getName().equals(Manifest.permission_group.CAMERA)) { 282 allOps.add(OPSTR_PHONE_CALL_CAMERA); 283 } 284 285 return allOps; 286 } 287 288 @Override getGroup()289 public @NonNull AppPermissionGroup getGroup() { 290 return mGroup; 291 } 292 293 @Override getLabel()294 public int getLabel() { 295 return Resources.ID_NULL; 296 } 297 298 @Override getAttributionTags()299 public @Nullable ArrayList<String> getAttributionTags() { 300 if (mHistoricalUsage == null || mHistoricalUsage.getAttributedOpsCount() == 0) { 301 return null; 302 } 303 ArrayList<String> attributionTags = new ArrayList<>(); 304 int count = mHistoricalUsage.getAttributedOpsCount(); 305 for (int i = 0; i < count; i++) { 306 attributionTags.add(mHistoricalUsage.getAttributedOpsAt(i).getTag()); 307 } 308 return attributionTags; 309 } 310 311 /** Creates a lookup from the attribution tag to its label. **/ 312 @RequiresApi(Build.VERSION_CODES.S) getAttributionTagToLabelMap( Attribution[] attributions)313 private static Map<String, Integer> getAttributionTagToLabelMap( 314 Attribution[] attributions) { 315 Map<String, Integer> attributionTagToLabelMap = new HashMap<>(); 316 for (Attribution attribution : attributions) { 317 attributionTagToLabelMap.put(attribution.getTag(), attribution.getLabel()); 318 } 319 return attributionTagToLabelMap; 320 } 321 322 /** Partitions the usages based on the attribution tag label. */ 323 @RequiresApi(Build.VERSION_CODES.S) getAttributionLabelledGroupUsages()324 public List<AttributionLabelledGroupUsage> getAttributionLabelledGroupUsages() { 325 if (mHistoricalUsage == null || mHistoricalUsage.getAttributedOpsCount() == 0) { 326 return new ArrayList<AttributionLabelledGroupUsage>(); 327 } 328 Map<String, Integer> attributionTagToLabelMap = 329 getAttributionTagToLabelMap(getGroup().getApp().attributions); 330 331 Set<String> allOps = getAllOps(mGroup); 332 333 // we need to collect discreteAccessTime for each label 334 Map<Integer, AttributionLabelledGroupUsage.Builder> labelDiscreteAccessMap = 335 new HashMap<>(); 336 337 for (int i = 0; i < mHistoricalUsage.getAttributedOpsCount(); i++) { 338 AttributedHistoricalOps attributedOp = mHistoricalUsage.getAttributedOpsAt(i); 339 String attributionTag = attributedOp.getTag(); 340 341 for (String opName : allOps) { 342 final HistoricalOp historicalOp = attributedOp.getOp(opName); 343 if (historicalOp == null) { 344 continue; 345 } 346 347 int discreteAccessCount = historicalOp.getDiscreteAccessCount(); 348 for (int j = 0; j < discreteAccessCount; j++) { 349 AttributedOpEntry opEntry = historicalOp.getDiscreteAccessAt(j); 350 Integer label = attributionTagToLabelMap.get(attributedOp.getTag()); 351 if (label == null) { 352 label = Resources.ID_NULL; 353 } 354 if (!labelDiscreteAccessMap.containsKey(label)) { 355 labelDiscreteAccessMap.put(label, 356 new AttributionLabelledGroupUsage.Builder(label, getGroup())); 357 } 358 labelDiscreteAccessMap.get(label).addAttributionTag(attributionTag); 359 labelDiscreteAccessMap.get(label).addDiscreteAccessTime(new Triple<>( 360 opEntry.getLastAccessTime(PRIVACY_HUB_FLAGS), 361 opEntry.getLastDuration(PRIVACY_HUB_FLAGS), 362 opEntry.getLastProxyInfo(PRIVACY_HUB_FLAGS))); 363 } 364 } 365 } 366 367 return labelDiscreteAccessMap.entrySet().stream() 368 .map(e -> e.getValue().build()) 369 .collect(Collectors.toList()); 370 } 371 372 /** 373 * Represents the slice of {@link GroupUsage} with a label. 374 * 375 * <p> {@link Resources#ID_NULL} as label means that there was no entry for the 376 * attribution tag in the manifest.</p> 377 */ 378 public static class AttributionLabelledGroupUsage implements TimelineUsage { 379 private final int mLabel; 380 private final AppPermissionGroup mAppPermissionGroup; 381 private final List<String> mAttributionTags; 382 private final List<Triple<Long, Long, OpEventProxyInfo>> mDiscreteAccessTime; 383 AttributionLabelledGroupUsage(int label, AppPermissionGroup appPermissionGroup, List<String> attributionTags, List<Triple<Long, Long, OpEventProxyInfo>> discreteAccessTime)384 AttributionLabelledGroupUsage(int label, 385 AppPermissionGroup appPermissionGroup, 386 List<String> attributionTags, 387 List<Triple<Long, Long, OpEventProxyInfo>> discreteAccessTime) { 388 mLabel = label; 389 mAppPermissionGroup = appPermissionGroup; 390 mAttributionTags = attributionTags; 391 mDiscreteAccessTime = discreteAccessTime; 392 } 393 394 @Override getLabel()395 public int getLabel() { 396 return mLabel; 397 } 398 399 @Override hasDiscreteData()400 public boolean hasDiscreteData() { 401 return mDiscreteAccessTime.size() > 0; 402 } 403 404 @Override getAllDiscreteAccessTime()405 public List<Triple<Long, Long, OpEventProxyInfo>> getAllDiscreteAccessTime() { 406 return mDiscreteAccessTime; 407 } 408 409 @Override getAttributionTags()410 public List<String> getAttributionTags() { 411 return mAttributionTags; 412 } 413 414 @Override getGroup()415 public AppPermissionGroup getGroup() { 416 return mAppPermissionGroup; 417 } 418 419 static class Builder { 420 private final int mLabel; 421 private final AppPermissionGroup mAppPermissionGroup; 422 private Set<String> mAttributionTags; 423 private List<Triple<Long, Long, OpEventProxyInfo>> mDiscreteAccessTime; 424 Builder(int label, AppPermissionGroup appPermissionGroup)425 Builder(int label, AppPermissionGroup appPermissionGroup) { 426 mLabel = label; 427 mAppPermissionGroup = appPermissionGroup; 428 mAttributionTags = new HashSet<>(); 429 mDiscreteAccessTime = new ArrayList<>(); 430 } 431 addAttributionTag(String attributionTag)432 @NonNull Builder addAttributionTag(String attributionTag) { 433 mAttributionTags.add(attributionTag); 434 return this; 435 } 436 437 @NonNull addDiscreteAccessTime( Triple<Long, Long, OpEventProxyInfo> discreteAccessTime)438 Builder addDiscreteAccessTime( 439 Triple<Long, Long, OpEventProxyInfo> discreteAccessTime) { 440 mDiscreteAccessTime.add(discreteAccessTime); 441 return this; 442 } 443 build()444 AttributionLabelledGroupUsage build() { 445 ArrayList<String> attributionTagsList = new ArrayList<>(); 446 attributionTagsList.addAll(mAttributionTags); 447 return new AttributionLabelledGroupUsage(mLabel, 448 mAppPermissionGroup, 449 attributionTagsList, 450 mDiscreteAccessTime); 451 } 452 } 453 } 454 } 455 456 public static class Builder { 457 private final @NonNull List<AppPermissionGroup> mGroups = new ArrayList<>(); 458 private final @NonNull PermissionApp mPermissionApp; 459 private @Nullable PackageOps mLastUsage; 460 private @Nullable HistoricalPackageOps mHistoricalUsage; 461 private @Nullable ArrayList<AudioRecordingConfiguration> mAudioRecordingConfigurations; 462 Builder(@onNull PermissionApp permissionApp)463 public Builder(@NonNull PermissionApp permissionApp) { 464 mPermissionApp = permissionApp; 465 } 466 addGroup(@onNull AppPermissionGroup group)467 public @NonNull Builder addGroup(@NonNull AppPermissionGroup group) { 468 mGroups.add(group); 469 return this; 470 } 471 setLastUsage(@ullable PackageOps lastUsage)472 public @NonNull Builder setLastUsage(@Nullable PackageOps lastUsage) { 473 mLastUsage = lastUsage; 474 return this; 475 } 476 setHistoricalUsage(@ullable HistoricalPackageOps historicalUsage)477 public @NonNull Builder setHistoricalUsage(@Nullable HistoricalPackageOps historicalUsage) { 478 mHistoricalUsage = historicalUsage; 479 return this; 480 } 481 setRecordingConfiguration( @ullable ArrayList<AudioRecordingConfiguration> recordings)482 public @NonNull Builder setRecordingConfiguration( 483 @Nullable ArrayList<AudioRecordingConfiguration> recordings) { 484 mAudioRecordingConfigurations = recordings; 485 return this; 486 } 487 build()488 public @NonNull AppPermissionUsage build() { 489 if (mGroups.isEmpty()) { 490 throw new IllegalStateException("mGroups cannot be empty."); 491 } 492 return new AppPermissionUsage(mPermissionApp, mGroups, mLastUsage, mHistoricalUsage, 493 mAudioRecordingConfigurations); 494 } 495 } 496 497 /** Usage for showing timeline view for a specific permission group with a label. */ 498 public interface TimelineUsage { 499 /** 500 * Returns whether the usage has discrete data. 501 */ hasDiscreteData()502 boolean hasDiscreteData(); 503 504 /** 505 * Returns all discrete access time in millis. 506 * Returns a list of triples of (access time, access duration, proxy) 507 */ getAllDiscreteAccessTime()508 List<Triple<Long, Long, OpEventProxyInfo>> getAllDiscreteAccessTime(); 509 510 /** 511 * Returns attribution tags for the usage. 512 */ getAttributionTags()513 List<String> getAttributionTags(); 514 515 /** 516 * Returns the permission group of the usage. 517 */ getGroup()518 AppPermissionGroup getGroup(); 519 520 /** 521 * Returns the user facing string's resource id. 522 * 523 * <p> {@link Resources#ID_NULL} means show the app name otherwise get the string 524 * resource from the app context.</p> 525 */ getLabel()526 int getLabel(); 527 } 528 } 529