• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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