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