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