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 378 if (mVirtualDeviceManager != null) { 379 List<VirtualDevice> virtualDevices = mVirtualDeviceManager.getVirtualDevices(); 380 ArraySet<String> persistentDeviceIds = new ArraySet<>(); 381 382 for (int num = 0; num < virtualDevices.size(); num++) { 383 persistentDeviceIds.add(virtualDevices.get(num).getPersistentDeviceId()); 384 } 385 persistentDeviceIds.add(VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); 386 387 for (int index = 0; index < persistentDeviceIds.size(); index++) { 388 allUsages.addAll( 389 getOpUsageDataByDevice(includeMicrophoneUsage, 390 persistentDeviceIds.valueAt(index))); 391 } 392 } 393 return allUsages; 394 } 395 396 updateSubattributionLabelsMap(List<OpUsage> usages, ArrayMap<String, Map<String, String>> subAttributionLabelsMap)397 private void updateSubattributionLabelsMap(List<OpUsage> usages, 398 ArrayMap<String, Map<String, String>> subAttributionLabelsMap) { 399 if (usages == null || usages.isEmpty()) { 400 return; 401 } 402 for (OpUsage usage : usages) { 403 if (usage.attributionTag != null && !subAttributionLabelsMap.containsKey( 404 usage.packageName)) { 405 subAttributionLabelsMap.put(usage.packageName, 406 getSubattributionLabelsForPackage(usage.packageName, usage.uid)); 407 } 408 } 409 } 410 411 /** 412 * Query attribution labels for a package 413 * 414 * @param packageName 415 * @param uid 416 * @return map of attribution tag -> attribution labels for a package 417 */ getSubattributionLabelsForPackage(String packageName, int uid)418 private ArrayMap<String, String> getSubattributionLabelsForPackage(String packageName, 419 int uid) { 420 ArrayMap<String, String> attributionLabelMap = new ArrayMap<>(); 421 UserHandle user = UserHandle.getUserHandleForUid(uid); 422 try { 423 if (!isSubattributionSupported(packageName, uid)) { 424 return attributionLabelMap; 425 } 426 Context userContext = getUserContext(user); 427 PackageInfo packageInfo = userContext.getPackageManager().getPackageInfo( 428 packageName, 429 PackageManager.PackageInfoFlags.of( 430 PackageManager.GET_PERMISSIONS | PackageManager.GET_ATTRIBUTIONS_LONG)); 431 Context pkgContext = userContext.createPackageContext(packageInfo.packageName, 0); 432 for (Attribution attribution : packageInfo.attributions) { 433 try { 434 String resourceForLabel = pkgContext.getString(attribution.getLabel()); 435 attributionLabelMap.put(attribution.getTag(), resourceForLabel); 436 } catch (Resources.NotFoundException e) { 437 // Shouldn't happen, do nothing 438 } 439 } 440 } catch (PackageManager.NameNotFoundException e) { 441 // Did not find the package, do nothing 442 } 443 return attributionLabelMap; 444 } 445 446 /** 447 * Returns true if the app satisfies subattribution policies and supports it 448 */ isSubattributionSupported(String packageName, int uid)449 private boolean isSubattributionSupported(String packageName, int uid) { 450 try { 451 if (!isLocationProvider(packageName)) { 452 return false; 453 } 454 PackageManager userPkgManager = 455 getUserContext(UserHandle.getUserHandleForUid(uid)).getPackageManager(); 456 ApplicationInfo appInfo = userPkgManager.getApplicationInfoAsUser(packageName, 457 PackageManager.ApplicationInfoFlags.of(0), 458 UserHandle.getUserId(uid)); 459 if (appInfo != null) { 460 return appInfo.areAttributionsUserVisible(); 461 } 462 return false; 463 } catch (PackageManager.NameNotFoundException e) { 464 return false; 465 } 466 } 467 468 /** 469 * @param packageName 470 * @return If the package is location provider 471 */ isLocationProvider(String packageName)472 private boolean isLocationProvider(String packageName) { 473 return Objects.requireNonNull( 474 mContext.getSystemService(LocationManager.class)).isProviderPackage(packageName); 475 } 476 477 /** 478 * Get the raw usages from the system, and then parse out the ones that are not recent enough, 479 * determine which permission group each belongs in, and removes duplicates (if the same app 480 * uses multiple permissions of the same group). Stores the package name, attribution tag, user, 481 * running/recent info, if the usage is a phone call, per permission group. 482 * 483 * @param opNames a list of op names to get usage for 484 * @param deviceId which device to get op usage for 485 * @return A map of permission group -> list of usages that are recent or running 486 */ getOpUsagesByDevice(List<String> opNames, String deviceId)487 private Map<String, List<OpUsage>> getOpUsagesByDevice(List<String> opNames, String deviceId) { 488 List<AppOpsManager.PackageOps> ops; 489 try { 490 if (Flags.deviceAwarePermissionApisEnabled()) { 491 ops = mAppOpsManager.getPackagesForOps(opNames.toArray(new String[opNames.size()]), 492 deviceId); 493 } else if (!Objects.equals(deviceId, 494 VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)) { 495 Slog.w(LOG_TAG, 496 "device_aware_permission_apis_enabled flag not enabled when deviceId is " 497 + "not default"); 498 return Collections.emptyMap(); 499 } else { 500 ops = mAppOpsManager.getPackagesForOps(opNames.toArray(new String[opNames.size()])); 501 } 502 } catch (NullPointerException e) { 503 // older builds might not support all the app-ops requested 504 return Collections.emptyMap(); 505 } 506 507 long now = System.currentTimeMillis(); 508 long recentThreshold = getRecentThreshold(now); 509 long runningThreshold = getRunningThreshold(now); 510 int opFlags = OP_FLAGS_ALL_TRUSTED; 511 Map<String, Map<Integer, OpUsage>> usages = new ArrayMap<>(); 512 513 int numPkgOps = ops.size(); 514 for (int pkgOpNum = 0; pkgOpNum < numPkgOps; pkgOpNum++) { 515 AppOpsManager.PackageOps pkgOps = ops.get(pkgOpNum); 516 int uid = pkgOps.getUid(); 517 UserHandle user = UserHandle.getUserHandleForUid(uid); 518 String packageName = pkgOps.getPackageName(); 519 520 int numOpEntries = pkgOps.getOps().size(); 521 for (int opEntryNum = 0; opEntryNum < numOpEntries; opEntryNum++) { 522 AppOpsManager.OpEntry opEntry = pkgOps.getOps().get(opEntryNum); 523 String op = opEntry.getOpStr(); 524 List<String> attributionTags = 525 new ArrayList<>(opEntry.getAttributedOpEntries().keySet()); 526 527 528 int numAttrEntries = opEntry.getAttributedOpEntries().size(); 529 for (int attrOpEntryNum = 0; attrOpEntryNum < numAttrEntries; attrOpEntryNum++) { 530 String attributionTag = attributionTags.get(attrOpEntryNum); 531 AppOpsManager.AttributedOpEntry attrOpEntry = 532 opEntry.getAttributedOpEntries().get(attributionTag); 533 534 long lastAccessTime = attrOpEntry.getLastAccessTime(opFlags); 535 if (attrOpEntry.isRunning()) { 536 lastAccessTime = now; 537 } 538 539 if (lastAccessTime < recentThreshold && !attrOpEntry.isRunning()) { 540 continue; 541 } 542 543 boolean isRunning = attrOpEntry.isRunning() 544 || lastAccessTime >= runningThreshold; 545 546 OpUsage proxyUsage = null; 547 AppOpsManager.OpEventProxyInfo proxy = attrOpEntry.getLastProxyInfo(opFlags); 548 if (proxy != null && proxy.getPackageName() != null) { 549 proxyUsage = new OpUsage(proxy.getPackageName(), proxy.getAttributionTag(), 550 op, proxy.getUid(), lastAccessTime, isRunning, null); 551 } 552 553 String permGroupName = getGroupForOp(op); 554 OpUsage usage = new OpUsage(packageName, attributionTag, op, uid, 555 lastAccessTime, isRunning, proxyUsage); 556 557 Integer packageAttr = usage.getPackageIdHash(); 558 if (!usages.containsKey(permGroupName)) { 559 ArrayMap<Integer, OpUsage> map = new ArrayMap<>(); 560 map.put(packageAttr, usage); 561 usages.put(permGroupName, map); 562 } else { 563 Map<Integer, OpUsage> permGroupUsages = 564 usages.get(permGroupName); 565 if (!permGroupUsages.containsKey(packageAttr)) { 566 permGroupUsages.put(packageAttr, usage); 567 } else if (usage.lastAccessTime 568 > permGroupUsages.get(packageAttr).lastAccessTime) { 569 permGroupUsages.put(packageAttr, usage); 570 } 571 } 572 } 573 } 574 } 575 576 Map<String, List<OpUsage>> flattenedUsages = new ArrayMap<>(); 577 List<String> permGroups = new ArrayList<>(usages.keySet()); 578 for (int i = 0; i < permGroups.size(); i++) { 579 String permGroupName = permGroups.get(i); 580 flattenedUsages.put(permGroupName, new ArrayList<>(usages.get(permGroupName).values())); 581 } 582 return flattenedUsages; 583 } 584 formatLabelList(List<CharSequence> labels)585 private CharSequence formatLabelList(List<CharSequence> labels) { 586 return ListFormatter.getInstance().format(labels); 587 } 588 getUniqueUsagesWithLabels(String permGroup, List<OpUsage> usages)589 private ArrayMap<OpUsage, CharSequence> getUniqueUsagesWithLabels(String permGroup, 590 List<OpUsage> usages) { 591 ArrayMap<OpUsage, CharSequence> usagesAndLabels = new ArrayMap<>(); 592 593 if (usages == null || usages.isEmpty()) { 594 return usagesAndLabels; 595 } 596 597 ArrayMap<Integer, OpUsage> allUsages = new ArrayMap<>(); 598 // map of packageName and uid hash -> most recent non-proxy-related usage for that uid. 599 ArrayMap<Integer, OpUsage> mostRecentUsages = new ArrayMap<>(); 600 // set of all packages involved in a proxy usage 601 ArraySet<Integer> proxyPackages = new ArraySet<>(); 602 // map of usage -> list of proxy app labels 603 ArrayMap<OpUsage, ArrayList<CharSequence>> proxyLabels = new ArrayMap<>(); 604 // map of usage.proxy hash -> usage hash, telling us if a usage is a proxy 605 ArrayMap<Integer, OpUsage> proxies = new ArrayMap<>(); 606 607 for (int i = 0; i < usages.size(); i++) { 608 OpUsage usage = usages.get(i); 609 allUsages.put(usage.getPackageIdHash(), usage); 610 if (usage.proxy != null) { 611 proxies.put(usage.proxy.getPackageIdHash(), usage); 612 } 613 } 614 615 // find all possible end points for chains, and find the most recent of the rest of the uses 616 for (int usageNum = 0; usageNum < usages.size(); usageNum++) { 617 OpUsage usage = usages.get(usageNum); 618 if (usage == null) { 619 continue; 620 } 621 622 int usageAttr = usage.getPackageIdHash(); 623 // If this usage has a proxy, but is not a proxy, it is the end of a chain. 624 // TODO remove once camera converted 625 if (!proxies.containsKey(usageAttr) && usage.proxy != null 626 && !MICROPHONE.equals(permGroup)) { 627 proxyLabels.put(usage, new ArrayList<>()); 628 proxyPackages.add(usage.getPackageIdHash()); 629 } 630 // If this usage is not by the system, and is more recent than the next-most recent 631 // for it's uid and package name, save it. 632 int usageId = usage.getPackageIdHash(); 633 OpUsage lastMostRecent = mostRecentUsages.get(usageId); 634 if (shouldShowPackage(usage.packageName) && (lastMostRecent == null 635 || usage.lastAccessTime > lastMostRecent.lastAccessTime)) { 636 mostRecentUsages.put(usageId, usage); 637 } 638 } 639 640 // get all the proxy labels 641 for (int numStart = 0; numStart < proxyLabels.size(); numStart++) { 642 OpUsage start = proxyLabels.keyAt(numStart); 643 // Remove any non-proxy usage for the starting package 644 mostRecentUsages.remove(start.getPackageIdHash()); 645 OpUsage currentUsage = proxyLabels.keyAt(numStart); 646 ArrayList<CharSequence> proxyLabelList = proxyLabels.get(currentUsage); 647 if (currentUsage == null || proxyLabelList == null) { 648 continue; 649 } 650 int iterNum = 0; 651 int maxUsages = allUsages.size(); 652 while (currentUsage.proxy != null) { 653 654 if (allUsages.containsKey(currentUsage.proxy.getPackageIdHash())) { 655 currentUsage = allUsages.get(currentUsage.proxy.getPackageIdHash()); 656 } else { 657 // We are missing the proxy usage. This may be because it's a one-step trusted 658 // proxy. Check if we should show the proxy label, and show it, if so. 659 OpUsage proxy = currentUsage.proxy; 660 if (shouldShowPackage(proxy.packageName)) { 661 currentUsage = proxy; 662 // We've effectively added one usage, so increment the max number of usages 663 maxUsages++; 664 } else { 665 break; 666 } 667 } 668 669 if (currentUsage == null || iterNum == maxUsages 670 || currentUsage.getPackageIdHash() == start.getPackageIdHash()) { 671 // We have an invalid state, or a cycle, so break 672 break; 673 } 674 675 proxyPackages.add(currentUsage.getPackageIdHash()); 676 // Don't add an app label for the main app, or the system app 677 if (!currentUsage.packageName.equals(start.packageName) 678 && shouldShowPackage(currentUsage.packageName)) { 679 try { 680 PackageManager userPkgManager = 681 getUserContext(currentUsage.getUser()).getPackageManager(); 682 ApplicationInfo appInfo = userPkgManager.getApplicationInfo( 683 currentUsage.packageName, 0); 684 CharSequence appLabel = appInfo.loadLabel(userPkgManager); 685 // If we don't already have the app label add it 686 if (!proxyLabelList.contains(appLabel)) { 687 proxyLabelList.add(appLabel); 688 } 689 } catch (PackageManager.NameNotFoundException e) { 690 // Ignore 691 } 692 } 693 iterNum++; 694 } 695 696 // TODO ntmyren: remove this proxy logic once camera is converted to AttributionSource 697 // For now: don't add mic proxy usages 698 if (!MICROPHONE.equals(permGroup)) { 699 usagesAndLabels.put(start, 700 proxyLabelList.isEmpty() ? null : formatLabelList(proxyLabelList)); 701 } 702 } 703 704 synchronized (mAttributionChains) { 705 for (int i = 0; i < mAttributionChains.size(); i++) { 706 List<AccessChainLink> usageList = mAttributionChains.valueAt(i); 707 int lastVisible = usageList.size() - 1; 708 // TODO ntmyren: remove this mic code once camera is converted to AttributionSource 709 // if the list is empty or incomplete, do not show it. 710 if (usageList.isEmpty() || !usageList.get(lastVisible).isEnd() 711 || !usageList.get(0).isStart() 712 || !permGroup.equals(getGroupForOp(usageList.get(0).usage.op)) 713 || !MICROPHONE.equals(permGroup)) { 714 continue; 715 } 716 717 //TODO ntmyren: remove once camera etc. etc. 718 for (AccessChainLink link : usageList) { 719 proxyPackages.add(link.usage.getPackageIdHash()); 720 } 721 722 AccessChainLink start = usageList.get(0); 723 AccessChainLink lastVisibleLink = usageList.get(lastVisible); 724 while (lastVisible > 0 && !shouldShowPackage(lastVisibleLink.usage.packageName)) { 725 lastVisible--; 726 lastVisibleLink = usageList.get(lastVisible); 727 } 728 String proxyLabel = null; 729 if (!lastVisibleLink.usage.packageName.equals(start.usage.packageName)) { 730 try { 731 PackageManager userPkgManager = 732 getUserContext(lastVisibleLink.usage.getUser()).getPackageManager(); 733 ApplicationInfo appInfo = userPkgManager.getApplicationInfo( 734 lastVisibleLink.usage.packageName, 0); 735 proxyLabel = appInfo.loadLabel(userPkgManager).toString(); 736 } catch (PackageManager.NameNotFoundException e) { 737 // do nothing 738 } 739 } 740 usagesAndLabels.put(start.usage, proxyLabel); 741 } 742 } 743 744 for (int packageHash : mostRecentUsages.keySet()) { 745 if (!proxyPackages.contains(packageHash)) { 746 usagesAndLabels.put(mostRecentUsages.get(packageHash), null); 747 } 748 } 749 750 return usagesAndLabels; 751 } 752 shouldShowPackage(String packageName)753 private boolean shouldShowPackage(String packageName) { 754 return PermissionManager.shouldShowPackageForIndicatorCached(mContext, packageName); 755 } 756 757 /** 758 * Represents the usage of an App op by a particular package and attribution 759 */ 760 private static class OpUsage { 761 762 public final String packageName; 763 public final String attributionTag; 764 public final String op; 765 public final int uid; 766 public final long lastAccessTime; 767 public final OpUsage proxy; 768 public final boolean isRunning; 769 OpUsage(String packageName, String attributionTag, String op, int uid, long lastAccessTime, boolean isRunning, OpUsage proxy)770 OpUsage(String packageName, String attributionTag, String op, int uid, long lastAccessTime, 771 boolean isRunning, OpUsage proxy) { 772 this.packageName = packageName; 773 this.attributionTag = attributionTag; 774 this.op = op; 775 this.uid = uid; 776 this.lastAccessTime = lastAccessTime; 777 this.isRunning = isRunning; 778 this.proxy = proxy; 779 } 780 getUser()781 public UserHandle getUser() { 782 return UserHandle.getUserHandleForUid(uid); 783 } 784 getPackageIdHash()785 public int getPackageIdHash() { 786 return Objects.hash(packageName, uid); 787 } 788 789 @Override hashCode()790 public int hashCode() { 791 return Objects.hash(packageName, attributionTag, op, uid, lastAccessTime, isRunning); 792 } 793 794 @Override equals(Object obj)795 public boolean equals(Object obj) { 796 if (!(obj instanceof OpUsage)) { 797 return false; 798 } 799 OpUsage other = (OpUsage) obj; 800 return Objects.equals(packageName, other.packageName) && Objects.equals(attributionTag, 801 other.attributionTag) && Objects.equals(op, other.op) && uid == other.uid 802 && lastAccessTime == other.lastAccessTime && isRunning == other.isRunning; 803 } 804 } 805 806 private static class AccessChainLink { 807 public final OpUsage usage; 808 public final @AttributionFlags int flags; 809 AccessChainLink(String op, String packageName, String attributionTag, int uid, int flags)810 AccessChainLink(String op, String packageName, String attributionTag, int uid, 811 int flags) { 812 this.usage = new OpUsage(packageName, attributionTag, op, uid, 813 System.currentTimeMillis(), true, null); 814 this.flags = flags; 815 } 816 isEnd()817 public boolean isEnd() { 818 return (flags & ATTRIBUTION_FLAG_ACCESSOR) != 0; 819 } 820 isStart()821 public boolean isStart() { 822 return (flags & ATTRIBUTION_FLAG_RECEIVER) != 0; 823 } 824 825 @Override equals(Object obj)826 public boolean equals(Object obj) { 827 if (!(obj instanceof AccessChainLink)) { 828 return false; 829 } 830 AccessChainLink other = (AccessChainLink) obj; 831 return other.flags == flags && packageAndOpEquals(other.usage.op, 832 other.usage.packageName, other.usage.attributionTag, other.usage.uid); 833 } 834 packageAndOpEquals(String op, String packageName, String attributionTag, int uid)835 public boolean packageAndOpEquals(String op, String packageName, String attributionTag, 836 int uid) { 837 return Objects.equals(op, usage.op) && Objects.equals(packageName, usage.packageName) 838 && Objects.equals(attributionTag, usage.attributionTag) && uid == usage.uid; 839 } 840 } 841 } 842