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.permissioncontroller.R;
40 import com.android.permissioncontroller.permission.model.AppPermissionGroup;
41 import com.android.permissioncontroller.permission.utils.Utils;
42 
43 import java.util.ArrayList;
44 import java.util.Collections;
45 import java.util.List;
46 
47 /**
48  * @deprecated Use classes from permission.ui.model instead
49  */
50 @Deprecated
51 public class PermissionApps {
52     private static final String LOG_TAG = "PermissionApps";
53 
54     private final Context mContext;
55     private final String mGroupName;
56     private final String mPackageName;
57     private final PackageManager mPm;
58     private final Callback mCallback;
59 
60     private final @Nullable PmCache mPmCache;
61     private final @Nullable AppDataCache mAppDataCache;
62 
63     private CharSequence mLabel;
64     private CharSequence mFullLabel;
65     private Drawable mIcon;
66     private @Nullable CharSequence mDescription;
67     private List<PermissionApp> mPermApps;
68     // Map (pkg|uid) -> AppPermission
69     private ArrayMap<String, PermissionApp> mAppLookup;
70 
71     private boolean mSkipUi;
72     private boolean mRefreshing;
73 
PermissionApps(Context context, String groupName, Callback callback)74     public PermissionApps(Context context, String groupName, Callback callback) {
75         this(context, groupName, null, callback, null, null);
76     }
77 
PermissionApps(Context context, String groupName, String packageName, Callback callback, @Nullable PmCache pmCache, @Nullable AppDataCache appDataCache)78     public PermissionApps(Context context, String groupName, String packageName,
79             Callback callback, @Nullable PmCache pmCache, @Nullable AppDataCache appDataCache) {
80         mPmCache = pmCache;
81         mAppDataCache = appDataCache;
82         mContext = context;
83         mPm = mContext.getPackageManager();
84         mGroupName = groupName;
85         mCallback = callback;
86         mPackageName = packageName;
87         loadGroupInfo();
88     }
89 
getGroupName()90     public String getGroupName() {
91         return mGroupName;
92     }
93 
94     /**
95      * Start an async refresh and call back the registered call back once done.
96      *
97      * @param getUiInfo If the UI info should be updated
98      */
refresh(boolean getUiInfo)99     public void refresh(boolean getUiInfo) {
100         if (!mRefreshing) {
101             mRefreshing = true;
102             mSkipUi = !getUiInfo;
103             new PermissionAppsLoader().execute();
104         }
105     }
106 
107     /**
108      * Refresh the state and do not return until it finishes. Should not be called while an {@link
109      * #refresh async referesh} is in progress.
110      */
refreshSync(boolean getUiInfo)111     public void refreshSync(boolean getUiInfo) {
112         mSkipUi = !getUiInfo;
113         createMap(loadPermissionApps());
114     }
115 
getGrantedCount()116     public int getGrantedCount() {
117         int count = 0;
118         for (PermissionApp app : mPermApps) {
119             if (!app.getAppInfo().enabled) {
120                 continue;
121             }
122             if (!Utils.shouldShowPermission(mContext, app.getPermissionGroup())) {
123                 continue;
124             }
125             if (!Utils.isGroupOrBgGroupUserSensitive(app.mAppPermissionGroup)) {
126                 // We default to not showing system apps, so hide them from count.
127                 continue;
128             }
129             if (app.areRuntimePermissionsGranted()) {
130                 count++;
131             }
132         }
133         return count;
134     }
135 
getTotalCount()136     public int getTotalCount() {
137         int count = 0;
138         for (PermissionApp app : mPermApps) {
139             if (!app.getAppInfo().enabled) {
140                 continue;
141             }
142             if (!Utils.shouldShowPermission(mContext, app.getPermissionGroup())) {
143                 continue;
144             }
145             if (!Utils.isGroupOrBgGroupUserSensitive(app.mAppPermissionGroup)) {
146                 // We default to not showing system apps, so hide them from count.
147                 continue;
148             }
149             count++;
150         }
151         return count;
152     }
153 
getApps()154     public List<PermissionApp> getApps() {
155         return mPermApps;
156     }
157 
getApp(String key)158     public PermissionApp getApp(String key) {
159         return mAppLookup.get(key);
160     }
161 
getLabel()162     public CharSequence getLabel() {
163         return mLabel;
164     }
165 
getFullLabel()166     public CharSequence getFullLabel() {
167         return mFullLabel;
168     }
169 
getIcon()170     public Drawable getIcon() {
171         return mIcon;
172     }
173 
getDescription()174     public CharSequence getDescription() {
175         return mDescription;
176     }
177 
getPackageInfos(@onNull UserHandle user)178     private @NonNull List<PackageInfo> getPackageInfos(@NonNull UserHandle user) {
179         List<PackageInfo> apps = (mPmCache != null) ? mPmCache.getPackages(
180                 user.getIdentifier()) : null;
181         if (apps != null) {
182             if (mPackageName != null) {
183                 final int appCount = apps.size();
184                 for (int i = 0; i < appCount; i++) {
185                     final PackageInfo app = apps.get(i);
186                     if (mPackageName.equals(app.packageName)) {
187                         apps = new ArrayList<>(1);
188                         apps.add(app);
189                         return apps;
190                     }
191                 }
192             }
193             return apps;
194         }
195         if (mPackageName == null) {
196             return mPm.getInstalledPackagesAsUser(PackageManager.GET_PERMISSIONS,
197                     user.getIdentifier());
198         } else {
199             try {
200                 final PackageInfo packageInfo = mPm.getPackageInfo(mPackageName,
201                         PackageManager.GET_PERMISSIONS);
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         @Override
compareTo(PermissionApp another)425         public int compareTo(PermissionApp another) {
426             final int result = mLabel.compareTo(another.mLabel);
427             if (result == 0) {
428                 // Unbadged before badged.
429                 return getKey().compareTo(another.getKey());
430             }
431             return result;
432         }
433 
getUid()434         public int getUid() {
435             return mAppPermissionGroup.getApp().applicationInfo.uid;
436         }
437     }
438 
439     private class PermissionAppsLoader extends AsyncTask<Void, Void, List<PermissionApp>> {
440 
441         @Override
doInBackground(Void... args)442         protected List<PermissionApp> doInBackground(Void... args) {
443             return loadPermissionApps();
444         }
445 
446         @Override
onPostExecute(List<PermissionApp> result)447         protected void onPostExecute(List<PermissionApp> result) {
448             mRefreshing = false;
449             createMap(result);
450             if (mCallback != null) {
451                 mCallback.onPermissionsLoaded(PermissionApps.this);
452             }
453         }
454     }
455 
456     /**
457      * Class used to reduce the number of calls to the package manager.
458      * This caches app information so it should only be used across parallel PermissionApps
459      * instances, and should not be retained across UI refresh.
460      */
461     public static class PmCache {
462         private final SparseArray<List<PackageInfo>> mPackageInfoCache = new SparseArray<>();
463         private final PackageManager mPm;
464 
PmCache(PackageManager pm)465         public PmCache(PackageManager pm) {
466             mPm = pm;
467         }
468 
getPackages(int userId)469         public synchronized List<PackageInfo> getPackages(int userId) {
470             List<PackageInfo> ret = mPackageInfoCache.get(userId);
471             if (ret == null) {
472                 ret = mPm.getInstalledPackagesAsUser(PackageManager.GET_PERMISSIONS, userId);
473                 mPackageInfoCache.put(userId, ret);
474             }
475             return ret;
476         }
477     }
478 
479     /**
480      * Class used to reduce the number of calls to loading labels and icons.
481      * This caches app information so it should only be used across parallel PermissionApps
482      * instances, and should not be retained across UI refresh.
483      */
484     public static class AppDataCache {
485         private final @NonNull SparseArray<ArrayMap<String, Pair<String, Drawable>>> mCache =
486                 new SparseArray<>();
487         private final @NonNull PackageManager mPm;
488         private final @NonNull Context mContext;
489 
AppDataCache(@onNull PackageManager pm, @NonNull Context context)490         public AppDataCache(@NonNull PackageManager pm, @NonNull Context context) {
491             mPm = pm;
492             mContext = context;
493         }
494 
495         /**
496          * Get the label and icon for the given app.
497          *
498          * @param userId the user id.
499          * @param app The app
500          *
501          * @return a pair of the label and icon.
502          */
getAppData(int userId, @NonNull ApplicationInfo app)503         public @NonNull Pair<String, Drawable> getAppData(int userId,
504                 @NonNull ApplicationInfo app) {
505             ArrayMap<String, Pair<String, Drawable>> dataForUser = mCache.get(userId);
506             if (dataForUser == null) {
507                 dataForUser = new ArrayMap<>();
508                 mCache.put(userId, dataForUser);
509             }
510             Pair<String, Drawable> data = dataForUser.get(app.packageName);
511             if (data == null) {
512                 data = Pair.create(app.loadLabel(mPm).toString(),
513                         Utils.getBadgedIcon(mContext, app));
514                 dataForUser.put(app.packageName, data);
515             }
516             return data;
517         }
518     }
519 
520     public interface Callback {
onPermissionsLoaded(PermissionApps permissionApps)521         void onPermissionsLoaded(PermissionApps permissionApps);
522     }
523 }
524