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