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 179 @Override onOpActiveChanged(@onNull String op, int uid, @NonNull String packageName, boolean active)180 public void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName, 181 boolean active) { 182 // not part of an attribution chain. Do nothing 183 } 184 185 @Override onOpActiveChanged(@onNull String op, int uid, @NonNull String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags int attributionFlags, int attributionChainId)186 public void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName, 187 @Nullable String attributionTag, boolean active, @AttributionFlags int attributionFlags, 188 int attributionChainId) { 189 if (active) { 190 // Started callback handles these 191 return; 192 } 193 194 // if any link in the chain is finished, remove the chain. Then, find any other chains that 195 // contain this op/package/uid/tag combination, and remove them, as well. 196 // TODO ntmyren: be smarter about this 197 mAttributionChains.remove(attributionChainId); 198 int numChains = mAttributionChains.size(); 199 ArrayList<Integer> toRemove = new ArrayList<>(); 200 for (int i = 0; i < numChains; i++) { 201 int chainId = mAttributionChains.keyAt(i); 202 ArrayList<AccessChainLink> chain = mAttributionChains.valueAt(i); 203 int chainSize = chain.size(); 204 for (int j = 0; j < chainSize; j++) { 205 AccessChainLink link = chain.get(j); 206 if (link.packageAndOpEquals(op, packageName, attributionTag, uid)) { 207 toRemove.add(chainId); 208 break; 209 } 210 } 211 } 212 mAttributionChains.removeAll(toRemove); 213 } 214 215 @Override onOpStarted(int op, int uid, String packageName, String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result)216 public void onOpStarted(int op, int uid, String packageName, String attributionTag, 217 @AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result) { 218 // not part of an attribution chain. Do nothing 219 } 220 221 @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)222 public void onOpStarted(int op, int uid, String packageName, String attributionTag, 223 @AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result, 224 @StartedType int startedType, @AttributionFlags int attributionFlags, 225 int attributionChainId) { 226 if (startedType == START_TYPE_FAILED || attributionChainId == ATTRIBUTION_CHAIN_ID_NONE 227 || attributionFlags == ATTRIBUTION_FLAGS_NONE 228 || (attributionFlags & ATTRIBUTION_FLAG_TRUSTED) == 0) { 229 // If this is not a successful start, or it is not a chain, or it is untrusted, return 230 return; 231 } 232 addLinkToChainIfNotPresent(AppOpsManager.opToPublicName(op), packageName, uid, 233 attributionTag, attributionFlags, attributionChainId); 234 } 235 addLinkToChainIfNotPresent(String op, String packageName, int uid, String attributionTag, int attributionFlags, int attributionChainId)236 private void addLinkToChainIfNotPresent(String op, String packageName, int uid, 237 String attributionTag, int attributionFlags, int attributionChainId) { 238 239 ArrayList<AccessChainLink> currentChain = mAttributionChains.computeIfAbsent( 240 attributionChainId, k -> new ArrayList<>()); 241 AccessChainLink link = new AccessChainLink(op, packageName, attributionTag, uid, 242 attributionFlags); 243 244 if (currentChain.contains(link)) { 245 return; 246 } 247 248 int currSize = currentChain.size(); 249 if (currSize == 0 || link.isEnd() || !currentChain.get(currSize - 1).isEnd()) { 250 // if the list is empty, this link is the end, or the last link in the current chain 251 // isn't the end, add it to the end 252 currentChain.add(link); 253 } else if (link.isStart()) { 254 currentChain.add(0, link); 255 } else if (currentChain.get(currentChain.size() - 1).isEnd()) { 256 // we already have the end, and this is a mid node, so insert before the end 257 currentChain.add(currSize - 1, link); 258 } 259 } 260 261 /** 262 * @see PermissionManager.getIndicatorAppOpUsageData 263 */ getOpUsageData(boolean isMicMuted)264 public @NonNull List<PermGroupUsage> getOpUsageData(boolean isMicMuted) { 265 List<PermGroupUsage> usages = new ArrayList<>(); 266 267 if (!shouldShowIndicators()) { 268 return usages; 269 } 270 271 List<String> ops = new ArrayList<>(CAMERA_OPS); 272 if (shouldShowLocationIndicator()) { 273 ops.addAll(LOCATION_OPS); 274 } 275 if (!isMicMuted) { 276 ops.addAll(MIC_OPS); 277 } 278 279 Map<String, List<OpUsage>> rawUsages = getOpUsages(ops); 280 281 ArrayList<String> usedPermGroups = new ArrayList<>(rawUsages.keySet()); 282 283 // If we have a phone call, and a carrier privileged app using microphone, hide the 284 // phone call. 285 AudioManager audioManager = mContext.getSystemService(AudioManager.class); 286 boolean hasPhoneCall = usedPermGroups.contains(OPSTR_PHONE_CALL_CAMERA) 287 || usedPermGroups.contains(OPSTR_PHONE_CALL_MICROPHONE); 288 if (hasPhoneCall && usedPermGroups.contains(MICROPHONE) && audioManager.getMode() 289 == MODE_IN_COMMUNICATION) { 290 TelephonyManager telephonyManager = 291 mContext.getSystemService(TelephonyManager.class); 292 List<OpUsage> permUsages = rawUsages.get(MICROPHONE); 293 for (int usageNum = 0; usageNum < permUsages.size(); usageNum++) { 294 if (telephonyManager.checkCarrierPrivilegesForPackage( 295 permUsages.get(usageNum).packageName) 296 == CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { 297 usedPermGroups.remove(OPSTR_PHONE_CALL_CAMERA); 298 usedPermGroups.remove(OPSTR_PHONE_CALL_MICROPHONE); 299 } 300 } 301 } 302 303 for (int permGroupNum = 0; permGroupNum < usedPermGroups.size(); permGroupNum++) { 304 boolean isPhone = false; 305 String permGroup = usedPermGroups.get(permGroupNum); 306 307 ArrayMap<OpUsage, CharSequence> usagesWithLabels = 308 getUniqueUsagesWithLabels(rawUsages.get(permGroup)); 309 310 if (permGroup.equals(OPSTR_PHONE_CALL_MICROPHONE)) { 311 isPhone = true; 312 permGroup = MICROPHONE; 313 } else if (permGroup.equals(OPSTR_PHONE_CALL_CAMERA)) { 314 isPhone = true; 315 permGroup = CAMERA; 316 } 317 318 for (int usageNum = 0; usageNum < usagesWithLabels.size(); usageNum++) { 319 OpUsage usage = usagesWithLabels.keyAt(usageNum); 320 usages.add(new PermGroupUsage(usage.packageName, usage.uid, permGroup, 321 usage.lastAccessTime, usage.isRunning, isPhone, 322 usagesWithLabels.valueAt(usageNum))); 323 } 324 } 325 326 return usages; 327 } 328 329 /** 330 * Get the raw usages from the system, and then parse out the ones that are not recent enough, 331 * determine which permission group each belongs in, and removes duplicates (if the same app 332 * uses multiple permissions of the same group). Stores the package name, attribution tag, user, 333 * running/recent info, if the usage is a phone call, per permission group. 334 * 335 * @param opNames a list of op names to get usage for 336 * 337 * @return A map of permission group -> list of usages that are recent or running 338 */ getOpUsages(List<String> opNames)339 private Map<String, List<OpUsage>> getOpUsages(List<String> opNames) { 340 List<AppOpsManager.PackageOps> ops; 341 try { 342 ops = mAppOpsManager.getPackagesForOps(opNames.toArray(new String[opNames.size()])); 343 } catch (NullPointerException e) { 344 // older builds might not support all the app-ops requested 345 return Collections.emptyMap(); 346 } 347 348 long now = System.currentTimeMillis(); 349 long recentThreshold = getRecentThreshold(now); 350 long runningThreshold = getRunningThreshold(now); 351 int opFlags = OP_FLAGS_ALL_TRUSTED; 352 Map<String, Map<Integer, OpUsage>> usages = new ArrayMap<>(); 353 354 int numPkgOps = ops.size(); 355 for (int pkgOpNum = 0; pkgOpNum < numPkgOps; pkgOpNum++) { 356 AppOpsManager.PackageOps pkgOps = ops.get(pkgOpNum); 357 int uid = pkgOps.getUid(); 358 UserHandle user = UserHandle.getUserHandleForUid(uid); 359 String packageName = pkgOps.getPackageName(); 360 361 int numOpEntries = pkgOps.getOps().size(); 362 for (int opEntryNum = 0; opEntryNum < numOpEntries; opEntryNum++) { 363 AppOpsManager.OpEntry opEntry = pkgOps.getOps().get(opEntryNum); 364 String op = opEntry.getOpStr(); 365 List<String> attributionTags = 366 new ArrayList<>(opEntry.getAttributedOpEntries().keySet()); 367 368 int numAttrEntries = opEntry.getAttributedOpEntries().size(); 369 for (int attrOpEntryNum = 0; attrOpEntryNum < numAttrEntries; attrOpEntryNum++) { 370 String attributionTag = attributionTags.get(attrOpEntryNum); 371 AppOpsManager.AttributedOpEntry attrOpEntry = 372 opEntry.getAttributedOpEntries().get(attributionTag); 373 374 long lastAccessTime = attrOpEntry.getLastAccessTime(opFlags); 375 if (attrOpEntry.isRunning()) { 376 lastAccessTime = now; 377 } 378 379 if (lastAccessTime < recentThreshold && !attrOpEntry.isRunning()) { 380 continue; 381 } 382 383 boolean isRunning = attrOpEntry.isRunning() 384 || lastAccessTime >= runningThreshold; 385 386 OpUsage proxyUsage = null; 387 AppOpsManager.OpEventProxyInfo proxy = attrOpEntry.getLastProxyInfo(opFlags); 388 if (proxy != null && proxy.getPackageName() != null) { 389 proxyUsage = new OpUsage(proxy.getPackageName(), proxy.getAttributionTag(), 390 op, proxy.getUid(), lastAccessTime, isRunning, null); 391 } 392 393 String permGroupName = getGroupForOp(op); 394 OpUsage usage = new OpUsage(packageName, attributionTag, op, uid, 395 lastAccessTime, isRunning, proxyUsage); 396 397 Integer packageAttr = usage.getPackageIdHash(); 398 if (!usages.containsKey(permGroupName)) { 399 ArrayMap<Integer, OpUsage> map = new ArrayMap<>(); 400 map.put(packageAttr, usage); 401 usages.put(permGroupName, map); 402 } else { 403 Map<Integer, OpUsage> permGroupUsages = 404 usages.get(permGroupName); 405 if (!permGroupUsages.containsKey(packageAttr)) { 406 permGroupUsages.put(packageAttr, usage); 407 } else if (usage.lastAccessTime 408 > permGroupUsages.get(packageAttr).lastAccessTime) { 409 permGroupUsages.put(packageAttr, usage); 410 } 411 } 412 } 413 } 414 } 415 416 Map<String, List<OpUsage>> flattenedUsages = new ArrayMap<>(); 417 List<String> permGroups = new ArrayList<>(usages.keySet()); 418 for (int i = 0; i < permGroups.size(); i++) { 419 String permGroupName = permGroups.get(i); 420 flattenedUsages.put(permGroupName, new ArrayList<>(usages.get(permGroupName).values())); 421 } 422 return flattenedUsages; 423 } 424 formatLabelList(List<CharSequence> labels)425 private CharSequence formatLabelList(List<CharSequence> labels) { 426 return ListFormatter.getInstance().format(labels); 427 } 428 getUniqueUsagesWithLabels(List<OpUsage> usages)429 private ArrayMap<OpUsage, CharSequence> getUniqueUsagesWithLabels(List<OpUsage> usages) { 430 ArrayMap<OpUsage, CharSequence> usagesAndLabels = new ArrayMap<>(); 431 432 if (usages == null || usages.isEmpty()) { 433 return usagesAndLabels; 434 } 435 436 ArrayMap<Integer, OpUsage> allUsages = new ArrayMap<>(); 437 // map of packageName and uid hash -> most recent non-proxy-related usage for that uid. 438 ArrayMap<Integer, OpUsage> mostRecentUsages = new ArrayMap<>(); 439 // set of all packages involved in a proxy usage 440 ArraySet<Integer> proxyPackages = new ArraySet<>(); 441 // map of usage -> list of proxy app labels 442 ArrayMap<OpUsage, ArrayList<CharSequence>> proxyLabels = new ArrayMap<>(); 443 // map of usage.proxy hash -> usage hash, telling us if a usage is a proxy 444 ArrayMap<Integer, OpUsage> proxies = new ArrayMap<>(); 445 for (int i = 0; i < usages.size(); i++) { 446 OpUsage usage = usages.get(i); 447 allUsages.put(usage.getPackageIdHash(), usage); 448 if (usage.proxy != null) { 449 proxies.put(usage.proxy.getPackageIdHash(), usage); 450 } 451 } 452 453 // find all possible end points for chains, and find the most recent of the rest of the uses 454 for (int usageNum = 0; usageNum < usages.size(); usageNum++) { 455 OpUsage usage = usages.get(usageNum); 456 if (usage == null) { 457 continue; 458 } 459 460 int usageAttr = usage.getPackageIdHash(); 461 // If this usage has a proxy, but is not a proxy, it is the end of a chain. 462 // TODO remove once camera converted 463 if (!proxies.containsKey(usageAttr) && usage.proxy != null 464 && !usage.op.equals(OPSTR_RECORD_AUDIO)) { 465 proxyLabels.put(usage, new ArrayList<>()); 466 proxyPackages.add(usage.getPackageIdHash()); 467 } 468 // If this usage is not by the system, and is more recent than the next-most recent 469 // for it's uid and package name, save it. 470 int usageId = usage.getPackageIdHash(); 471 OpUsage lastMostRecent = mostRecentUsages.get(usageId); 472 if (shouldShowPackage(usage.packageName) && (lastMostRecent == null 473 || usage.lastAccessTime > lastMostRecent.lastAccessTime)) { 474 mostRecentUsages.put(usageId, usage); 475 } 476 } 477 478 // get all the proxy labels 479 for (int numStart = 0; numStart < proxyLabels.size(); numStart++) { 480 OpUsage start = proxyLabels.keyAt(numStart); 481 // Remove any non-proxy usage for the starting package 482 mostRecentUsages.remove(start.getPackageIdHash()); 483 OpUsage currentUsage = proxyLabels.keyAt(numStart); 484 ArrayList<CharSequence> proxyLabelList = proxyLabels.get(currentUsage); 485 if (currentUsage == null || proxyLabelList == null) { 486 continue; 487 } 488 int iterNum = 0; 489 int maxUsages = allUsages.size(); 490 while (currentUsage.proxy != null) { 491 492 if (allUsages.containsKey(currentUsage.proxy.getPackageIdHash())) { 493 currentUsage = allUsages.get(currentUsage.proxy.getPackageIdHash()); 494 } else { 495 // We are missing the proxy usage. This may be because it's a one-step trusted 496 // proxy. Check if we should show the proxy label, and show it, if so. 497 OpUsage proxy = currentUsage.proxy; 498 if (shouldShowPackage(proxy.packageName)) { 499 currentUsage = proxy; 500 // We've effectively added one usage, so increment the max number of usages 501 maxUsages++; 502 } else { 503 break; 504 } 505 } 506 507 if (currentUsage == null || iterNum == maxUsages 508 || currentUsage.getPackageIdHash() == start.getPackageIdHash()) { 509 // We have an invalid state, or a cycle, so break 510 break; 511 } 512 513 proxyPackages.add(currentUsage.getPackageIdHash()); 514 // Don't add an app label for the main app, or the system app 515 if (!currentUsage.packageName.equals(start.packageName) 516 && shouldShowPackage(currentUsage.packageName)) { 517 try { 518 PackageManager userPkgManager = 519 getUserContext(currentUsage.getUser()).getPackageManager(); 520 ApplicationInfo appInfo = userPkgManager.getApplicationInfo( 521 currentUsage.packageName, 0); 522 CharSequence appLabel = appInfo.loadLabel(userPkgManager); 523 // If we don't already have the app label add it 524 if (!proxyLabelList.contains(appLabel)) { 525 proxyLabelList.add(appLabel); 526 } 527 } catch (PackageManager.NameNotFoundException e) { 528 // Ignore 529 } 530 } 531 iterNum++; 532 } 533 534 // TODO ntmyren: remove this proxy logic once camera is converted to AttributionSource 535 // For now: don't add mic proxy usages 536 if (!start.op.equals(OPSTR_RECORD_AUDIO)) { 537 usagesAndLabels.put(start, 538 proxyLabelList.isEmpty() ? null : formatLabelList(proxyLabelList)); 539 } 540 } 541 542 for (int i = 0; i < mAttributionChains.size(); i++) { 543 List<AccessChainLink> usageList = mAttributionChains.valueAt(i); 544 int lastVisible = usageList.size() - 1; 545 // TODO ntmyren: remove this mic code once camera is converted to AttributionSource 546 // if the list is empty or incomplete, do not show it. 547 if (usageList.isEmpty() || !usageList.get(lastVisible).isEnd() 548 || !usageList.get(0).isStart() 549 || !usageList.get(lastVisible).usage.op.equals(OPSTR_RECORD_AUDIO)) { 550 continue; 551 } 552 553 //TODO ntmyren: remove once camera etc. etc. 554 for (AccessChainLink link: usageList) { 555 proxyPackages.add(link.usage.getPackageIdHash()); 556 } 557 558 AccessChainLink start = usageList.get(0); 559 AccessChainLink lastVisibleLink = usageList.get(lastVisible); 560 while (lastVisible > 0 && !shouldShowPackage(lastVisibleLink.usage.packageName)) { 561 lastVisible--; 562 lastVisibleLink = usageList.get(lastVisible); 563 } 564 String proxyLabel = null; 565 if (!lastVisibleLink.usage.packageName.equals(start.usage.packageName)) { 566 try { 567 PackageManager userPkgManager = 568 getUserContext(lastVisibleLink.usage.getUser()).getPackageManager(); 569 ApplicationInfo appInfo = userPkgManager.getApplicationInfo( 570 lastVisibleLink.usage.packageName, 0); 571 proxyLabel = appInfo.loadLabel(userPkgManager).toString(); 572 } catch (PackageManager.NameNotFoundException e) { 573 // do nothing 574 } 575 576 } 577 usagesAndLabels.put(start.usage, proxyLabel); 578 } 579 580 for (int packageHash : mostRecentUsages.keySet()) { 581 if (!proxyPackages.contains(packageHash)) { 582 usagesAndLabels.put(mostRecentUsages.get(packageHash), null); 583 } 584 } 585 586 return usagesAndLabels; 587 } 588 shouldShowPackage(String packageName)589 private boolean shouldShowPackage(String packageName) { 590 return PermissionManager.shouldShowPackageForIndicatorCached(mContext, packageName); 591 } 592 593 /** 594 * Represents the usage of an App op by a particular package and attribution 595 */ 596 private static class OpUsage { 597 598 public final String packageName; 599 public final String attributionTag; 600 public final String op; 601 public final int uid; 602 public final long lastAccessTime; 603 public final OpUsage proxy; 604 public final boolean isRunning; 605 OpUsage(String packageName, String attributionTag, String op, int uid, long lastAccessTime, boolean isRunning, OpUsage proxy)606 OpUsage(String packageName, String attributionTag, String op, int uid, long lastAccessTime, 607 boolean isRunning, OpUsage proxy) { 608 this.packageName = packageName; 609 this.attributionTag = attributionTag; 610 this.op = op; 611 this.uid = uid; 612 this.lastAccessTime = lastAccessTime; 613 this.isRunning = isRunning; 614 this.proxy = proxy; 615 } 616 getUser()617 public UserHandle getUser() { 618 return UserHandle.getUserHandleForUid(uid); 619 } 620 getPackageIdHash()621 public int getPackageIdHash() { 622 return Objects.hash(packageName, uid); 623 } 624 625 @Override hashCode()626 public int hashCode() { 627 return Objects.hash(packageName, attributionTag, op, uid, lastAccessTime, isRunning); 628 } 629 630 @Override equals(Object obj)631 public boolean equals(Object obj) { 632 if (!(obj instanceof OpUsage)) { 633 return false; 634 } 635 OpUsage other = (OpUsage) obj; 636 return Objects.equals(packageName, other.packageName) && Objects.equals(attributionTag, 637 other.attributionTag) && Objects.equals(op, other.op) && uid == other.uid 638 && lastAccessTime == other.lastAccessTime && isRunning == other.isRunning; 639 } 640 } 641 642 private static class AccessChainLink { 643 public final OpUsage usage; 644 public final @AttributionFlags int flags; 645 AccessChainLink(String op, String packageName, String attributionTag, int uid, int flags)646 AccessChainLink(String op, String packageName, String attributionTag, int uid, 647 int flags) { 648 this.usage = new OpUsage(packageName, attributionTag, op, uid, 649 System.currentTimeMillis(), true, null); 650 this.flags = flags; 651 } 652 isEnd()653 public boolean isEnd() { 654 return (flags & ATTRIBUTION_FLAG_ACCESSOR) != 0; 655 } 656 isStart()657 public boolean isStart() { 658 return (flags & ATTRIBUTION_FLAG_RECEIVER) != 0; 659 } 660 661 @Override equals(Object obj)662 public boolean equals(Object obj) { 663 if (!(obj instanceof AccessChainLink)) { 664 return false; 665 } 666 AccessChainLink other = (AccessChainLink) obj; 667 return other.flags == flags && packageAndOpEquals(other.usage.op, 668 other.usage.packageName, other.usage.attributionTag, other.usage.uid); 669 } 670 packageAndOpEquals(String op, String packageName, String attributionTag, int uid)671 public boolean packageAndOpEquals(String op, String packageName, String attributionTag, 672 int uid) { 673 return Objects.equals(op, usage.op) && Objects.equals(packageName, usage.packageName) 674 && Objects.equals(attributionTag, usage.attributionTag) && uid == usage.uid; 675 } 676 } 677 } 678