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