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