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 android.permission; 18 19 import static android.Manifest.permission_group.CAMERA; 20 import static android.Manifest.permission_group.LOCATION; 21 import static android.Manifest.permission_group.MICROPHONE; 22 import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE; 23 import static android.app.AppOpsManager.ATTRIBUTION_FLAGS_NONE; 24 import static android.app.AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR; 25 import static android.app.AppOpsManager.ATTRIBUTION_FLAG_RECEIVER; 26 import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED; 27 import static android.app.AppOpsManager.AttributionFlags; 28 import static android.app.AppOpsManager.OPSTR_CAMERA; 29 import static android.app.AppOpsManager.OPSTR_COARSE_LOCATION; 30 import static android.app.AppOpsManager.OPSTR_FINE_LOCATION; 31 import static android.app.AppOpsManager.OPSTR_PHONE_CALL_CAMERA; 32 import static android.app.AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE; 33 import static android.app.AppOpsManager.OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO; 34 import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO; 35 import static android.app.AppOpsManager.OP_CAMERA; 36 import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED; 37 import static android.app.AppOpsManager.OP_RECORD_AUDIO; 38 import static android.media.AudioSystem.MODE_IN_COMMUNICATION; 39 import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; 40 41 import android.annotation.NonNull; 42 import android.annotation.Nullable; 43 import android.app.AppOpsManager; 44 import android.companion.virtual.VirtualDevice; 45 import android.companion.virtual.VirtualDeviceManager; 46 import android.content.Context; 47 import android.content.pm.ApplicationInfo; 48 import android.content.pm.Attribution; 49 import android.content.pm.PackageInfo; 50 import android.content.pm.PackageManager; 51 import android.content.res.Resources; 52 import android.icu.text.ListFormatter; 53 import android.location.LocationManager; 54 import android.media.AudioManager; 55 import android.os.Process; 56 import android.os.UserHandle; 57 import android.permission.flags.Flags; 58 import android.provider.DeviceConfig; 59 import android.telephony.TelephonyManager; 60 import android.util.ArrayMap; 61 import android.util.ArraySet; 62 import android.util.Slog; 63 64 import com.android.internal.annotations.GuardedBy; 65 66 import java.util.ArrayList; 67 import java.util.Collections; 68 import java.util.List; 69 import java.util.Map; 70 import java.util.Objects; 71 72 /** 73 * A helper which gets all apps which have used microphone, camera, and possible location 74 * permissions within a certain timeframe, as well as possible special attributions, and if the 75 * usage is a phone call. 76 * 77 * @hide 78 */ 79 public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedListener, 80 AppOpsManager.OnOpStartedListener { 81 82 private static final String LOG_TAG = PermissionUsageHelper.class.getName(); 83 84 /** 85 * Whether to show the mic and camera icons. 86 */ 87 private static final String PROPERTY_CAMERA_MIC_ICONS_ENABLED = "camera_mic_icons_enabled"; 88 89 /** 90 * Whether to show the location indicators. 91 */ 92 private static final String PROPERTY_LOCATION_INDICATORS_ENABLED = 93 "location_indicators_enabled"; 94 95 /** 96 * How long after an access to show it as "recent" 97 */ 98 private static final String RECENT_ACCESS_TIME_MS = "recent_access_time_ms"; 99 100 /** 101 * How long after an access to show it as "running" 102 */ 103 private static final String RUNNING_ACCESS_TIME_MS = "running_access_time_ms"; 104 105 private static final String SYSTEM_PKG = "android"; 106 107 private static final long DEFAULT_RUNNING_TIME_MS = 5000L; 108 private static final long DEFAULT_RECENT_TIME_MS = 15000L; 109 shouldShowIndicators()110 private static boolean shouldShowIndicators() { 111 return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, 112 PROPERTY_CAMERA_MIC_ICONS_ENABLED, true); 113 } 114 shouldShowLocationIndicator()115 private static boolean shouldShowLocationIndicator() { 116 return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, 117 PROPERTY_LOCATION_INDICATORS_ENABLED, false); 118 } 119 getRecentThreshold(Long now)120 private static long getRecentThreshold(Long now) { 121 return now - DeviceConfig.getLong(DeviceConfig.NAMESPACE_PRIVACY, 122 RECENT_ACCESS_TIME_MS, DEFAULT_RECENT_TIME_MS); 123 } 124 getRunningThreshold(Long now)125 private static long getRunningThreshold(Long now) { 126 return now - DeviceConfig.getLong(DeviceConfig.NAMESPACE_PRIVACY, 127 RUNNING_ACCESS_TIME_MS, DEFAULT_RUNNING_TIME_MS); 128 } 129 130 private static final List<String> LOCATION_OPS = List.of( 131 OPSTR_COARSE_LOCATION, 132 OPSTR_FINE_LOCATION 133 ); 134 135 private static final List<String> MIC_OPS = List.of( 136 OPSTR_PHONE_CALL_MICROPHONE, 137 OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO, 138 OPSTR_RECORD_AUDIO 139 ); 140 141 private static final List<String> CAMERA_OPS = List.of( 142 OPSTR_PHONE_CALL_CAMERA, 143 OPSTR_CAMERA 144 ); 145 getGroupForOp(String op)146 private static @NonNull String getGroupForOp(String op) { 147 switch (op) { 148 case OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO: 149 case OPSTR_RECORD_AUDIO: 150 return MICROPHONE; 151 case OPSTR_CAMERA: 152 return CAMERA; 153 case OPSTR_PHONE_CALL_MICROPHONE: 154 case OPSTR_PHONE_CALL_CAMERA: 155 return op; 156 case OPSTR_COARSE_LOCATION: 157 case OPSTR_FINE_LOCATION: 158 return LOCATION; 159 default: 160 throw new IllegalArgumentException("Unknown app op: " + op); 161 } 162 } 163 164 private Context mContext; 165 private ArrayMap<UserHandle, Context> mUserContexts; 166 private PackageManager mPkgManager; 167 private AppOpsManager mAppOpsManager; 168 private VirtualDeviceManager mVirtualDeviceManager; 169 @GuardedBy("mAttributionChains") 170 private final ArrayMap<Integer, ArrayList<AccessChainLink>> mAttributionChains = 171 new ArrayMap<>(); 172 173 /** 174 * Constructor for PermissionUsageHelper 175 * 176 * @param context The context from which to derive the package information 177 */ PermissionUsageHelper(@onNull Context context)178 public PermissionUsageHelper(@NonNull Context context) { 179 mContext = context; 180 mPkgManager = context.getPackageManager(); 181 mAppOpsManager = context.getSystemService(AppOpsManager.class); 182 mVirtualDeviceManager = context.getSystemService(VirtualDeviceManager.class); 183 mUserContexts = new ArrayMap<>(); 184 mUserContexts.put(Process.myUserHandle(), mContext); 185 // TODO ntmyren: make this listen for flag enable/disable changes 186 String[] opStrs = {OPSTR_CAMERA, OPSTR_RECORD_AUDIO}; 187 mAppOpsManager.startWatchingActive(opStrs, context.getMainExecutor(), this); 188 int[] ops = {OP_CAMERA, OP_RECORD_AUDIO}; 189 mAppOpsManager.startWatchingStarted(ops, this); 190 } 191 getUserContext(UserHandle user)192 private Context getUserContext(UserHandle user) { 193 if (!(mUserContexts.containsKey(user))) { 194 mUserContexts.put(user, mContext.createContextAsUser(user, 0)); 195 } 196 return mUserContexts.get(user); 197 } 198 tearDown()199 public void tearDown() { 200 mAppOpsManager.stopWatchingActive(this); 201 mAppOpsManager.stopWatchingStarted(this); 202 } 203 204 @Override onOpActiveChanged(@onNull String op, int uid, @NonNull String packageName, boolean active)205 public void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName, 206 boolean active) { 207 // not part of an attribution chain. Do nothing 208 } 209 210 @Override onOpActiveChanged(@onNull String op, int uid, @NonNull String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags int attributionFlags, int attributionChainId)211 public void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName, 212 @Nullable String attributionTag, boolean active, @AttributionFlags int attributionFlags, 213 int attributionChainId) { 214 if (active) { 215 // Started callback handles these 216 return; 217 } 218 219 // if any link in the chain is finished, remove the chain. Then, find any other chains that 220 // contain this op/package/uid/tag combination, and remove them, as well. 221 // TODO ntmyren: be smarter about this 222 synchronized (mAttributionChains) { 223 mAttributionChains.remove(attributionChainId); 224 int numChains = mAttributionChains.size(); 225 ArrayList<Integer> toRemove = new ArrayList<>(); 226 for (int i = 0; i < numChains; i++) { 227 int chainId = mAttributionChains.keyAt(i); 228 ArrayList<AccessChainLink> chain = mAttributionChains.valueAt(i); 229 int chainSize = chain.size(); 230 for (int j = 0; j < chainSize; j++) { 231 AccessChainLink link = chain.get(j); 232 if (link.packageAndOpEquals(op, packageName, attributionTag, uid)) { 233 toRemove.add(chainId); 234 break; 235 } 236 } 237 } 238 mAttributionChains.removeAll(toRemove); 239 } 240 } 241 242 @Override onOpStarted(int op, int uid, String packageName, String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result)243 public void onOpStarted(int op, int uid, String packageName, String attributionTag, 244 @AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result) { 245 // not part of an attribution chain. Do nothing 246 } 247 248 @Override onOpStarted(int op, int uid, String packageName, String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result, @StartedType int startedType, @AttributionFlags int attributionFlags, int attributionChainId)249 public void onOpStarted(int op, int uid, String packageName, String attributionTag, 250 @AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result, 251 @StartedType int startedType, @AttributionFlags int attributionFlags, 252 int attributionChainId) { 253 if (startedType == START_TYPE_FAILED || attributionChainId == ATTRIBUTION_CHAIN_ID_NONE 254 || attributionFlags == ATTRIBUTION_FLAGS_NONE 255 || (attributionFlags & ATTRIBUTION_FLAG_TRUSTED) == 0) { 256 // If this is not a successful start, or it is not a chain, or it is untrusted, return 257 return; 258 } 259 synchronized (mAttributionChains) { 260 addLinkToChainIfNotPresentLocked(AppOpsManager.opToPublicName(op), packageName, uid, 261 attributionTag, attributionFlags, attributionChainId); 262 } 263 } 264 addLinkToChainIfNotPresentLocked(String op, String packageName, int uid, String attributionTag, int attributionFlags, int attributionChainId)265 private void addLinkToChainIfNotPresentLocked(String op, String packageName, int uid, 266 String attributionTag, int attributionFlags, int attributionChainId) { 267 268 ArrayList<AccessChainLink> currentChain = mAttributionChains.computeIfAbsent( 269 attributionChainId, k -> new ArrayList<>()); 270 AccessChainLink link = new AccessChainLink(op, packageName, attributionTag, uid, 271 attributionFlags); 272 273 if (currentChain.contains(link)) { 274 return; 275 } 276 277 int currSize = currentChain.size(); 278 if (currSize == 0 || link.isEnd() || !currentChain.get(currSize - 1).isEnd()) { 279 // if the list is empty, this link is the end, or the last link in the current chain 280 // isn't the end, add it to the end 281 currentChain.add(link); 282 } else if (link.isStart()) { 283 currentChain.add(0, link); 284 } else if (currentChain.get(currentChain.size() - 1).isEnd()) { 285 // we already have the end, and this is a mid node, so insert before the end 286 currentChain.add(currSize - 1, link); 287 } 288 } 289 290 /** 291 * Return Op usage for CAMERA, LOCATION AND MICROPHONE for all packages for a device. 292 * The returned data is to power privacy indicator. 293 */ getOpUsageDataByDevice( boolean includeMicrophoneUsage, String deviceId)294 public @NonNull List<PermissionGroupUsage> getOpUsageDataByDevice( 295 boolean includeMicrophoneUsage, String deviceId) { 296 List<PermissionGroupUsage> usages = new ArrayList<>(); 297 298 if (!shouldShowIndicators()) { 299 return usages; 300 } 301 302 List<String> ops = new ArrayList<>(CAMERA_OPS); 303 if (shouldShowLocationIndicator()) { 304 ops.addAll(LOCATION_OPS); 305 } 306 if (includeMicrophoneUsage) { 307 ops.addAll(MIC_OPS); 308 } 309 310 Map<String, List<OpUsage>> rawUsages = getOpUsagesByDevice(ops, deviceId); 311 312 ArrayList<String> usedPermGroups = new ArrayList<>(rawUsages.keySet()); 313 314 // If we have a phone call, and a carrier privileged app using microphone, hide the 315 // phone call. 316 AudioManager audioManager = mContext.getSystemService(AudioManager.class); 317 boolean hasPhoneCall = usedPermGroups.contains(OPSTR_PHONE_CALL_CAMERA) 318 || usedPermGroups.contains(OPSTR_PHONE_CALL_MICROPHONE); 319 if (hasPhoneCall && usedPermGroups.contains(MICROPHONE) && audioManager.getMode() 320 == MODE_IN_COMMUNICATION) { 321 TelephonyManager telephonyManager = 322 mContext.getSystemService(TelephonyManager.class); 323 List<OpUsage> permUsages = rawUsages.get(MICROPHONE); 324 for (int usageNum = 0; usageNum < permUsages.size(); usageNum++) { 325 if (telephonyManager.checkCarrierPrivilegesForPackage( 326 permUsages.get(usageNum).packageName) 327 == CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { 328 usedPermGroups.remove(OPSTR_PHONE_CALL_CAMERA); 329 usedPermGroups.remove(OPSTR_PHONE_CALL_MICROPHONE); 330 } 331 } 332 } 333 334 // map of package name -> map of attribution tag -> attribution labels 335 ArrayMap<String, Map<String, String>> subAttributionLabelsMap = new ArrayMap<>(); 336 337 for (int permGroupNum = 0; permGroupNum < usedPermGroups.size(); permGroupNum++) { 338 boolean isPhone = false; 339 String permGroup = usedPermGroups.get(permGroupNum); 340 341 ArrayMap<OpUsage, CharSequence> usagesWithLabels = 342 getUniqueUsagesWithLabels(permGroup, rawUsages.get(permGroup)); 343 344 updateSubattributionLabelsMap(rawUsages.get(permGroup), subAttributionLabelsMap); 345 346 if (permGroup.equals(OPSTR_PHONE_CALL_MICROPHONE)) { 347 isPhone = true; 348 permGroup = MICROPHONE; 349 } else if (permGroup.equals(OPSTR_PHONE_CALL_CAMERA)) { 350 isPhone = true; 351 permGroup = CAMERA; 352 } 353 354 for (int usageNum = 0; usageNum < usagesWithLabels.size(); usageNum++) { 355 OpUsage usage = usagesWithLabels.keyAt(usageNum); 356 String attributionLabel = subAttributionLabelsMap.getOrDefault(usage.packageName, 357 new ArrayMap<>()).getOrDefault(usage.attributionTag, null); 358 usages.add( 359 new PermissionGroupUsage(usage.packageName, usage.uid, usage.lastAccessTime, 360 permGroup, 361 usage.isRunning, isPhone, usage.attributionTag, attributionLabel, 362 usagesWithLabels.valueAt(usageNum), deviceId)); 363 } 364 } 365 366 return usages; 367 } 368 369 /** 370 * Return Op usage for CAMERA, LOCATION AND MICROPHONE for all packages and all connected 371 * devices. 372 * The returned data is to power privacy indicator. 373 */ getOpUsageDataForAllDevices( boolean includeMicrophoneUsage)374 public @NonNull List<PermissionGroupUsage> getOpUsageDataForAllDevices( 375 boolean includeMicrophoneUsage) { 376 List<PermissionGroupUsage> allUsages = new ArrayList<>(); 377 List<VirtualDevice> virtualDevices = mVirtualDeviceManager.getVirtualDevices(); 378 ArraySet<String> persistentDeviceIds = new ArraySet<>(); 379 380 for (int num = 0; num < virtualDevices.size(); num++) { 381 persistentDeviceIds.add(virtualDevices.get(num).getPersistentDeviceId()); 382 } 383 persistentDeviceIds.add(VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); 384 385 for (int index = 0; index < persistentDeviceIds.size(); index++) { 386 allUsages.addAll( 387 getOpUsageDataByDevice(includeMicrophoneUsage, 388 persistentDeviceIds.valueAt(index))); 389 } 390 391 return allUsages; 392 } 393 394 updateSubattributionLabelsMap(List<OpUsage> usages, ArrayMap<String, Map<String, String>> subAttributionLabelsMap)395 private void updateSubattributionLabelsMap(List<OpUsage> usages, 396 ArrayMap<String, Map<String, String>> subAttributionLabelsMap) { 397 if (usages == null || usages.isEmpty()) { 398 return; 399 } 400 for (OpUsage usage : usages) { 401 if (usage.attributionTag != null && !subAttributionLabelsMap.containsKey( 402 usage.packageName)) { 403 subAttributionLabelsMap.put(usage.packageName, 404 getSubattributionLabelsForPackage(usage.packageName, usage.uid)); 405 } 406 } 407 } 408 409 /** 410 * Query attribution labels for a package 411 * 412 * @param packageName 413 * @param uid 414 * @return map of attribution tag -> attribution labels for a package 415 */ getSubattributionLabelsForPackage(String packageName, int uid)416 private ArrayMap<String, String> getSubattributionLabelsForPackage(String packageName, 417 int uid) { 418 ArrayMap<String, String> attributionLabelMap = new ArrayMap<>(); 419 UserHandle user = UserHandle.getUserHandleForUid(uid); 420 try { 421 if (!isSubattributionSupported(packageName, uid)) { 422 return attributionLabelMap; 423 } 424 Context userContext = getUserContext(user); 425 PackageInfo packageInfo = userContext.getPackageManager().getPackageInfo( 426 packageName, 427 PackageManager.PackageInfoFlags.of( 428 PackageManager.GET_PERMISSIONS | PackageManager.GET_ATTRIBUTIONS_LONG)); 429 Context pkgContext = userContext.createPackageContext(packageInfo.packageName, 0); 430 for (Attribution attribution : packageInfo.attributions) { 431 try { 432 String resourceForLabel = pkgContext.getString(attribution.getLabel()); 433 attributionLabelMap.put(attribution.getTag(), resourceForLabel); 434 } catch (Resources.NotFoundException e) { 435 // Shouldn't happen, do nothing 436 } 437 } 438 } catch (PackageManager.NameNotFoundException e) { 439 // Did not find the package, do nothing 440 } 441 return attributionLabelMap; 442 } 443 444 /** 445 * Returns true if the app satisfies subattribution policies and supports it 446 */ isSubattributionSupported(String packageName, int uid)447 private boolean isSubattributionSupported(String packageName, int uid) { 448 try { 449 if (!isLocationProvider(packageName)) { 450 return false; 451 } 452 PackageManager userPkgManager = 453 getUserContext(UserHandle.getUserHandleForUid(uid)).getPackageManager(); 454 ApplicationInfo appInfo = userPkgManager.getApplicationInfoAsUser(packageName, 455 PackageManager.ApplicationInfoFlags.of(0), 456 UserHandle.getUserId(uid)); 457 if (appInfo != null) { 458 return appInfo.areAttributionsUserVisible(); 459 } 460 return false; 461 } catch (PackageManager.NameNotFoundException e) { 462 return false; 463 } 464 } 465 466 /** 467 * @param packageName 468 * @return If the package is location provider 469 */ isLocationProvider(String packageName)470 private boolean isLocationProvider(String packageName) { 471 return Objects.requireNonNull( 472 mContext.getSystemService(LocationManager.class)).isProviderPackage(packageName); 473 } 474 475 /** 476 * Get the raw usages from the system, and then parse out the ones that are not recent enough, 477 * determine which permission group each belongs in, and removes duplicates (if the same app 478 * uses multiple permissions of the same group). Stores the package name, attribution tag, user, 479 * running/recent info, if the usage is a phone call, per permission group. 480 * 481 * @param opNames a list of op names to get usage for 482 * @param deviceId which device to get op usage for 483 * @return A map of permission group -> list of usages that are recent or running 484 */ getOpUsagesByDevice(List<String> opNames, String deviceId)485 private Map<String, List<OpUsage>> getOpUsagesByDevice(List<String> opNames, String deviceId) { 486 List<AppOpsManager.PackageOps> ops; 487 try { 488 if (Flags.deviceAwarePermissionApisEnabled()) { 489 ops = mAppOpsManager.getPackagesForOps(opNames.toArray(new String[opNames.size()]), 490 deviceId); 491 } else if (!Objects.equals(deviceId, 492 VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)) { 493 Slog.w(LOG_TAG, 494 "device_aware_permission_apis_enabled flag not enabled when deviceId is " 495 + "not default"); 496 return Collections.emptyMap(); 497 } else { 498 ops = mAppOpsManager.getPackagesForOps(opNames.toArray(new String[opNames.size()])); 499 } 500 } catch (NullPointerException e) { 501 // older builds might not support all the app-ops requested 502 return Collections.emptyMap(); 503 } 504 505 long now = System.currentTimeMillis(); 506 long recentThreshold = getRecentThreshold(now); 507 long runningThreshold = getRunningThreshold(now); 508 int opFlags = OP_FLAGS_ALL_TRUSTED; 509 Map<String, Map<Integer, OpUsage>> usages = new ArrayMap<>(); 510 511 int numPkgOps = ops.size(); 512 for (int pkgOpNum = 0; pkgOpNum < numPkgOps; pkgOpNum++) { 513 AppOpsManager.PackageOps pkgOps = ops.get(pkgOpNum); 514 int uid = pkgOps.getUid(); 515 UserHandle user = UserHandle.getUserHandleForUid(uid); 516 String packageName = pkgOps.getPackageName(); 517 518 int numOpEntries = pkgOps.getOps().size(); 519 for (int opEntryNum = 0; opEntryNum < numOpEntries; opEntryNum++) { 520 AppOpsManager.OpEntry opEntry = pkgOps.getOps().get(opEntryNum); 521 String op = opEntry.getOpStr(); 522 List<String> attributionTags = 523 new ArrayList<>(opEntry.getAttributedOpEntries().keySet()); 524 525 526 int numAttrEntries = opEntry.getAttributedOpEntries().size(); 527 for (int attrOpEntryNum = 0; attrOpEntryNum < numAttrEntries; attrOpEntryNum++) { 528 String attributionTag = attributionTags.get(attrOpEntryNum); 529 AppOpsManager.AttributedOpEntry attrOpEntry = 530 opEntry.getAttributedOpEntries().get(attributionTag); 531 532 long lastAccessTime = attrOpEntry.getLastAccessTime(opFlags); 533 if (attrOpEntry.isRunning()) { 534 lastAccessTime = now; 535 } 536 537 if (lastAccessTime < recentThreshold && !attrOpEntry.isRunning()) { 538 continue; 539 } 540 541 boolean isRunning = attrOpEntry.isRunning() 542 || lastAccessTime >= runningThreshold; 543 544 OpUsage proxyUsage = null; 545 AppOpsManager.OpEventProxyInfo proxy = attrOpEntry.getLastProxyInfo(opFlags); 546 if (proxy != null && proxy.getPackageName() != null) { 547 proxyUsage = new OpUsage(proxy.getPackageName(), proxy.getAttributionTag(), 548 op, proxy.getUid(), lastAccessTime, isRunning, null); 549 } 550 551 String permGroupName = getGroupForOp(op); 552 OpUsage usage = new OpUsage(packageName, attributionTag, op, uid, 553 lastAccessTime, isRunning, proxyUsage); 554 555 Integer packageAttr = usage.getPackageIdHash(); 556 if (!usages.containsKey(permGroupName)) { 557 ArrayMap<Integer, OpUsage> map = new ArrayMap<>(); 558 map.put(packageAttr, usage); 559 usages.put(permGroupName, map); 560 } else { 561 Map<Integer, OpUsage> permGroupUsages = 562 usages.get(permGroupName); 563 if (!permGroupUsages.containsKey(packageAttr)) { 564 permGroupUsages.put(packageAttr, usage); 565 } else if (usage.lastAccessTime 566 > permGroupUsages.get(packageAttr).lastAccessTime) { 567 permGroupUsages.put(packageAttr, usage); 568 } 569 } 570 } 571 } 572 } 573 574 Map<String, List<OpUsage>> flattenedUsages = new ArrayMap<>(); 575 List<String> permGroups = new ArrayList<>(usages.keySet()); 576 for (int i = 0; i < permGroups.size(); i++) { 577 String permGroupName = permGroups.get(i); 578 flattenedUsages.put(permGroupName, new ArrayList<>(usages.get(permGroupName).values())); 579 } 580 return flattenedUsages; 581 } 582 formatLabelList(List<CharSequence> labels)583 private CharSequence formatLabelList(List<CharSequence> labels) { 584 return ListFormatter.getInstance().format(labels); 585 } 586 getUniqueUsagesWithLabels(String permGroup, List<OpUsage> usages)587 private ArrayMap<OpUsage, CharSequence> getUniqueUsagesWithLabels(String permGroup, 588 List<OpUsage> usages) { 589 ArrayMap<OpUsage, CharSequence> usagesAndLabels = new ArrayMap<>(); 590 591 if (usages == null || usages.isEmpty()) { 592 return usagesAndLabels; 593 } 594 595 ArrayMap<Integer, OpUsage> allUsages = new ArrayMap<>(); 596 // map of packageName and uid hash -> most recent non-proxy-related usage for that uid. 597 ArrayMap<Integer, OpUsage> mostRecentUsages = new ArrayMap<>(); 598 // set of all packages involved in a proxy usage 599 ArraySet<Integer> proxyPackages = new ArraySet<>(); 600 // map of usage -> list of proxy app labels 601 ArrayMap<OpUsage, ArrayList<CharSequence>> proxyLabels = new ArrayMap<>(); 602 // map of usage.proxy hash -> usage hash, telling us if a usage is a proxy 603 ArrayMap<Integer, OpUsage> proxies = new ArrayMap<>(); 604 605 for (int i = 0; i < usages.size(); i++) { 606 OpUsage usage = usages.get(i); 607 allUsages.put(usage.getPackageIdHash(), usage); 608 if (usage.proxy != null) { 609 proxies.put(usage.proxy.getPackageIdHash(), usage); 610 } 611 } 612 613 // find all possible end points for chains, and find the most recent of the rest of the uses 614 for (int usageNum = 0; usageNum < usages.size(); usageNum++) { 615 OpUsage usage = usages.get(usageNum); 616 if (usage == null) { 617 continue; 618 } 619 620 int usageAttr = usage.getPackageIdHash(); 621 // If this usage has a proxy, but is not a proxy, it is the end of a chain. 622 // TODO remove once camera converted 623 if (!proxies.containsKey(usageAttr) && usage.proxy != null 624 && !MICROPHONE.equals(permGroup)) { 625 proxyLabels.put(usage, new ArrayList<>()); 626 proxyPackages.add(usage.getPackageIdHash()); 627 } 628 // If this usage is not by the system, and is more recent than the next-most recent 629 // for it's uid and package name, save it. 630 int usageId = usage.getPackageIdHash(); 631 OpUsage lastMostRecent = mostRecentUsages.get(usageId); 632 if (shouldShowPackage(usage.packageName) && (lastMostRecent == null 633 || usage.lastAccessTime > lastMostRecent.lastAccessTime)) { 634 mostRecentUsages.put(usageId, usage); 635 } 636 } 637 638 // get all the proxy labels 639 for (int numStart = 0; numStart < proxyLabels.size(); numStart++) { 640 OpUsage start = proxyLabels.keyAt(numStart); 641 // Remove any non-proxy usage for the starting package 642 mostRecentUsages.remove(start.getPackageIdHash()); 643 OpUsage currentUsage = proxyLabels.keyAt(numStart); 644 ArrayList<CharSequence> proxyLabelList = proxyLabels.get(currentUsage); 645 if (currentUsage == null || proxyLabelList == null) { 646 continue; 647 } 648 int iterNum = 0; 649 int maxUsages = allUsages.size(); 650 while (currentUsage.proxy != null) { 651 652 if (allUsages.containsKey(currentUsage.proxy.getPackageIdHash())) { 653 currentUsage = allUsages.get(currentUsage.proxy.getPackageIdHash()); 654 } else { 655 // We are missing the proxy usage. This may be because it's a one-step trusted 656 // proxy. Check if we should show the proxy label, and show it, if so. 657 OpUsage proxy = currentUsage.proxy; 658 if (shouldShowPackage(proxy.packageName)) { 659 currentUsage = proxy; 660 // We've effectively added one usage, so increment the max number of usages 661 maxUsages++; 662 } else { 663 break; 664 } 665 } 666 667 if (currentUsage == null || iterNum == maxUsages 668 || currentUsage.getPackageIdHash() == start.getPackageIdHash()) { 669 // We have an invalid state, or a cycle, so break 670 break; 671 } 672 673 proxyPackages.add(currentUsage.getPackageIdHash()); 674 // Don't add an app label for the main app, or the system app 675 if (!currentUsage.packageName.equals(start.packageName) 676 && shouldShowPackage(currentUsage.packageName)) { 677 try { 678 PackageManager userPkgManager = 679 getUserContext(currentUsage.getUser()).getPackageManager(); 680 ApplicationInfo appInfo = userPkgManager.getApplicationInfo( 681 currentUsage.packageName, 0); 682 CharSequence appLabel = appInfo.loadLabel(userPkgManager); 683 // If we don't already have the app label add it 684 if (!proxyLabelList.contains(appLabel)) { 685 proxyLabelList.add(appLabel); 686 } 687 } catch (PackageManager.NameNotFoundException e) { 688 // Ignore 689 } 690 } 691 iterNum++; 692 } 693 694 // TODO ntmyren: remove this proxy logic once camera is converted to AttributionSource 695 // For now: don't add mic proxy usages 696 if (!MICROPHONE.equals(permGroup)) { 697 usagesAndLabels.put(start, 698 proxyLabelList.isEmpty() ? null : formatLabelList(proxyLabelList)); 699 } 700 } 701 702 synchronized (mAttributionChains) { 703 for (int i = 0; i < mAttributionChains.size(); i++) { 704 List<AccessChainLink> usageList = mAttributionChains.valueAt(i); 705 int lastVisible = usageList.size() - 1; 706 // TODO ntmyren: remove this mic code once camera is converted to AttributionSource 707 // if the list is empty or incomplete, do not show it. 708 if (usageList.isEmpty() || !usageList.get(lastVisible).isEnd() 709 || !usageList.get(0).isStart() 710 || !permGroup.equals(getGroupForOp(usageList.get(0).usage.op)) 711 || !MICROPHONE.equals(permGroup)) { 712 continue; 713 } 714 715 //TODO ntmyren: remove once camera etc. etc. 716 for (AccessChainLink link : usageList) { 717 proxyPackages.add(link.usage.getPackageIdHash()); 718 } 719 720 AccessChainLink start = usageList.get(0); 721 AccessChainLink lastVisibleLink = usageList.get(lastVisible); 722 while (lastVisible > 0 && !shouldShowPackage(lastVisibleLink.usage.packageName)) { 723 lastVisible--; 724 lastVisibleLink = usageList.get(lastVisible); 725 } 726 String proxyLabel = null; 727 if (!lastVisibleLink.usage.packageName.equals(start.usage.packageName)) { 728 try { 729 PackageManager userPkgManager = 730 getUserContext(lastVisibleLink.usage.getUser()).getPackageManager(); 731 ApplicationInfo appInfo = userPkgManager.getApplicationInfo( 732 lastVisibleLink.usage.packageName, 0); 733 proxyLabel = appInfo.loadLabel(userPkgManager).toString(); 734 } catch (PackageManager.NameNotFoundException e) { 735 // do nothing 736 } 737 } 738 usagesAndLabels.put(start.usage, proxyLabel); 739 } 740 } 741 742 for (int packageHash : mostRecentUsages.keySet()) { 743 if (!proxyPackages.contains(packageHash)) { 744 usagesAndLabels.put(mostRecentUsages.get(packageHash), null); 745 } 746 } 747 748 return usagesAndLabels; 749 } 750 shouldShowPackage(String packageName)751 private boolean shouldShowPackage(String packageName) { 752 return PermissionManager.shouldShowPackageForIndicatorCached(mContext, packageName); 753 } 754 755 /** 756 * Represents the usage of an App op by a particular package and attribution 757 */ 758 private static class OpUsage { 759 760 public final String packageName; 761 public final String attributionTag; 762 public final String op; 763 public final int uid; 764 public final long lastAccessTime; 765 public final OpUsage proxy; 766 public final boolean isRunning; 767 OpUsage(String packageName, String attributionTag, String op, int uid, long lastAccessTime, boolean isRunning, OpUsage proxy)768 OpUsage(String packageName, String attributionTag, String op, int uid, long lastAccessTime, 769 boolean isRunning, OpUsage proxy) { 770 this.packageName = packageName; 771 this.attributionTag = attributionTag; 772 this.op = op; 773 this.uid = uid; 774 this.lastAccessTime = lastAccessTime; 775 this.isRunning = isRunning; 776 this.proxy = proxy; 777 } 778 getUser()779 public UserHandle getUser() { 780 return UserHandle.getUserHandleForUid(uid); 781 } 782 getPackageIdHash()783 public int getPackageIdHash() { 784 return Objects.hash(packageName, uid); 785 } 786 787 @Override hashCode()788 public int hashCode() { 789 return Objects.hash(packageName, attributionTag, op, uid, lastAccessTime, isRunning); 790 } 791 792 @Override equals(Object obj)793 public boolean equals(Object obj) { 794 if (!(obj instanceof OpUsage)) { 795 return false; 796 } 797 OpUsage other = (OpUsage) obj; 798 return Objects.equals(packageName, other.packageName) && Objects.equals(attributionTag, 799 other.attributionTag) && Objects.equals(op, other.op) && uid == other.uid 800 && lastAccessTime == other.lastAccessTime && isRunning == other.isRunning; 801 } 802 } 803 804 private static class AccessChainLink { 805 public final OpUsage usage; 806 public final @AttributionFlags int flags; 807 AccessChainLink(String op, String packageName, String attributionTag, int uid, int flags)808 AccessChainLink(String op, String packageName, String attributionTag, int uid, 809 int flags) { 810 this.usage = new OpUsage(packageName, attributionTag, op, uid, 811 System.currentTimeMillis(), true, null); 812 this.flags = flags; 813 } 814 isEnd()815 public boolean isEnd() { 816 return (flags & ATTRIBUTION_FLAG_ACCESSOR) != 0; 817 } 818 isStart()819 public boolean isStart() { 820 return (flags & ATTRIBUTION_FLAG_RECEIVER) != 0; 821 } 822 823 @Override equals(Object obj)824 public boolean equals(Object obj) { 825 if (!(obj instanceof AccessChainLink)) { 826 return false; 827 } 828 AccessChainLink other = (AccessChainLink) obj; 829 return other.flags == flags && packageAndOpEquals(other.usage.op, 830 other.usage.packageName, other.usage.attributionTag, other.usage.uid); 831 } 832 packageAndOpEquals(String op, String packageName, String attributionTag, int uid)833 public boolean packageAndOpEquals(String op, String packageName, String attributionTag, 834 int uid) { 835 return Objects.equals(op, usage.op) && Objects.equals(packageName, usage.packageName) 836 && Objects.equals(attributionTag, usage.attributionTag) && uid == usage.uid; 837 } 838 } 839 } 840