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.packageinstaller.permission.model;
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.PermissionInfo;
25 import android.graphics.drawable.Drawable;
26 import android.os.AsyncTask;
27 import android.os.Process;
28 import android.os.UserHandle;
29 import android.os.UserManager;
30 import android.util.ArrayMap;
31 import android.util.ArraySet;
32 import android.util.Log;
33 import android.util.SparseArray;
34 
35 import com.android.packageinstaller.R;
36 import com.android.packageinstaller.permission.utils.Utils;
37 
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.List;
41 
42 public class PermissionApps {
43     private static final String LOG_TAG = "PermissionApps";
44 
45     private final Context mContext;
46     private final String mGroupName;
47     private final PackageManager mPm;
48     private final Callback mCallback;
49 
50     private final PmCache mCache;
51 
52     private CharSequence mLabel;
53     private Drawable mIcon;
54     private List<PermissionApp> mPermApps;
55     // Map (pkg|uid) -> AppPermission
56     private ArrayMap<String, PermissionApp> mAppLookup;
57 
58     private boolean mSkipUi;
59     private boolean mRefreshing;
60 
PermissionApps(Context context, String groupName, Callback callback)61     public PermissionApps(Context context, String groupName, Callback callback) {
62         this(context, groupName, callback, null);
63     }
64 
PermissionApps(Context context, String groupName, Callback callback, PmCache cache)65     public PermissionApps(Context context, String groupName, Callback callback, PmCache cache) {
66         mCache = cache;
67         mContext = context;
68         mPm = mContext.getPackageManager();
69         mGroupName = groupName;
70         mCallback = callback;
71         loadGroupInfo();
72     }
73 
getGroupName()74     public String getGroupName() {
75         return mGroupName;
76     }
77 
loadNowWithoutUi()78     public void loadNowWithoutUi() {
79         mSkipUi = true;
80         createMap(loadPermissionApps());
81     }
82 
refresh(boolean getUiInfo)83     public void refresh(boolean getUiInfo) {
84         if (!mRefreshing) {
85             mRefreshing = true;
86             mSkipUi = !getUiInfo;
87             new PermissionAppsLoader().execute();
88         }
89     }
90 
getGrantedCount(ArraySet<String> launcherPkgs)91     public int getGrantedCount(ArraySet<String> launcherPkgs) {
92         int count = 0;
93         for (PermissionApp app : mPermApps) {
94             if (!Utils.shouldShowPermission(app)) {
95                 continue;
96             }
97             if (Utils.isSystem(app, launcherPkgs)) {
98                 // We default to not showing system apps, so hide them from count.
99                 continue;
100             }
101             if (app.areRuntimePermissionsGranted()) {
102                 count++;
103             }
104         }
105         return count;
106     }
107 
getTotalCount(ArraySet<String> launcherPkgs)108     public int getTotalCount(ArraySet<String> launcherPkgs) {
109         int count = 0;
110         for (PermissionApp app : mPermApps) {
111             if (!Utils.shouldShowPermission(app)) {
112                 continue;
113             }
114             if (Utils.isSystem(app, launcherPkgs)) {
115                 // We default to not showing system apps, so hide them from count.
116                 continue;
117             }
118             count++;
119         }
120         return count;
121     }
122 
getApps()123     public List<PermissionApp> getApps() {
124         return mPermApps;
125     }
126 
getApp(String key)127     public PermissionApp getApp(String key) {
128         return mAppLookup.get(key);
129     }
130 
getLabel()131     public CharSequence getLabel() {
132         return mLabel;
133     }
134 
getIcon()135     public Drawable getIcon() {
136         return mIcon;
137     }
138 
loadPermissionApps()139     private List<PermissionApp> loadPermissionApps() {
140         PackageItemInfo groupInfo = getGroupInfo(mGroupName);
141         if (groupInfo == null) {
142             return Collections.emptyList();
143         }
144 
145         List<PermissionInfo> groupPermInfos = getGroupPermissionInfos(mGroupName);
146         if (groupPermInfos == null) {
147             return Collections.emptyList();
148         }
149 
150         ArrayList<PermissionApp> permApps = new ArrayList<>();
151 
152         UserManager userManager = mContext.getSystemService(UserManager.class);
153         for (UserHandle user : userManager.getUserProfiles()) {
154             List<PackageInfo> apps = mCache != null ? mCache.getPackages(user.getIdentifier())
155                     : mPm.getInstalledPackagesAsUser(PackageManager.GET_PERMISSIONS,
156                             user.getIdentifier());
157 
158             final int N = apps.size();
159             for (int i = 0; i < N; i++) {
160                 PackageInfo app = apps.get(i);
161                 if (app.requestedPermissions == null) {
162                     continue;
163                 }
164 
165                 for (int j = 0; j < app.requestedPermissions.length; j++) {
166                     String requestedPerm = app.requestedPermissions[j];
167 
168                     PermissionInfo requestedPermissionInfo = null;
169 
170                     for (PermissionInfo groupPermInfo : groupPermInfos) {
171                         if (requestedPerm.equals(groupPermInfo.name)) {
172                             requestedPermissionInfo = groupPermInfo;
173                             break;
174                         }
175                     }
176 
177                     if (requestedPermissionInfo == null) {
178                         continue;
179                     }
180 
181                     if (requestedPermissionInfo.protectionLevel
182                                 != PermissionInfo.PROTECTION_DANGEROUS
183                             || (requestedPermissionInfo.flags
184                                 & PermissionInfo.FLAG_INSTALLED) == 0
185                             || (requestedPermissionInfo.flags
186                                 & PermissionInfo.FLAG_REMOVED) != 0) {
187                         continue;
188                     }
189 
190                     AppPermissionGroup group = AppPermissionGroup.create(mContext,
191                             app, groupInfo, groupPermInfos, user);
192 
193                     if (group == null) {
194                         continue;
195                     }
196 
197                     String label = mSkipUi ? app.packageName
198                             : app.applicationInfo.loadLabel(mPm).toString();
199 
200                     Drawable icon = null;
201                     if (!mSkipUi) {
202                         UserHandle userHandle = new UserHandle(
203                                 UserHandle.getUserId(group.getApp().applicationInfo.uid));
204 
205                         icon = mPm.getUserBadgedIcon(
206                                 mPm.loadUnbadgedItemIcon(app.applicationInfo, app.applicationInfo),
207                                 userHandle);
208                     }
209 
210                     PermissionApp permApp = new PermissionApp(app.packageName, group, label, icon,
211                             app.applicationInfo);
212 
213                     permApps.add(permApp);
214                     break; // move to the next app.
215                 }
216             }
217         }
218 
219         Collections.sort(permApps);
220 
221         return permApps;
222     }
223 
createMap(List<PermissionApp> result)224     private void createMap(List<PermissionApp> result) {
225         mAppLookup = new ArrayMap<>();
226         for (PermissionApp app : result) {
227             mAppLookup.put(app.getKey(), app);
228         }
229         mPermApps = result;
230     }
231 
getGroupInfo(String groupName)232     private PackageItemInfo getGroupInfo(String groupName) {
233         try {
234             return mContext.getPackageManager().getPermissionGroupInfo(groupName, 0);
235         } catch (NameNotFoundException e) {
236             /* ignore */
237         }
238         try {
239             return mContext.getPackageManager().getPermissionInfo(groupName, 0);
240         } catch (NameNotFoundException e2) {
241             /* ignore */
242         }
243         return null;
244     }
245 
getGroupPermissionInfos(String groupName)246     private List<PermissionInfo> getGroupPermissionInfos(String groupName) {
247         try {
248             return mContext.getPackageManager().queryPermissionsByGroup(groupName, 0);
249         } catch (NameNotFoundException e) {
250             /* ignore */
251         }
252         try {
253             PermissionInfo permissionInfo = mContext.getPackageManager()
254                     .getPermissionInfo(groupName, 0);
255             List<PermissionInfo> permissions = new ArrayList<>();
256             permissions.add(permissionInfo);
257             return permissions;
258         } catch (NameNotFoundException e2) {
259             /* ignore */
260         }
261         return null;
262     }
263 
loadGroupInfo()264     private void loadGroupInfo() {
265         PackageItemInfo info;
266         try {
267             info = mPm.getPermissionGroupInfo(mGroupName, 0);
268         } catch (PackageManager.NameNotFoundException e) {
269             try {
270                 PermissionInfo permInfo = mPm.getPermissionInfo(mGroupName, 0);
271                 if (permInfo.protectionLevel != PermissionInfo.PROTECTION_DANGEROUS) {
272                     Log.w(LOG_TAG, mGroupName + " is not a runtime permission");
273                     return;
274                 }
275                 info = permInfo;
276             } catch (NameNotFoundException reallyNotFound) {
277                 Log.w(LOG_TAG, "Can't find permission: " + mGroupName, reallyNotFound);
278                 return;
279             }
280         }
281         mLabel = info.loadLabel(mPm);
282         if (info.icon != 0) {
283             mIcon = info.loadUnbadgedIcon(mPm);
284         } else {
285             mIcon = mContext.getDrawable(R.drawable.ic_perm_device_info);
286         }
287         mIcon = Utils.applyTint(mContext, mIcon, android.R.attr.colorControlNormal);
288     }
289 
290     public static class PermissionApp implements Comparable<PermissionApp> {
291         private final String mPackageName;
292         private final AppPermissionGroup mAppPermissionGroup;
293         private final String mLabel;
294         private final Drawable mIcon;
295         private final ApplicationInfo mInfo;
296 
PermissionApp(String packageName, AppPermissionGroup appPermissionGroup, String label, Drawable icon, ApplicationInfo info)297         public PermissionApp(String packageName, AppPermissionGroup appPermissionGroup,
298                 String label, Drawable icon, ApplicationInfo info) {
299             mPackageName = packageName;
300             mAppPermissionGroup = appPermissionGroup;
301             mLabel = label;
302             mIcon = icon;
303             mInfo = info;
304         }
305 
getAppInfo()306         public ApplicationInfo getAppInfo() {
307             return mInfo;
308         }
309 
getKey()310         public String getKey() {
311             return mPackageName + getUid();
312         }
313 
getLabel()314         public String getLabel() {
315             return mLabel;
316         }
317 
getIcon()318         public Drawable getIcon() {
319             return mIcon;
320         }
321 
areRuntimePermissionsGranted()322         public boolean areRuntimePermissionsGranted() {
323             return mAppPermissionGroup.areRuntimePermissionsGranted();
324         }
325 
isReviewRequired()326         public boolean isReviewRequired() {
327             return mAppPermissionGroup.isReviewRequired();
328         }
329 
grantRuntimePermissions()330         public void grantRuntimePermissions() {
331             mAppPermissionGroup.grantRuntimePermissions(false);
332         }
333 
revokeRuntimePermissions()334         public void revokeRuntimePermissions() {
335             mAppPermissionGroup.revokeRuntimePermissions(false);
336         }
337 
isPolicyFixed()338         public boolean isPolicyFixed() {
339             return mAppPermissionGroup.isPolicyFixed();
340         }
341 
isSystemFixed()342         public boolean isSystemFixed() {
343             return mAppPermissionGroup.isSystemFixed();
344         }
345 
hasGrantedByDefaultPermissions()346         public boolean hasGrantedByDefaultPermissions() {
347             return mAppPermissionGroup.hasGrantedByDefaultPermission();
348         }
349 
hasRuntimePermissions()350         public boolean hasRuntimePermissions() {
351             return mAppPermissionGroup.hasRuntimePermission();
352         }
353 
getUserId()354         public int getUserId() {
355             return mAppPermissionGroup.getUserId();
356         }
357 
getPackageName()358         public String getPackageName() {
359             return mPackageName;
360         }
361 
getPermissionGroup()362         public AppPermissionGroup getPermissionGroup() {
363             return mAppPermissionGroup;
364         }
365 
366         @Override
compareTo(PermissionApp another)367         public int compareTo(PermissionApp another) {
368             final int result = mLabel.compareTo(another.mLabel);
369             if (result == 0) {
370                 // Unbadged before badged.
371                 return getKey().compareTo(another.getKey());
372             }
373             return result;
374         }
375 
getUid()376         public int getUid() {
377             return mAppPermissionGroup.getApp().applicationInfo.uid;
378         }
379     }
380 
381     private class PermissionAppsLoader extends AsyncTask<Void, Void, List<PermissionApp>> {
382 
383         @Override
doInBackground(Void... args)384         protected List<PermissionApp> doInBackground(Void... args) {
385             return loadPermissionApps();
386         }
387 
388         @Override
onPostExecute(List<PermissionApp> result)389         protected void onPostExecute(List<PermissionApp> result) {
390             mRefreshing = false;
391             createMap(result);
392             if (mCallback != null) {
393                 mCallback.onPermissionsLoaded(PermissionApps.this);
394             }
395         }
396     }
397 
398     /**
399      * Class used to reduce the number of calls to the package manager.
400      * This caches app information so it should only be used across parallel PermissionApps
401      * instances, and should not be retained across UI refresh.
402      */
403     public static class PmCache {
404         private final SparseArray<List<PackageInfo>> mPackageInfoCache = new SparseArray<>();
405         private final PackageManager mPm;
406 
PmCache(PackageManager pm)407         public PmCache(PackageManager pm) {
408             mPm = pm;
409         }
410 
getPackages(int userId)411         public synchronized List<PackageInfo> getPackages(int userId) {
412             List<PackageInfo> ret = mPackageInfoCache.get(userId);
413             if (ret == null) {
414                 ret = mPm.getInstalledPackagesAsUser(PackageManager.GET_PERMISSIONS, userId);
415                 mPackageInfoCache.put(userId, ret);
416             }
417             return ret;
418         }
419     }
420 
421     public interface Callback {
onPermissionsLoaded(PermissionApps permissionApps)422         void onPermissionsLoaded(PermissionApps permissionApps);
423     }
424 }
425