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