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.CAMERA;
20 import static android.Manifest.permission.RECORD_AUDIO;
21 import static android.app.AppOpsManager.OPSTR_PHONE_CALL_CAMERA;
22 import static android.app.AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE;
23 import static android.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP;
24 
25 import static com.android.permissioncontroller.Constants.OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO;
26 import static com.android.permissioncontroller.permission.utils.Utils.isHealthPermissionUiEnabled;
27 
28 import android.app.AppOpsManager;
29 import android.app.AppOpsManager.HistoricalOps;
30 import android.app.AppOpsManager.HistoricalOpsRequest;
31 import android.app.AppOpsManager.HistoricalPackageOps;
32 import android.app.AppOpsManager.HistoricalUidOps;
33 import android.app.AppOpsManager.PackageOps;
34 import android.app.LoaderManager;
35 import android.app.LoaderManager.LoaderCallbacks;
36 import android.content.AsyncTaskLoader;
37 import android.content.Context;
38 import android.content.Loader;
39 import android.content.pm.PackageInfo;
40 import android.media.AudioManager;
41 import android.media.AudioRecordingConfiguration;
42 import android.os.Build;
43 import android.os.Bundle;
44 import android.os.Process;
45 import android.util.ArrayMap;
46 import android.util.ArraySet;
47 import android.util.Pair;
48 import android.util.SparseArray;
49 
50 import androidx.annotation.NonNull;
51 import androidx.annotation.Nullable;
52 import androidx.annotation.RequiresApi;
53 
54 import com.android.modules.utils.build.SdkLevel;
55 import com.android.permissioncontroller.permission.model.AppPermissionGroup;
56 import com.android.permissioncontroller.permission.model.Permission;
57 import com.android.permissioncontroller.permission.model.legacy.PermissionApps.PermissionApp;
58 import com.android.permissioncontroller.permission.model.legacy.PermissionGroup;
59 import com.android.permissioncontroller.permission.model.legacy.PermissionGroups;
60 import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage.Builder;
61 import com.android.permissioncontroller.permission.utils.Utils;
62 
63 import java.util.ArrayList;
64 import java.util.Arrays;
65 import java.util.Collections;
66 import java.util.List;
67 import java.util.concurrent.CountDownLatch;
68 import java.util.concurrent.TimeUnit;
69 import java.util.concurrent.atomic.AtomicReference;
70 
71 /**
72  * Loads all permission usages for a set of apps and permission groups.
73  */
74 @RequiresApi(Build.VERSION_CODES.S)
75 public final class PermissionUsages implements LoaderCallbacks<List<AppPermissionUsage>> {
76     public static final int USAGE_FLAG_LAST = 1 << 0;
77     public static final int USAGE_FLAG_HISTORICAL = 1 << 2;
78 
79     private final ArrayList<AppPermissionUsage> mUsages = new ArrayList<>();
80     private final @NonNull Context mContext;
81 
82     private static final String KEY_FILTER_UID =  "KEY_FILTER_UID";
83     private static final String KEY_FILTER_PACKAGE_NAME =  "KEY_FILTER_PACKAGE_NAME";
84     private static final String KEY_FILTER_PERMISSION_GROUP =  "KEY_FILTER_PERMISSION_GROUP";
85     private static final String KEY_FILTER_BEGIN_TIME_MILLIS =  "KEY_FILTER_BEGIN_TIME_MILLIS";
86     private static final String KEY_FILTER_END_TIME_MILLIS =  "KEY_FILTER_END_TIME_MILLIS";
87     private static final String KEY_USAGE_FLAGS =  "KEY_USAGE_FLAGS";
88     private static final String KEY_GET_UI_INFO =  "KEY_GET_UI_INFO";
89     private static final String KEY_GET_NON_PLATFORM_PERMISSIONS =
90             "KEY_GET_NON_PLATFORM_PERMISSIONS";
91     private static final String TELECOM_PACKAGE = "com.android.server.telecom";
92     private static final int DEFAULT_REQUIRED_PERMISSION_FLAG = 3;
93 
94     public static final int HISTORY_FLAG_GET_ATTRIBUTION_CHAINS = 1 << 2;
95 
96     private @Nullable PermissionsUsagesChangeCallback mCallback;
97 
98     /**
99      * Callback for when the permission usages has loaded or changed.
100      */
101     public interface PermissionsUsagesChangeCallback {
102         /**
103          * Called when the permission usages have loaded or changed.
104          */
onPermissionUsagesChanged()105         void onPermissionUsagesChanged();
106     }
107 
108     /**
109      * Creates a new instance of {@link PermissionUsages}.
110      */
PermissionUsages(@onNull Context context)111     public PermissionUsages(@NonNull Context context) {
112         mContext = context;
113     }
114 
115     /**
116      * Start the {@link Loader} to load the permission usages in the background. Loads without a uid
117      * filter.
118      */
load(@ullable String filterPackageName, @Nullable String[] filterPermissionGroups, long filterBeginTimeMillis, long filterEndTimeMillis, int usageFlags, @NonNull LoaderManager loaderManager, boolean getUiInfo, boolean getNonPlatformPermissions, @NonNull PermissionsUsagesChangeCallback callback, boolean sync)119     public void load(@Nullable String filterPackageName,
120             @Nullable String[] filterPermissionGroups, long filterBeginTimeMillis,
121             long filterEndTimeMillis, int usageFlags, @NonNull LoaderManager loaderManager,
122             boolean getUiInfo, boolean getNonPlatformPermissions,
123             @NonNull PermissionsUsagesChangeCallback callback, boolean sync) {
124         load(Process.INVALID_UID, filterPackageName, filterPermissionGroups, filterBeginTimeMillis,
125                 filterEndTimeMillis, usageFlags, loaderManager, getUiInfo,
126                 getNonPlatformPermissions, callback, sync);
127     }
128 
129     /**
130      * Start the {@link Loader} to load the permission usages in the background. Loads only
131      * permissions for the specified {@code filterUid}.
132      */
load(int filterUid, @Nullable String filterPackageName, @Nullable String[] filterPermissionGroups, long filterBeginTimeMillis, long filterEndTimeMillis, int usageFlags, @NonNull LoaderManager loaderManager, boolean getUiInfo, boolean getNonPlatformPermissions, @NonNull PermissionsUsagesChangeCallback callback, boolean sync)133     public void load(int filterUid, @Nullable String filterPackageName,
134             @Nullable String[] filterPermissionGroups, long filterBeginTimeMillis,
135             long filterEndTimeMillis, int usageFlags, @NonNull LoaderManager loaderManager,
136             boolean getUiInfo, boolean getNonPlatformPermissions,
137             @NonNull PermissionsUsagesChangeCallback callback, boolean sync) {
138         mCallback = callback;
139         final Bundle args = new Bundle();
140         args.putInt(KEY_FILTER_UID, filterUid);
141         args.putString(KEY_FILTER_PACKAGE_NAME, filterPackageName);
142         args.putStringArray(KEY_FILTER_PERMISSION_GROUP, filterPermissionGroups);
143         args.putLong(KEY_FILTER_BEGIN_TIME_MILLIS, filterBeginTimeMillis);
144         args.putLong(KEY_FILTER_END_TIME_MILLIS, filterEndTimeMillis);
145         args.putInt(KEY_USAGE_FLAGS, usageFlags);
146         args.putBoolean(KEY_GET_UI_INFO, getUiInfo);
147         args.putBoolean(KEY_GET_NON_PLATFORM_PERMISSIONS, getNonPlatformPermissions);
148         if (sync) {
149             final UsageLoader loader = new UsageLoader(mContext, args);
150             final List<AppPermissionUsage> usages = loader.loadInBackground();
151             onLoadFinished(loader, usages);
152         } else {
153             loaderManager.restartLoader(1, args, this);
154         }
155     }
156 
157     @Override
onCreateLoader(int id, Bundle args)158     public Loader<List<AppPermissionUsage>> onCreateLoader(int id, Bundle args) {
159         return new UsageLoader(mContext, args);
160     }
161 
162     @Override
onLoadFinished(@onNull Loader<List<AppPermissionUsage>> loader, List<AppPermissionUsage> usages)163     public void onLoadFinished(@NonNull Loader<List<AppPermissionUsage>> loader,
164             List<AppPermissionUsage> usages) {
165         mUsages.clear();
166         mUsages.addAll(usages);
167         if (mCallback != null) {
168             mCallback.onPermissionUsagesChanged();
169         }
170     }
171 
172     @Override
onLoaderReset(@onNull Loader<List<AppPermissionUsage>> loader)173     public void onLoaderReset(@NonNull Loader<List<AppPermissionUsage>> loader) {
174         mUsages.clear();
175         mCallback.onPermissionUsagesChanged();
176     }
177 
178     /**
179      * Return the usages that have already been loaded.
180      */
getUsages()181     public @NonNull List<AppPermissionUsage> getUsages() {
182         return mUsages;
183     }
184 
185     /**
186      * Stop the {@link Loader} from loading the usages.
187      */
stopLoader(@onNull LoaderManager loaderManager)188     public void stopLoader(@NonNull LoaderManager loaderManager) {
189         loaderManager.destroyLoader(1);
190     }
191 
192     private static final class UsageLoader extends AsyncTaskLoader<List<AppPermissionUsage>> {
193         private final int mFilterUid;
194         private @Nullable String mFilterPackageName;
195         private @Nullable String[] mFilterPermissionGroups;
196         private final long mFilterBeginTimeMillis;
197         private final long mFilterEndTimeMillis;
198         private final int mUsageFlags;
199         private final boolean mGetUiInfo;
200         private final boolean mGetNonPlatformPermissions;
201 
UsageLoader(@onNull Context context, @NonNull Bundle args)202         UsageLoader(@NonNull Context context, @NonNull Bundle args) {
203             super(context);
204             mFilterUid = args.getInt(KEY_FILTER_UID);
205             mFilterPackageName = args.getString(KEY_FILTER_PACKAGE_NAME);
206             mFilterPermissionGroups = args.getStringArray(KEY_FILTER_PERMISSION_GROUP);
207             mFilterBeginTimeMillis = args.getLong(KEY_FILTER_BEGIN_TIME_MILLIS);
208             mFilterEndTimeMillis = args.getLong(KEY_FILTER_END_TIME_MILLIS);
209             mUsageFlags = args.getInt(KEY_USAGE_FLAGS);
210             mGetUiInfo = args.getBoolean(KEY_GET_UI_INFO);
211             mGetNonPlatformPermissions = args.getBoolean(KEY_GET_NON_PLATFORM_PERMISSIONS);
212         }
213 
214         @Override
onStartLoading()215         protected void onStartLoading() {
216             forceLoad();
217         }
218 
219         @Override
loadInBackground()220         public @NonNull List<AppPermissionUsage> loadInBackground() {
221             final List<PermissionGroup> groups = PermissionGroups.getPermissionGroups(
222                     getContext(), this::isLoadInBackgroundCanceled, mGetUiInfo,
223                     mGetNonPlatformPermissions, mFilterPermissionGroups, mFilterPackageName);
224             if (groups.isEmpty()) {
225                 return Collections.emptyList();
226             }
227 
228             final List<AppPermissionUsage> usages = new ArrayList<>();
229             final ArraySet<String> opNames = new ArraySet<>();
230             final ArrayMap<Pair<Integer, String>, AppPermissionUsage.Builder> usageBuilders =
231                     new ArrayMap<>();
232 
233             final int groupCount = groups.size();
234             boolean telecomMicAndCamAdded = false;
235             for (int groupIdx = 0; groupIdx < groupCount; groupIdx++) {
236                 final PermissionGroup group = groups.get(groupIdx);
237                 // Filter out third party permissions
238                 if (!(group.getDeclaringPackage().equals(Utils.OS_PKG)
239                         || (isHealthPermissionUiEnabled() && HEALTH_PERMISSION_GROUP.equals(
240                         group.getName())))) {
241                     continue;
242                 }
243 
244                 groups.add(group);
245 
246                 final List<PermissionApp> permissionApps = group.getPermissionApps().getApps();
247                 final int appCount = permissionApps.size();
248                 for (int appIdx = 0; appIdx < appCount; appIdx++) {
249                     final PermissionApp permissionApp = permissionApps.get(appIdx);
250                     if (mFilterUid != Process.INVALID_UID
251                             && permissionApp.getAppInfo().uid != mFilterUid) {
252                         continue;
253                     }
254 
255                     final AppPermissionGroup appPermGroup = permissionApp.getPermissionGroup();
256                     if (!Utils.shouldShowPermission(getContext(), appPermGroup)) {
257                         continue;
258                     }
259                     final Pair<Integer, String> usageKey = Pair.create(permissionApp.getUid(),
260                             permissionApp.getPackageName());
261                     AppPermissionUsage.Builder usageBuilder = usageBuilders.get(usageKey);
262                     if (usageBuilder == null) {
263                         usageBuilder = new Builder(permissionApp);
264                         usageBuilders.put(usageKey, usageBuilder);
265                     }
266                     usageBuilder.addGroup(appPermGroup);
267 
268                     // Since PermissionGroups.getPermissionGroups doesn't return
269                     // Telecom PermissionApp entity with Microphone and Camera permission groups,
270                     // we have to manually add those entries here.
271                     if (!telecomMicAndCamAdded
272                             && permissionApp.getPackageName().equals(TELECOM_PACKAGE)) {
273                         PackageInfo telecomPackageInfo = appPermGroup.getApp();
274 
275                         String[] newReqPerms = Arrays.copyOf(
276                                 telecomPackageInfo.requestedPermissions,
277                                 telecomPackageInfo.requestedPermissions.length + 2);
278                         newReqPerms[telecomPackageInfo.requestedPermissions.length] = RECORD_AUDIO;
279                         newReqPerms[telecomPackageInfo.requestedPermissions.length + 1] = CAMERA;
280                         telecomPackageInfo.requestedPermissions = newReqPerms;
281 
282                         int[] newReqPermsFlags = Arrays.copyOf(
283                                 telecomPackageInfo.requestedPermissionsFlags,
284                                 telecomPackageInfo.requestedPermissionsFlags.length + 2);
285                         newReqPermsFlags[telecomPackageInfo.requestedPermissionsFlags.length] =
286                                 DEFAULT_REQUIRED_PERMISSION_FLAG;
287                         newReqPermsFlags[telecomPackageInfo.requestedPermissionsFlags.length + 1] =
288                                 DEFAULT_REQUIRED_PERMISSION_FLAG;
289                         telecomPackageInfo.requestedPermissionsFlags = newReqPermsFlags;
290 
291                         AppPermissionGroup micGroup = AppPermissionGroup.create(getContext(),
292                                 telecomPackageInfo, RECORD_AUDIO, false);
293                         AppPermissionGroup camGroup = AppPermissionGroup.create(getContext(),
294                                 telecomPackageInfo, CAMERA, false);
295 
296                         if (micGroup != null) {
297                             usageBuilder.addGroup(micGroup);
298                         }
299 
300                         if (camGroup != null) {
301                             usageBuilder.addGroup(camGroup);
302                         }
303 
304                         telecomMicAndCamAdded = true;
305                     }
306 
307                     final List<Permission> permissions = appPermGroup.getPermissions();
308                     final int permCount = permissions.size();
309                     for (int permIdx = 0; permIdx < permCount; permIdx++) {
310                         final Permission permission = permissions.get(permIdx);
311                         final String opName = permission.getAppOp();
312                         if (opName != null) {
313                             opNames.add(opName);
314                         }
315                     }
316                 }
317             }
318 
319             if (usageBuilders.isEmpty()) {
320                 return Collections.emptyList();
321             }
322 
323             final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
324 
325             // Get last usage data and put in a map for a quick lookup.
326             final ArrayMap<Pair<Integer, String>, PackageOps> lastUsages =
327                     new ArrayMap<>(usageBuilders.size());
328             opNames.add(OPSTR_PHONE_CALL_MICROPHONE);
329             opNames.add(OPSTR_PHONE_CALL_CAMERA);
330             if (SdkLevel.isAtLeastT()) {
331                 opNames.add(OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO);
332             }
333             final String[] opNamesArray = opNames.toArray(new String[opNames.size()]);
334             if ((mUsageFlags & USAGE_FLAG_LAST) != 0) {
335                 final List<PackageOps> usageOps;
336                 if (mFilterPackageName != null || mFilterUid != Process.INVALID_UID) {
337                     usageOps = appOpsManager.getOpsForPackage(mFilterUid, mFilterPackageName,
338                             opNamesArray);
339                 } else {
340                     usageOps = appOpsManager.getPackagesForOps(opNamesArray);
341                 }
342                 if (usageOps != null && !usageOps.isEmpty()) {
343                     final int usageOpsCount = usageOps.size();
344                     for (int i = 0; i < usageOpsCount; i++) {
345                         final PackageOps usageOp = usageOps.get(i);
346                         lastUsages.put(Pair.create(usageOp.getUid(), usageOp.getPackageName()),
347                                 usageOp);
348                     }
349                 }
350             }
351 
352             if (isLoadInBackgroundCanceled()) {
353                 return Collections.emptyList();
354             }
355 
356             // Get historical usage data and put in a map for a quick lookup
357             final ArrayMap<Pair<Integer, String>, HistoricalPackageOps> historicalUsages =
358                     new ArrayMap<>(usageBuilders.size());
359             if ((mUsageFlags & USAGE_FLAG_HISTORICAL) != 0) {
360                 final AtomicReference<HistoricalOps> historicalOpsRef = new AtomicReference<>();
361                 final CountDownLatch latch = new CountDownLatch(1);
362 
363                 // query for discrete timeline data for location, mic and camera
364                 final HistoricalOpsRequest request = new HistoricalOpsRequest.Builder(
365                         mFilterBeginTimeMillis, mFilterEndTimeMillis)
366                         .setFlags(AppOpsManager.OP_FLAG_SELF
367                                 | AppOpsManager.OP_FLAG_TRUSTED_PROXIED)
368                         .setHistoryFlags(AppOpsManager.HISTORY_FLAG_DISCRETE
369                                 | HISTORY_FLAG_GET_ATTRIBUTION_CHAINS)
370                         .build();
371                 appOpsManager.getHistoricalOps(request, Runnable::run,
372                         (HistoricalOps ops) -> {
373                             historicalOpsRef.set(ops);
374                             latch.countDown();
375                         });
376                 try {
377                     latch.await(5, TimeUnit.DAYS);
378                 } catch (InterruptedException ignored) { }
379 
380                 final HistoricalOps historicalOps = historicalOpsRef.get();
381 
382                 if (historicalOps != null) {
383                     final int uidCount = historicalOps.getUidCount();
384                     for (int i = 0; i < uidCount; i++) {
385                         final HistoricalUidOps uidOps = historicalOps.getUidOpsAt(i);
386                         final int packageCount = uidOps.getPackageCount();
387                         for (int j = 0; j < packageCount; j++) {
388                             final HistoricalPackageOps packageOps = uidOps.getPackageOpsAt(j);
389                             historicalUsages.put(
390                                     Pair.create(uidOps.getUid(), packageOps.getPackageName()),
391                                     packageOps);
392                         }
393                     }
394                 }
395             }
396 
397             // Get audio recording config
398             List<AudioRecordingConfiguration> allRecordings = getContext()
399                     .getSystemService(AudioManager.class).getActiveRecordingConfigurations();
400             SparseArray<ArrayList<AudioRecordingConfiguration>> recordingsByUid =
401                     new SparseArray<>();
402 
403             final int recordingsCount = allRecordings.size();
404             for (int i = 0; i < recordingsCount; i++) {
405                 AudioRecordingConfiguration recording = allRecordings.get(i);
406 
407                 ArrayList<AudioRecordingConfiguration> recordings = recordingsByUid.get(
408                         recording.getClientUid());
409                 if (recordings == null) {
410                     recordings = new ArrayList<>();
411                     recordingsByUid.put(recording.getClientUid(), recordings);
412                 }
413                 recordings.add(recording);
414             }
415 
416             // Construct the historical usages based on data we fetched
417             final int builderCount = usageBuilders.size();
418             for (int i = 0; i < builderCount; i++) {
419                 final Pair<Integer, String> key = usageBuilders.keyAt(i);
420                 final Builder usageBuilder = usageBuilders.valueAt(i);
421                 final PackageOps lastUsage = lastUsages.get(key);
422                 usageBuilder.setLastUsage(lastUsage);
423                 final HistoricalPackageOps historicalUsage = historicalUsages.get(key);
424 
425                 usageBuilder.setHistoricalUsage(historicalUsage);
426                 usageBuilder.setRecordingConfiguration(recordingsByUid.get(key.first));
427                 usages.add(usageBuilder.build());
428             }
429 
430             return usages;
431         }
432     }
433 }
434