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