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