1 /*
2  * Copyright (C) 2015 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 package com.android.permissioncontroller.permission.model.legacy;
17 
18 import android.content.Context;
19 import android.content.pm.ApplicationInfo;
20 import android.content.pm.PackageInfo;
21 import android.content.pm.PackageItemInfo;
22 import android.content.pm.PackageManager;
23 import android.content.pm.PackageManager.NameNotFoundException;
24 import android.content.pm.PermissionGroupInfo;
25 import android.content.pm.PermissionInfo;
26 import android.graphics.drawable.Drawable;
27 import android.os.AsyncTask;
28 import android.os.UserHandle;
29 import android.os.UserManager;
30 import android.text.TextUtils;
31 import android.util.ArrayMap;
32 import android.util.Log;
33 import android.util.Pair;
34 import android.util.SparseArray;
35 
36 import androidx.annotation.NonNull;
37 import androidx.annotation.Nullable;
38 
39 import com.android.modules.utils.build.SdkLevel;
40 import com.android.permissioncontroller.R;
41 import com.android.permissioncontroller.permission.model.AppPermissionGroup;
42 import com.android.permissioncontroller.permission.utils.Utils;
43 
44 import java.util.ArrayList;
45 import java.util.Collections;
46 import java.util.List;
47 
48 /**
49  * @deprecated Use classes from permission.ui.model instead
50  */
51 @Deprecated
52 public class PermissionApps {
53     private static final String LOG_TAG = "PermissionApps";
54 
55     private final Context mContext;
56     private final String mGroupName;
57     private final String mPackageName;
58     private final PackageManager mPm;
59     private final Callback mCallback;
60 
61     private final @Nullable PmCache mPmCache;
62     private final @Nullable AppDataCache mAppDataCache;
63 
64     private CharSequence mLabel;
65     private CharSequence mFullLabel;
66     private Drawable mIcon;
67     private @Nullable CharSequence mDescription;
68     private List<PermissionApp> mPermApps;
69     // Map (pkg|uid) -> AppPermission
70     private ArrayMap<String, PermissionApp> mAppLookup;
71 
72     private boolean mSkipUi;
73     private boolean mRefreshing;
74 
PermissionApps(Context context, String groupName, Callback callback)75     public PermissionApps(Context context, String groupName, Callback callback) {
76         this(context, groupName, null, callback, null, null);
77     }
78 
PermissionApps(Context context, String groupName, String packageName, Callback callback, @Nullable PmCache pmCache, @Nullable AppDataCache appDataCache)79     public PermissionApps(Context context, String groupName, String packageName,
80             Callback callback, @Nullable PmCache pmCache, @Nullable AppDataCache appDataCache) {
81         mPmCache = pmCache;
82         mAppDataCache = appDataCache;
83         mContext = context;
84         mPm = mContext.getPackageManager();
85         mGroupName = groupName;
86         mCallback = callback;
87         mPackageName = packageName;
88         loadGroupInfo();
89     }
90 
getGroupName()91     public String getGroupName() {
92         return mGroupName;
93     }
94 
95     /**
96      * Start an async refresh and call back the registered call back once done.
97      *
98      * @param getUiInfo If the UI info should be updated
99      */
refresh(boolean getUiInfo)100     public void refresh(boolean getUiInfo) {
101         if (!mRefreshing) {
102             mRefreshing = true;
103             mSkipUi = !getUiInfo;
104             new PermissionAppsLoader().execute();
105         }
106     }
107 
108     /**
109      * Refresh the state and do not return until it finishes. Should not be called while an {@link
110      * #refresh async referesh} is in progress.
111      */
refreshSync(boolean getUiInfo)112     public void refreshSync(boolean getUiInfo) {
113         mSkipUi = !getUiInfo;
114         createMap(loadPermissionApps());
115     }
116 
getGrantedCount()117     public int getGrantedCount() {
118         int count = 0;
119         for (PermissionApp app : mPermApps) {
120             if (!app.getAppInfo().enabled) {
121                 continue;
122             }
123             if (!Utils.shouldShowPermission(mContext, app.getPermissionGroup())) {
124                 continue;
125             }
126             if (!Utils.isGroupOrBgGroupUserSensitive(app.mAppPermissionGroup)) {
127                 // We default to not showing system apps, so hide them from count.
128                 continue;
129             }
130             if (app.areRuntimePermissionsGranted()) {
131                 count++;
132             }
133         }
134         return count;
135     }
136 
getTotalCount()137     public int getTotalCount() {
138         int count = 0;
139         for (PermissionApp app : mPermApps) {
140             if (!app.getAppInfo().enabled) {
141                 continue;
142             }
143             if (!Utils.shouldShowPermission(mContext, app.getPermissionGroup())) {
144                 continue;
145             }
146             if (!Utils.isGroupOrBgGroupUserSensitive(app.mAppPermissionGroup)) {
147                 // We default to not showing system apps, so hide them from count.
148                 continue;
149             }
150             count++;
151         }
152         return count;
153     }
154 
getApps()155     public List<PermissionApp> getApps() {
156         return mPermApps;
157     }
158 
getApp(String key)159     public PermissionApp getApp(String key) {
160         return mAppLookup.get(key);
161     }
162 
getLabel()163     public CharSequence getLabel() {
164         return mLabel;
165     }
166 
getFullLabel()167     public CharSequence getFullLabel() {
168         return mFullLabel;
169     }
170 
getIcon()171     public Drawable getIcon() {
172         return mIcon;
173     }
174 
getDescription()175     public CharSequence getDescription() {
176         return mDescription;
177     }
178 
getPackageInfos(@onNull UserHandle user)179     private @NonNull List<PackageInfo> getPackageInfos(@NonNull UserHandle user) {
180         List<PackageInfo> apps = (mPmCache != null) ? mPmCache.getPackages(
181                 user.getIdentifier()) : null;
182         if (apps != null) {
183             if (mPackageName != null) {
184                 final int appCount = apps.size();
185                 for (int i = 0; i < appCount; i++) {
186                     final PackageInfo app = apps.get(i);
187                     if (mPackageName.equals(app.packageName)) {
188                         apps = new ArrayList<>(1);
189                         apps.add(app);
190                         return apps;
191                     }
192                 }
193             }
194             return apps;
195         }
196         int pkgQueryFlags = getPackageQueryFlags();
197         if (mPackageName == null) {
198             return mPm.getInstalledPackagesAsUser(pkgQueryFlags, user.getIdentifier());
199         } else {
200             try {
201                 final PackageInfo packageInfo = mPm.getPackageInfo(mPackageName, pkgQueryFlags);
202                 apps = new ArrayList<>(1);
203                 apps.add(packageInfo);
204                 return apps;
205             } catch (NameNotFoundException e) {
206                 return Collections.emptyList();
207             }
208         }
209     }
210 
loadPermissionApps()211     private List<PermissionApp> loadPermissionApps() {
212         PackageItemInfo groupInfo = Utils.getGroupInfo(mGroupName, mContext);
213         if (groupInfo == null) {
214             return Collections.emptyList();
215         }
216 
217         List<PermissionInfo> groupPermInfos = Utils.getGroupPermissionInfos(mGroupName, mContext);
218         if (groupPermInfos == null) {
219             return Collections.emptyList();
220         }
221         List<PermissionInfo> targetPermInfos = new ArrayList<PermissionInfo>(groupPermInfos.size());
222         for (int i = 0; i < groupPermInfos.size(); i++) {
223             PermissionInfo permInfo = groupPermInfos.get(i);
224             if ((permInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
225                     == PermissionInfo.PROTECTION_DANGEROUS
226                     && (permInfo.flags & PermissionInfo.FLAG_INSTALLED) != 0
227                     && (permInfo.flags & PermissionInfo.FLAG_REMOVED) == 0) {
228                 targetPermInfos.add(permInfo);
229             }
230         }
231 
232         PackageManager packageManager = mContext.getPackageManager();
233         CharSequence groupLabel = groupInfo.loadLabel(packageManager);
234         CharSequence fullGroupLabel = groupInfo.loadSafeLabel(packageManager, 0,
235                 TextUtils.SAFE_STRING_FLAG_TRIM | TextUtils.SAFE_STRING_FLAG_FIRST_LINE);
236 
237         ArrayList<PermissionApp> permApps = new ArrayList<>();
238 
239         UserManager userManager = mContext.getSystemService(UserManager.class);
240         for (UserHandle user : userManager.getUserProfiles()) {
241             List<PackageInfo> apps = getPackageInfos(user);
242             final int N = apps.size();
243             for (int i = 0; i < N; i++) {
244                 PackageInfo app = apps.get(i);
245                 if (app.requestedPermissions == null) {
246                     continue;
247                 }
248 
249                 for (int j = 0; j < app.requestedPermissions.length; j++) {
250                     String requestedPerm = app.requestedPermissions[j];
251 
252                     PermissionInfo requestedPermissionInfo = null;
253 
254                     for (PermissionInfo groupPermInfo : targetPermInfos) {
255                         if (requestedPerm.equals(groupPermInfo.name)) {
256                             requestedPermissionInfo = groupPermInfo;
257                             break;
258                         }
259                     }
260 
261                     if (requestedPermissionInfo == null) {
262                         continue;
263                     }
264 
265                     AppPermissionGroup group = AppPermissionGroup.create(mContext,
266                             app, groupInfo, groupPermInfos, groupLabel, fullGroupLabel, false);
267 
268                     if (group == null) {
269                         continue;
270                     }
271 
272                     Pair<String, Drawable> appData = null;
273                     if (mAppDataCache != null && !mSkipUi) {
274                         appData = mAppDataCache.getAppData(user.getIdentifier(),
275                                 app.applicationInfo);
276                     }
277 
278                     String label;
279                     if (mSkipUi) {
280                         label = app.packageName;
281                     } else if (appData != null) {
282                         label = appData.first;
283                     } else {
284                         label = app.applicationInfo.loadLabel(mPm).toString();
285                     }
286 
287                     Drawable icon = null;
288                     if (!mSkipUi) {
289                         if (appData != null) {
290                             icon = appData.second;
291                         } else {
292                             icon = Utils.getBadgedIcon(mContext, app.applicationInfo);
293                         }
294                     }
295 
296                     PermissionApp permApp = new PermissionApp(app.packageName, group, label, icon,
297                             app.applicationInfo);
298 
299                     permApps.add(permApp);
300                     break; // move to the next app.
301                 }
302             }
303         }
304 
305         Collections.sort(permApps);
306 
307         return permApps;
308     }
309 
createMap(List<PermissionApp> result)310     private void createMap(List<PermissionApp> result) {
311         mAppLookup = new ArrayMap<>();
312         for (PermissionApp app : result) {
313             mAppLookup.put(app.getKey(), app);
314         }
315         mPermApps = result;
316     }
317 
loadGroupInfo()318     private void loadGroupInfo() {
319         PackageItemInfo info;
320         try {
321             info = mPm.getPermissionGroupInfo(mGroupName, 0);
322         } catch (PackageManager.NameNotFoundException e) {
323             try {
324                 PermissionInfo permInfo = mPm.getPermissionInfo(mGroupName, 0);
325                 if ((permInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
326                         != PermissionInfo.PROTECTION_DANGEROUS) {
327                     Log.w(LOG_TAG, mGroupName + " is not a runtime permission");
328                     return;
329                 }
330                 info = permInfo;
331             } catch (NameNotFoundException reallyNotFound) {
332                 Log.w(LOG_TAG, "Can't find permission: " + mGroupName, reallyNotFound);
333                 return;
334             }
335         }
336         mLabel = info.loadLabel(mPm);
337         mFullLabel = info.loadSafeLabel(mPm, 0,
338                 TextUtils.SAFE_STRING_FLAG_TRIM | TextUtils.SAFE_STRING_FLAG_FIRST_LINE);
339         if (info.icon != 0) {
340             mIcon = info.loadUnbadgedIcon(mPm);
341         } else {
342             mIcon = mContext.getDrawable(R.drawable.ic_perm_device_info);
343         }
344         mIcon = Utils.applyTint(mContext, mIcon, android.R.attr.colorControlNormal);
345         if (info instanceof PermissionGroupInfo) {
346             mDescription = ((PermissionGroupInfo) info).loadDescription(mPm);
347         } else if (info instanceof PermissionInfo) {
348             mDescription = ((PermissionInfo) info).loadDescription(mPm);
349         }
350     }
351 
352     public static class PermissionApp implements Comparable<PermissionApp> {
353         private final String mPackageName;
354         private final AppPermissionGroup mAppPermissionGroup;
355         private String mLabel;
356         private Drawable mIcon;
357         private final ApplicationInfo mInfo;
358 
PermissionApp(String packageName, AppPermissionGroup appPermissionGroup, String label, Drawable icon, ApplicationInfo info)359         public PermissionApp(String packageName, AppPermissionGroup appPermissionGroup,
360                 String label, Drawable icon, ApplicationInfo info) {
361             mPackageName = packageName;
362             mAppPermissionGroup = appPermissionGroup;
363             mLabel = label;
364             mIcon = icon;
365             mInfo = info;
366         }
367 
getAppInfo()368         public ApplicationInfo getAppInfo() {
369             return mInfo;
370         }
371 
getKey()372         public String getKey() {
373             return mPackageName + getUid();
374         }
375 
getLabel()376         public String getLabel() {
377             return mLabel;
378         }
379 
getIcon()380         public Drawable getIcon() {
381             return mIcon;
382         }
383 
areRuntimePermissionsGranted()384         public boolean areRuntimePermissionsGranted() {
385             return mAppPermissionGroup.areRuntimePermissionsGranted();
386         }
387 
isReviewRequired()388         public boolean isReviewRequired() {
389             return mAppPermissionGroup.isReviewRequired();
390         }
391 
grantRuntimePermissions()392         public void grantRuntimePermissions() {
393             mAppPermissionGroup.grantRuntimePermissions(true, false);
394         }
395 
revokeRuntimePermissions()396         public void revokeRuntimePermissions() {
397             mAppPermissionGroup.revokeRuntimePermissions(false);
398         }
399 
isPolicyFixed()400         public boolean isPolicyFixed() {
401             return mAppPermissionGroup.isPolicyFixed();
402         }
403 
isSystemFixed()404         public boolean isSystemFixed() {
405             return mAppPermissionGroup.isSystemFixed();
406         }
407 
hasGrantedByDefaultPermissions()408         public boolean hasGrantedByDefaultPermissions() {
409             return mAppPermissionGroup.hasGrantedByDefaultPermission();
410         }
411 
doesSupportRuntimePermissions()412         public boolean doesSupportRuntimePermissions() {
413             return mAppPermissionGroup.doesSupportRuntimePermissions();
414         }
415 
getPackageName()416         public String getPackageName() {
417             return mPackageName;
418         }
419 
getPermissionGroup()420         public AppPermissionGroup getPermissionGroup() {
421             return mAppPermissionGroup;
422         }
423 
424         /**
425          * Load this app's label and icon if they were not previously loaded.
426          *
427          * @param appDataCache the cache of already-loaded labels and icons.
428          */
loadLabelAndIcon(@onNull AppDataCache appDataCache)429         public void loadLabelAndIcon(@NonNull AppDataCache appDataCache) {
430             if (mInfo.packageName.equals(mLabel) || mIcon == null) {
431                 Pair<String, Drawable> appData = appDataCache.getAppData(getUid(), mInfo);
432                 mLabel = appData.first;
433                 mIcon = appData.second;
434             }
435         }
436 
437         @Override
compareTo(PermissionApp another)438         public int compareTo(PermissionApp another) {
439             final int result = mLabel.compareTo(another.mLabel);
440             if (result == 0) {
441                 // Unbadged before badged.
442                 return getKey().compareTo(another.getKey());
443             }
444             return result;
445         }
446 
getUid()447         public int getUid() {
448             return mAppPermissionGroup.getApp().applicationInfo.uid;
449         }
450     }
451 
452     private class PermissionAppsLoader extends AsyncTask<Void, Void, List<PermissionApp>> {
453 
454         @Override
doInBackground(Void... args)455         protected List<PermissionApp> doInBackground(Void... args) {
456             return loadPermissionApps();
457         }
458 
459         @Override
onPostExecute(List<PermissionApp> result)460         protected void onPostExecute(List<PermissionApp> result) {
461             mRefreshing = false;
462             createMap(result);
463             if (mCallback != null) {
464                 mCallback.onPermissionsLoaded(PermissionApps.this);
465             }
466         }
467     }
468 
469     /**
470      * Class used to reduce the number of calls to the package manager.
471      * This caches app information so it should only be used across parallel PermissionApps
472      * instances, and should not be retained across UI refresh.
473      */
474     public static class PmCache {
475         private final SparseArray<List<PackageInfo>> mPackageInfoCache = new SparseArray<>();
476         private final PackageManager mPm;
477 
PmCache(PackageManager pm)478         public PmCache(PackageManager pm) {
479             mPm = pm;
480         }
481 
getPackages(int userId)482         public synchronized List<PackageInfo> getPackages(int userId) {
483             List<PackageInfo> ret = mPackageInfoCache.get(userId);
484             if (ret == null) {
485                 ret = mPm.getInstalledPackagesAsUser(getPackageQueryFlags(), userId);
486                 mPackageInfoCache.put(userId, ret);
487             }
488             return ret;
489         }
490     }
491 
492     /**
493      * Class used to reduce the number of calls to loading labels and icons.
494      * This caches app information so it should only be used across parallel PermissionApps
495      * instances, and should not be retained across UI refresh.
496      */
497     public static class AppDataCache {
498         private final @NonNull SparseArray<ArrayMap<String, Pair<String, Drawable>>> mCache =
499                 new SparseArray<>();
500         private final @NonNull PackageManager mPm;
501         private final @NonNull Context mContext;
502 
AppDataCache(@onNull PackageManager pm, @NonNull Context context)503         public AppDataCache(@NonNull PackageManager pm, @NonNull Context context) {
504             mPm = pm;
505             mContext = context;
506         }
507 
508         /**
509          * Get the label and icon for the given app.
510          *
511          * @param userId the user id.
512          * @param app The app
513          *
514          * @return a pair of the label and icon.
515          */
getAppData(int userId, @NonNull ApplicationInfo app)516         public @NonNull Pair<String, Drawable> getAppData(int userId,
517                 @NonNull ApplicationInfo app) {
518             ArrayMap<String, Pair<String, Drawable>> dataForUser = mCache.get(userId);
519             if (dataForUser == null) {
520                 dataForUser = new ArrayMap<>();
521                 mCache.put(userId, dataForUser);
522             }
523             Pair<String, Drawable> data = dataForUser.get(app.packageName);
524             if (data == null) {
525                 data = Pair.create(app.loadLabel(mPm).toString(),
526                         Utils.getBadgedIcon(mContext, app));
527                 dataForUser.put(app.packageName, data);
528             }
529             return data;
530         }
531     }
532 
533     public interface Callback {
onPermissionsLoaded(PermissionApps permissionApps)534         void onPermissionsLoaded(PermissionApps permissionApps);
535     }
536 
537     /**
538      * Class used to asynchronously load apps' labels and icons.
539      */
540     public static class AppDataLoader extends AsyncTask<PermissionApp, Void, Void> {
541 
542         private final Context mContext;
543         private final Runnable mCallback;
544 
AppDataLoader(Context context, Runnable callback)545         public AppDataLoader(Context context, Runnable callback) {
546             mContext = context;
547             mCallback = callback;
548         }
549 
550         @Override
doInBackground(PermissionApp... args)551         protected Void doInBackground(PermissionApp... args) {
552             AppDataCache appDataCache = new AppDataCache(mContext.getPackageManager(), mContext);
553             int numArgs = args.length;
554             for (int i = 0; i < numArgs; i++) {
555                 args[i].loadLabelAndIcon(appDataCache);
556             }
557             return null;
558         }
559 
560         @Override
onPostExecute(Void result)561         protected void onPostExecute(Void result) {
562             mCallback.run();
563         }
564     }
565 
getPackageQueryFlags()566     private static int getPackageQueryFlags() {
567         int pkgQueryFlags = PackageManager.GET_PERMISSIONS;
568         if (SdkLevel.isAtLeastS()) {
569             pkgQueryFlags = pkgQueryFlags | PackageManager.GET_ATTRIBUTIONS;
570         }
571         return pkgQueryFlags;
572     }
573 }
574