• 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 com.android.permissioncontroller.permission.model;
18 
19 import static android.Manifest.permission_group.MICROPHONE;
20 
21 import android.Manifest;
22 import android.app.AppOpsManager;
23 import android.app.AppOpsManager.AttributedHistoricalOps;
24 import android.app.AppOpsManager.AttributedOpEntry;
25 import android.app.AppOpsManager.HistoricalOp;
26 import android.app.AppOpsManager.HistoricalPackageOps;
27 import android.app.AppOpsManager.OpEntry;
28 import android.app.AppOpsManager.OpEventProxyInfo;
29 import android.app.AppOpsManager.PackageOps;
30 import android.content.pm.Attribution;
31 import android.media.AudioRecordingConfiguration;
32 import android.os.Build;
33 
34 import androidx.annotation.NonNull;
35 import androidx.annotation.Nullable;
36 import androidx.annotation.RequiresApi;
37 
38 import com.android.permissioncontroller.permission.model.legacy.PermissionApps.PermissionApp;
39 
40 import java.util.ArrayList;
41 import java.util.HashMap;
42 import java.util.HashSet;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.Set;
46 import java.util.function.Function;
47 import java.util.stream.Collectors;
48 
49 import kotlin.Triple;
50 
51 /**
52  * Stats for permission usage of an app. This data is for a given time period,
53  * i.e. does not contain the full history.
54  */
55 public final class AppPermissionUsage {
56     private final @NonNull List<GroupUsage> mGroupUsages = new ArrayList<>();
57     private final @NonNull PermissionApp mPermissionApp;
58 
59     // TODO: theianchen move them to SystemApi
60     private static final String OPSTR_PHONE_CALL_MICROPHONE = "android:phone_call_microphone";
61     private static final String OPSTR_PHONE_CALL_CAMERA = "android:phone_call_camera";
62     private static final int PRIVACY_HUB_FLAGS = AppOpsManager.OP_FLAG_SELF
63             | AppOpsManager.OP_FLAG_TRUSTED_PROXIED | AppOpsManager.OP_FLAG_TRUSTED_PROXY;
64 
AppPermissionUsage(@onNull PermissionApp permissionApp, @NonNull List<AppPermissionGroup> groups, @Nullable PackageOps lastUsage, @Nullable HistoricalPackageOps historicalUsage, @Nullable ArrayList<AudioRecordingConfiguration> recordings)65     private AppPermissionUsage(@NonNull PermissionApp permissionApp,
66             @NonNull List<AppPermissionGroup> groups, @Nullable PackageOps lastUsage,
67             @Nullable HistoricalPackageOps historicalUsage,
68             @Nullable ArrayList<AudioRecordingConfiguration> recordings) {
69         mPermissionApp = permissionApp;
70         final int groupCount = groups.size();
71         for (int i = 0; i < groupCount; i++) {
72             final AppPermissionGroup group = groups.get(i);
73 
74             /**
75              * TODO: HACK HACK HACK.
76              *
77              * Exclude for the UIDs that are currently silenced. This happens if an app keeps
78              * recording while in the background for more than a few seconds.
79              */
80             if (recordings != null && group.getName().equals(MICROPHONE)) {
81                 boolean isSilenced = false;
82                 int recordingsCount = recordings.size();
83                 for (int recordingNum = 0; recordingNum < recordingsCount; recordingNum++) {
84                     AudioRecordingConfiguration recording = recordings.get(recordingNum);
85                     if (recording.isClientSilenced()) {
86                         isSilenced = true;
87                         break;
88                     }
89                 }
90 
91                 if (isSilenced) {
92                     continue;
93                 }
94             }
95 
96             mGroupUsages.add(new GroupUsage(group, lastUsage, historicalUsage));
97         }
98     }
99 
getApp()100     public @NonNull PermissionApp getApp() {
101         return mPermissionApp;
102     }
103 
getPackageName()104     public @NonNull String getPackageName() {
105         return mPermissionApp.getPackageName();
106     }
107 
getUid()108     public int getUid() {
109         return mPermissionApp.getUid();
110     }
111 
getLastAccessTime()112     public long getLastAccessTime() {
113         long lastAccessTime = 0;
114         final int permissionCount = mGroupUsages.size();
115         for (int i = 0; i < permissionCount; i++) {
116             final GroupUsage groupUsage = mGroupUsages.get(i);
117             lastAccessTime = Math.max(lastAccessTime, groupUsage.getLastAccessTime());
118         }
119         return lastAccessTime;
120     }
121 
getAccessCount()122     public long getAccessCount() {
123         long accessCount = 0;
124         final int permissionCount = mGroupUsages.size();
125         for (int i = 0; i < permissionCount; i++) {
126             final GroupUsage permission = mGroupUsages.get(i);
127             accessCount += permission.getAccessCount();
128         }
129         return accessCount;
130     }
131 
getGroupUsages()132     public @NonNull List<GroupUsage> getGroupUsages() {
133         return mGroupUsages;
134     }
135 
136     /**
137      * Stats for permission usage of a permission group. This data is for a
138      * given time period, i.e. does not contain the full history.
139      */
140     public static class GroupUsage implements TimelineUsage {
141         private final @NonNull AppPermissionGroup mGroup;
142         private final @Nullable PackageOps mLastUsage;
143         private final @Nullable HistoricalPackageOps mHistoricalUsage;
144 
GroupUsage(@onNull AppPermissionGroup group, @Nullable PackageOps lastUsage, @Nullable HistoricalPackageOps historicalUsage)145         public GroupUsage(@NonNull AppPermissionGroup group, @Nullable PackageOps lastUsage,
146                 @Nullable HistoricalPackageOps historicalUsage) {
147             mGroup = group;
148             mLastUsage = lastUsage;
149             mHistoricalUsage = historicalUsage;
150         }
151 
getLastAccessTime()152         public long getLastAccessTime() {
153             if (mLastUsage == null) {
154                 return 0;
155             }
156 
157             return lastAccessAggregate((op) -> op.getLastAccessTime(PRIVACY_HUB_FLAGS));
158         }
159 
getLastAccessForegroundTime()160         public long getLastAccessForegroundTime() {
161             if (mLastUsage == null) {
162                 return 0;
163             }
164 
165             return lastAccessAggregate((op) -> op.getLastAccessForegroundTime(PRIVACY_HUB_FLAGS));
166         }
167 
getLastAccessBackgroundTime()168         public long getLastAccessBackgroundTime() {
169             if (mLastUsage == null) {
170                 return 0;
171             }
172 
173             return lastAccessAggregate((op) -> op.getLastAccessBackgroundTime(PRIVACY_HUB_FLAGS));
174         }
175 
getForegroundAccessCount()176         public long getForegroundAccessCount() {
177             if (mHistoricalUsage == null) {
178                 return 0;
179             }
180 
181             return extractAggregate((HistoricalOp op)
182                     -> op.getForegroundAccessCount(PRIVACY_HUB_FLAGS));
183         }
184 
getBackgroundAccessCount()185         public long getBackgroundAccessCount() {
186             if (mHistoricalUsage == null) {
187                 return 0;
188             }
189 
190             return extractAggregate((HistoricalOp op)
191                     -> op.getBackgroundAccessCount(PRIVACY_HUB_FLAGS));
192         }
193 
getAccessCount()194         public long getAccessCount() {
195             if (mHistoricalUsage == null) {
196                 return 0;
197             }
198 
199             return extractAggregate((HistoricalOp op) ->
200                     op.getForegroundAccessCount(PRIVACY_HUB_FLAGS)
201                             + op.getBackgroundAccessCount(PRIVACY_HUB_FLAGS)
202             );
203         }
204 
205         /**
206          * Get the last access duration.
207          */
getLastAccessDuration()208         public long getLastAccessDuration() {
209             if (mLastUsage == null) {
210                 return 0;
211             }
212             return lastAccessAggregate(
213                     (op) -> op.getLastDuration(AppOpsManager.OP_FLAGS_ALL_TRUSTED));
214         }
215 
216         /**
217          * Get the access duration.
218          */
getAccessDuration()219         public long getAccessDuration() {
220             if (mHistoricalUsage == null) {
221                 return 0;
222             }
223             return extractAggregate((HistoricalOp op) ->
224                     op.getForegroundAccessDuration(AppOpsManager.OP_FLAGS_ALL_TRUSTED)
225                             + op.getBackgroundAccessDuration(AppOpsManager.OP_FLAGS_ALL_TRUSTED)
226             );
227         }
228 
229 
230         @Override
hasDiscreteData()231         public boolean hasDiscreteData() {
232             if (mHistoricalUsage == null) {
233                 return false;
234             }
235 
236             Set<String> allOps = getAllOps(mGroup);
237             for (String opName : allOps) {
238                 final HistoricalOp historicalOp = mHistoricalUsage.getOp(opName);
239                 if (historicalOp != null && historicalOp.getDiscreteAccessCount() > 0) {
240                     return true;
241                 }
242             }
243             return false;
244         }
245 
246         @Override
getAllDiscreteAccessTime()247         public List<Triple<Long, Long, OpEventProxyInfo>> getAllDiscreteAccessTime() {
248             List<Triple<Long, Long, OpEventProxyInfo>> allDiscreteAccessTime = new ArrayList<>();
249             if (!hasDiscreteData()) {
250                 return allDiscreteAccessTime;
251             }
252 
253             Set<String> allOps = getAllOps(mGroup);
254             for (String opName : allOps) {
255                 final HistoricalOp historicalOp = mHistoricalUsage.getOp(opName);
256                 if (historicalOp == null) {
257                     continue;
258                 }
259 
260                 int discreteAccessCount = historicalOp.getDiscreteAccessCount();
261                 for (int j = 0; j < discreteAccessCount; j++) {
262                     AppOpsManager.AttributedOpEntry opEntry = historicalOp.getDiscreteAccessAt(j);
263                     allDiscreteAccessTime.add(new Triple<>(
264                             opEntry.getLastAccessTime(PRIVACY_HUB_FLAGS),
265                             opEntry.getLastDuration(PRIVACY_HUB_FLAGS),
266                             opEntry.getLastProxyInfo(PRIVACY_HUB_FLAGS)));
267                 }
268             }
269 
270             return allDiscreteAccessTime;
271         }
272 
isRunning()273         public boolean isRunning() {
274             if (mLastUsage == null) {
275                 return false;
276             }
277 
278             Set<String> allOps = getAllOps(mGroup);
279             final List<OpEntry> ops = mLastUsage.getOps();
280             final int opCount = ops.size();
281             for (int j = 0; j < opCount; j++) {
282                 final OpEntry op = ops.get(j);
283                 if (allOps.contains(op.getOpStr()) && op.isRunning()) {
284                     return true;
285                 }
286             }
287 
288             return false;
289         }
290 
extractAggregate(@onNull Function<HistoricalOp, Long> extractor)291         private long extractAggregate(@NonNull Function<HistoricalOp, Long> extractor) {
292             long aggregate = 0;
293 
294             Set<String> allOps = getAllOps(mGroup);
295             for (String opName : allOps) {
296                 final HistoricalOp historicalOp = mHistoricalUsage.getOp(opName);
297                 if (historicalOp != null) {
298                     aggregate += extractor.apply(historicalOp);
299                 }
300             }
301 
302             return aggregate;
303         }
304 
lastAccessAggregate(@onNull Function<OpEntry, Long> extractor)305         private long lastAccessAggregate(@NonNull Function<OpEntry, Long> extractor) {
306             long aggregate = 0;
307 
308             Set<String> allOps = getAllOps(mGroup);
309             final List<OpEntry> ops = mLastUsage.getOps();
310             final int opCount = ops.size();
311 
312             for (int opNum = 0; opNum < opCount; opNum++) {
313                 final OpEntry op = ops.get(opNum);
314                 if (allOps.contains(op.getOpStr())) {
315                     aggregate = Math.max(aggregate, extractor.apply(op));
316                 }
317             }
318 
319             return aggregate;
320         }
321 
getAllOps(AppPermissionGroup appPermissionGroup)322         private static Set<String> getAllOps(AppPermissionGroup appPermissionGroup) {
323             Set<String> allOps = new HashSet<>();
324             List<Permission> permissions = appPermissionGroup.getPermissions();
325             final int permissionCount = permissions.size();
326             for (int permissionNum = 0; permissionNum < permissionCount; permissionNum++) {
327                 final Permission permission = permissions.get(permissionNum);
328                 final String opName = permission.getAppOp();
329                 if (opName != null) {
330                     allOps.add(opName);
331                 }
332             }
333 
334             if (appPermissionGroup.getName().equals(Manifest.permission_group.MICROPHONE)) {
335                 allOps.add(OPSTR_PHONE_CALL_MICROPHONE);
336             }
337 
338             if (appPermissionGroup.getName().equals(Manifest.permission_group.CAMERA)) {
339                 allOps.add(OPSTR_PHONE_CALL_CAMERA);
340             }
341 
342             return allOps;
343         }
344 
345         @Override
getGroup()346         public @NonNull AppPermissionGroup getGroup() {
347             return mGroup;
348         }
349 
350         @Override
getLabel()351         public int getLabel() {
352             return -1;
353         }
354 
355         @Override
getAttributionTags()356         public @Nullable ArrayList<String> getAttributionTags() {
357             if (mHistoricalUsage == null || mHistoricalUsage.getAttributedOpsCount() == 0) {
358                 return null;
359             }
360             ArrayList<String> attributionTags = new ArrayList<>();
361             int count = mHistoricalUsage.getAttributedOpsCount();
362             for (int i = 0; i < count; i++) {
363                 attributionTags.add(mHistoricalUsage.getAttributedOpsAt(i).getTag());
364             }
365             return attributionTags;
366         }
367 
368         /** Creates a lookup from the attribution tag to its label. **/
369         @RequiresApi(Build.VERSION_CODES.S)
getAttributionTagToLabelMap( Attribution[] attributions)370         private static Map<String, Integer> getAttributionTagToLabelMap(
371                 Attribution[] attributions) {
372             Map<String, Integer> attributionTagToLabelMap = new HashMap<>();
373             for (Attribution attribution : attributions) {
374                 attributionTagToLabelMap.put(attribution.getTag(), attribution.getLabel());
375             }
376             return attributionTagToLabelMap;
377         }
378 
379         /** Partitions the usages based on the attribution tag label. */
380         @RequiresApi(Build.VERSION_CODES.S)
getAttributionLabelledGroupUsages()381         public List<AttributionLabelledGroupUsage> getAttributionLabelledGroupUsages() {
382             Map<String, Integer> attributionTagToLabelMap =
383                     getAttributionTagToLabelMap(getGroup().getApp().attributions);
384 
385             Set<String> allOps = getAllOps(mGroup);
386 
387             // we need to collect discreteAccessTime for each label
388             Map<Integer, AttributionLabelledGroupUsage.Builder> labelDiscreteAccessMap =
389                     new HashMap<>();
390 
391             for (int i = 0; i < mHistoricalUsage.getAttributedOpsCount(); i++) {
392                 AttributedHistoricalOps attributedOp = mHistoricalUsage.getAttributedOpsAt(i);
393                 String attributionTag = attributedOp.getTag();
394 
395                 for (String opName : allOps) {
396                     final HistoricalOp historicalOp = attributedOp.getOp(opName);
397                     if (historicalOp == null) {
398                         continue;
399                     }
400 
401                     int discreteAccessCount = historicalOp.getDiscreteAccessCount();
402                     for (int j = 0; j < discreteAccessCount; j++) {
403                         AttributedOpEntry opEntry = historicalOp.getDiscreteAccessAt(j);
404                         Integer label = attributionTagToLabelMap.get(attributedOp.getTag());
405                         if (!labelDiscreteAccessMap.containsKey(label)) {
406                             labelDiscreteAccessMap.put(label,
407                                     new AttributionLabelledGroupUsage.Builder(label, getGroup()));
408                         }
409                         labelDiscreteAccessMap.get(label).addAttributionTag(attributionTag);
410                         labelDiscreteAccessMap.get(label).addDiscreteAccessTime(new Triple<>(
411                                 opEntry.getLastAccessTime(PRIVACY_HUB_FLAGS),
412                                 opEntry.getLastDuration(PRIVACY_HUB_FLAGS),
413                                 opEntry.getLastProxyInfo(PRIVACY_HUB_FLAGS)));
414                     }
415                 }
416             }
417 
418             return labelDiscreteAccessMap.entrySet().stream()
419                     .map(e -> e.getValue().build())
420                     .collect(Collectors.toList());
421         }
422 
423         /**
424          * Represents the slice of {@link GroupUsage} with a label.
425          *
426          * <p> -1 as label means that there was no entry for the attribution tag in the
427          * manifest.</p>
428          */
429         public static class AttributionLabelledGroupUsage implements TimelineUsage {
430             private final int mLabel;
431             private final AppPermissionGroup mAppPermissionGroup;
432             private final List<String> mAttributionTags;
433             private final List<Triple<Long, Long, OpEventProxyInfo>> mDiscreteAccessTime;
434 
AttributionLabelledGroupUsage(int label, AppPermissionGroup appPermissionGroup, List<String> attributionTags, List<Triple<Long, Long, OpEventProxyInfo>> discreteAccessTime)435             AttributionLabelledGroupUsage(int label,
436                     AppPermissionGroup appPermissionGroup,
437                     List<String> attributionTags,
438                     List<Triple<Long, Long, OpEventProxyInfo>> discreteAccessTime) {
439                 mLabel = label;
440                 mAppPermissionGroup = appPermissionGroup;
441                 mAttributionTags = attributionTags;
442                 mDiscreteAccessTime = discreteAccessTime;
443             }
444 
445             @Override
getLabel()446             public int getLabel() {
447                 return mLabel;
448             }
449 
450             @Override
hasDiscreteData()451             public boolean hasDiscreteData() {
452                 return mDiscreteAccessTime.size() > 0;
453             }
454 
455             @Override
getAllDiscreteAccessTime()456             public List<Triple<Long, Long, OpEventProxyInfo>> getAllDiscreteAccessTime() {
457                 return mDiscreteAccessTime;
458             }
459 
460             @Override
getAttributionTags()461             public List<String> getAttributionTags() {
462                 return mAttributionTags;
463             }
464 
465             @Override
getGroup()466             public AppPermissionGroup getGroup() {
467                 return mAppPermissionGroup;
468             }
469 
470             static class Builder {
471                 private final int mLabel;
472                 private final AppPermissionGroup mAppPermissionGroup;
473                 private Set<String> mAttributionTags;
474                 private List<Triple<Long, Long, OpEventProxyInfo>>  mDiscreteAccessTime;
475 
Builder(int label, AppPermissionGroup appPermissionGroup)476                 Builder(int label, AppPermissionGroup appPermissionGroup) {
477                     mLabel = label;
478                     mAppPermissionGroup = appPermissionGroup;
479                     mAttributionTags = new HashSet<>();
480                     mDiscreteAccessTime = new ArrayList<>();
481                 }
482 
addAttributionTag(String attributionTag)483                 @NonNull Builder addAttributionTag(String attributionTag) {
484                     mAttributionTags.add(attributionTag);
485                     return this;
486                 }
487 
488                 @NonNull
addDiscreteAccessTime( Triple<Long, Long, OpEventProxyInfo> discreteAccessTime)489                 Builder addDiscreteAccessTime(
490                         Triple<Long, Long, OpEventProxyInfo> discreteAccessTime) {
491                     mDiscreteAccessTime.add(discreteAccessTime);
492                     return this;
493                 }
494 
build()495                 AttributionLabelledGroupUsage build() {
496                     return new AttributionLabelledGroupUsage(mLabel,
497                             mAppPermissionGroup,
498                             new ArrayList<String>() {{
499                                 addAll(mAttributionTags);
500                             }}, mDiscreteAccessTime);
501                 }
502             }
503         }
504     }
505 
506     public static class Builder {
507         private final @NonNull List<AppPermissionGroup> mGroups = new ArrayList<>();
508         private final @NonNull PermissionApp mPermissionApp;
509         private @Nullable PackageOps mLastUsage;
510         private @Nullable HistoricalPackageOps mHistoricalUsage;
511         private @Nullable ArrayList<AudioRecordingConfiguration> mAudioRecordingConfigurations;
512 
513         public Builder(@NonNull PermissionApp permissionApp) {
514             mPermissionApp = permissionApp;
515         }
516 
517         public @NonNull Builder addGroup(@NonNull AppPermissionGroup group) {
518             mGroups.add(group);
519             return this;
520         }
521 
522         public @NonNull Builder setLastUsage(@Nullable PackageOps lastUsage) {
523             mLastUsage = lastUsage;
524             return this;
525         }
526 
527         public @NonNull Builder setHistoricalUsage(@Nullable HistoricalPackageOps historicalUsage) {
528             mHistoricalUsage = historicalUsage;
529             return this;
530         }
531 
532         public @NonNull Builder setRecordingConfiguration(
533                 @Nullable ArrayList<AudioRecordingConfiguration> recordings) {
534             mAudioRecordingConfigurations = recordings;
535             return this;
536         }
537 
538         public @NonNull AppPermissionUsage build() {
539             if (mGroups.isEmpty()) {
540                 throw new IllegalStateException("mGroups cannot be empty.");
541             }
542             return new AppPermissionUsage(mPermissionApp, mGroups, mLastUsage, mHistoricalUsage,
543                     mAudioRecordingConfigurations);
544         }
545     }
546 
547     /** Usage for showing timeline view for a specific permission group with a label. */
548     public interface TimelineUsage {
549         /**
550          * Returns whether the usage has discrete data.
551          */
552         boolean hasDiscreteData();
553 
554         /**
555          * Returns all discrete access time in millis.
556          * Returns a list of triples of (access time, access duration, proxy)
557          */
558         List<Triple<Long, Long, OpEventProxyInfo>> getAllDiscreteAccessTime();
559 
560         /**
561          * Returns attribution tags for the usage.
562          */
563         List<String> getAttributionTags();
564 
565         /**
566          * Returns the permission group of the usage.
567          */
568         AppPermissionGroup getGroup();
569 
570         /**
571          * Returns the user facing string's resource id.
572          *
573          * <p> -1 means show the app name otherwise get the string resource from the app
574          * context.</p>
575          */
576         int getLabel();
577     }
578 }
579