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 
     17 package com.android.packageinstaller.permission.model;
     18 
     19 import android.app.ActivityManager;
     20 import android.app.AppOpsManager;
     21 import android.content.Context;
     22 import android.content.pm.PackageInfo;
     23 import android.content.pm.PackageItemInfo;
     24 import android.content.pm.PackageManager;
     25 import android.content.pm.PermissionGroupInfo;
     26 import android.content.pm.PermissionInfo;
     27 import android.os.Build;
     28 import android.os.UserHandle;
     29 import android.util.ArrayMap;
     30 
     31 import com.android.packageinstaller.R;
     32 import com.android.packageinstaller.permission.utils.LocationUtils;
     33 
     34 import java.util.ArrayList;
     35 import java.util.List;
     36 
     37 public final class AppPermissionGroup implements Comparable<AppPermissionGroup> {
     38     private static final String PLATFORM_PACKAGE_NAME = "android";
     39 
     40     private static final String KILL_REASON_APP_OP_CHANGE = "Permission related app op changed";
     41 
     42     private final Context mContext;
     43     private final UserHandle mUserHandle;
     44     private final PackageManager mPackageManager;
     45     private final AppOpsManager mAppOps;
     46     private final ActivityManager mActivityManager;
     47 
     48     private final PackageInfo mPackageInfo;
     49     private final String mName;
     50     private final String mDeclaringPackage;
     51     private final CharSequence mLabel;
     52     private final CharSequence mDescription;
     53     private final ArrayMap<String, Permission> mPermissions = new ArrayMap<>();
     54     private final String mIconPkg;
     55     private final int mIconResId;
     56 
     57     private final boolean mAppSupportsRuntimePermissions;
     58 
     59     public static AppPermissionGroup create(Context context, PackageInfo packageInfo,
     60             String permissionName) {
     61         PermissionInfo permissionInfo;
     62         try {
     63             permissionInfo = context.getPackageManager().getPermissionInfo(permissionName, 0);
     64         } catch (PackageManager.NameNotFoundException e) {
     65             return null;
     66         }
     67 
     68         if (permissionInfo.protectionLevel != PermissionInfo.PROTECTION_DANGEROUS
     69                 || (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) == 0
     70                 || (permissionInfo.flags & PermissionInfo.FLAG_HIDDEN) != 0) {
     71             return null;
     72         }
     73 
     74         PackageItemInfo groupInfo = permissionInfo;
     75         if (permissionInfo.group != null) {
     76             try {
     77                 groupInfo = context.getPackageManager().getPermissionGroupInfo(
     78                         permissionInfo.group, 0);
     79             } catch (PackageManager.NameNotFoundException e) {
     80                 /* ignore */
     81             }
     82         }
     83 
     84         List<PermissionInfo> permissionInfos = null;
     85         if (groupInfo instanceof PermissionGroupInfo) {
     86             try {
     87                 permissionInfos = context.getPackageManager().queryPermissionsByGroup(
     88                         groupInfo.name, 0);
     89             } catch (PackageManager.NameNotFoundException e) {
     90                 /* ignore */
     91             }
     92         }
     93 
     94         return create(context, packageInfo, groupInfo, permissionInfos,
     95                 new UserHandle(context.getUserId()));
     96     }
     97 
     98     public static AppPermissionGroup create(Context context, PackageInfo packageInfo,
     99             PackageItemInfo groupInfo, List<PermissionInfo> permissionInfos,
    100             UserHandle userHandle) {
    101 
    102         AppPermissionGroup group = new AppPermissionGroup(context, packageInfo, groupInfo.name,
    103                 groupInfo.packageName, groupInfo.loadLabel(context.getPackageManager()),
    104                 loadGroupDescription(context, groupInfo), groupInfo.packageName, groupInfo.icon,
    105                 userHandle);
    106 
    107         if (groupInfo instanceof PermissionInfo) {
    108             permissionInfos = new ArrayList<>();
    109             permissionInfos.add((PermissionInfo) groupInfo);
    110         }
    111 
    112         if (permissionInfos == null || permissionInfos.isEmpty()) {
    113             return null;
    114         }
    115 
    116         final int permissionCount = packageInfo.requestedPermissions.length;
    117         for (int i = 0; i < permissionCount; i++) {
    118             String requestedPermission = packageInfo.requestedPermissions[i];
    119 
    120             PermissionInfo requestedPermissionInfo = null;
    121 
    122             for (PermissionInfo permissionInfo : permissionInfos) {
    123                 if (requestedPermission.equals(permissionInfo.name)) {
    124                     requestedPermissionInfo = permissionInfo;
    125                     break;
    126                 }
    127             }
    128 
    129             if (requestedPermissionInfo == null) {
    130                 continue;
    131             }
    132 
    133             // Collect only runtime permissions.
    134             if (requestedPermissionInfo.protectionLevel != PermissionInfo.PROTECTION_DANGEROUS) {
    135                 continue;
    136             }
    137 
    138             // Don't allow toggle of non platform defined permissions for legacy apps via app ops.
    139             if (packageInfo.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1
    140                     && !PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName)) {
    141                 continue;
    142             }
    143 
    144             final boolean granted = (packageInfo.requestedPermissionsFlags[i]
    145                     & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0;
    146 
    147             final int appOp = PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName)
    148                     ? AppOpsManager.permissionToOpCode(requestedPermissionInfo.name)
    149                     : AppOpsManager.OP_NONE;
    150 
    151             final boolean appOpAllowed = appOp != AppOpsManager.OP_NONE
    152                     && context.getSystemService(AppOpsManager.class).checkOp(appOp,
    153                     packageInfo.applicationInfo.uid, packageInfo.packageName)
    154                     == AppOpsManager.MODE_ALLOWED;
    155 
    156             final int flags = context.getPackageManager().getPermissionFlags(
    157                     requestedPermission, packageInfo.packageName, userHandle);
    158 
    159             Permission permission = new Permission(requestedPermission, granted,
    160                     appOp, appOpAllowed, flags);
    161             group.addPermission(permission);
    162         }
    163 
    164         return group;
    165     }
    166 
    167     private static CharSequence loadGroupDescription(Context context, PackageItemInfo group) {
    168         CharSequence description = null;
    169         if (group instanceof PermissionGroupInfo) {
    170             description = ((PermissionGroupInfo) group).loadDescription(
    171                     context.getPackageManager());
    172         } else if (group instanceof PermissionInfo) {
    173             description = ((PermissionInfo) group).loadDescription(
    174                     context.getPackageManager());
    175         }
    176 
    177         if (description == null || description.length() <= 0) {
    178             description = context.getString(R.string.default_permission_description);
    179         }
    180 
    181         return description;
    182     }
    183 
    184     private AppPermissionGroup(Context context, PackageInfo packageInfo, String name,
    185             String declaringPackage, CharSequence label, CharSequence description,
    186             String iconPkg, int iconResId, UserHandle userHandle) {
    187         mContext = context;
    188         mUserHandle = userHandle;
    189         mPackageManager = mContext.getPackageManager();
    190         mPackageInfo = packageInfo;
    191         mAppSupportsRuntimePermissions = packageInfo.applicationInfo
    192                 .targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
    193         mAppOps = context.getSystemService(AppOpsManager.class);
    194         mActivityManager = context.getSystemService(ActivityManager.class);
    195         mDeclaringPackage = declaringPackage;
    196         mName = name;
    197         mLabel = label;
    198         mDescription = description;
    199         if (iconResId != 0) {
    200             mIconPkg = iconPkg;
    201             mIconResId = iconResId;
    202         } else {
    203             mIconPkg = context.getPackageName();
    204             mIconResId = R.drawable.ic_perm_device_info;
    205         }
    206     }
    207 
    208     public boolean hasRuntimePermission() {
    209         return mAppSupportsRuntimePermissions;
    210     }
    211 
    212 
    213     public boolean hasGrantedByDefaultPermission() {
    214         final int permissionCount = mPermissions.size();
    215         for (int i = 0; i < permissionCount; i++) {
    216             Permission permission = mPermissions.valueAt(i);
    217             if (permission.isGrantedByDefault()) {
    218                 return true;
    219             }
    220         }
    221         return false;
    222     }
    223 
    224     public boolean hasAppOpPermission() {
    225         final int permissionCount = mPermissions.size();
    226         for (int i = 0; i < permissionCount; i++) {
    227             Permission permission = mPermissions.valueAt(i);
    228             if (permission.getAppOp() != AppOpsManager.OP_NONE) {
    229                 return true;
    230             }
    231         }
    232         return false;
    233     }
    234 
    235     public PackageInfo getApp() {
    236         return mPackageInfo;
    237     }
    238 
    239     public String getName() {
    240         return mName;
    241     }
    242 
    243     public String getDeclaringPackage() {
    244         return mDeclaringPackage;
    245     }
    246 
    247     public String getIconPkg() {
    248         return mIconPkg;
    249     }
    250 
    251     public int getIconResId() {
    252         return mIconResId;
    253     }
    254 
    255     public CharSequence getLabel() {
    256         return mLabel;
    257     }
    258 
    259     public CharSequence getDescription() {
    260         return mDescription;
    261     }
    262 
    263     public boolean hasPermission(String permission) {
    264         return mPermissions.get(permission) != null;
    265     }
    266 
    267     public boolean areRuntimePermissionsGranted() {
    268         if (LocationUtils.isLocationGroupAndProvider(mName, mPackageInfo.packageName)) {
    269             return LocationUtils.isLocationEnabled(mContext);
    270         }
    271         final int permissionCount = mPermissions.size();
    272         for (int i = 0; i < permissionCount; i++) {
    273             Permission permission = mPermissions.valueAt(i);
    274             if (mAppSupportsRuntimePermissions) {
    275                 if (permission.isGranted()) {
    276                     return true;
    277                 }
    278             } else if (permission.isGranted() && ((permission.getAppOp()
    279                     != AppOpsManager.OP_NONE && permission.isAppOpAllowed())
    280                     || permission.getAppOp() == AppOpsManager.OP_NONE)) {
    281                 return true;
    282             }
    283         }
    284         return false;
    285     }
    286 
    287     public boolean grantRuntimePermissions(boolean fixedByTheUser) {
    288         final boolean isSharedUser = mPackageInfo.sharedUserId != null;
    289         final int uid = mPackageInfo.applicationInfo.uid;
    290 
    291         // We toggle permissions only to apps that support runtime
    292         // permissions, otherwise we toggle the app op corresponding
    293         // to the permission if the permission is granted to the app.
    294         for (Permission permission : mPermissions.values()) {
    295             if (mAppSupportsRuntimePermissions) {
    296                 // Do not touch permissions fixed by the system.
    297                 if (permission.isSystemFixed()) {
    298                     return false;
    299                 }
    300 
    301                 // Ensure the permission app op enabled before the permission grant.
    302                 if (permission.hasAppOp() && !permission.isAppOpAllowed()) {
    303                     permission.setAppOpAllowed(true);
    304                     mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED);
    305                 }
    306 
    307                 // Grant the permission if needed.
    308                 if (!permission.isGranted()) {
    309                     permission.setGranted(true);
    310                     mPackageManager.grantRuntimePermission(mPackageInfo.packageName,
    311                             permission.getName(), mUserHandle);
    312                 }
    313 
    314                 // Update the permission flags.
    315                 if (!fixedByTheUser) {
    316                     // Now the apps can ask for the permission as the user
    317                     // no longer has it fixed in a denied state.
    318                     if (permission.isUserFixed() || permission.isUserSet()) {
    319                         permission.setUserFixed(false);
    320                         permission.setUserSet(true);
    321                         mPackageManager.updatePermissionFlags(permission.getName(),
    322                                 mPackageInfo.packageName,
    323                                 PackageManager.FLAG_PERMISSION_USER_FIXED
    324                                         | PackageManager.FLAG_PERMISSION_USER_SET,
    325                                 0, mUserHandle);
    326                     }
    327                 }
    328             } else {
    329                 // Legacy apps cannot have a not granted permission but just in case.
    330                 // Also if the permissions has no corresponding app op, then it is a
    331                 // third-party one and we do not offer toggling of such permissions.
    332                 if (!permission.isGranted() || !permission.hasAppOp()) {
    333                     continue;
    334                 }
    335 
    336                 if (!permission.isAppOpAllowed()) {
    337                     permission.setAppOpAllowed(true);
    338                     // It this is a shared user we want to enable the app op for all
    339                     // packages in the shared user to match the behavior of this
    340                     // shared user having a runtime permission.
    341                     if (isSharedUser) {
    342                         // Enable the app op.
    343                         String[] packageNames = mPackageManager.getPackagesForUid(uid);
    344                         for (String packageName : packageNames) {
    345                             mAppOps.setUidMode(permission.getAppOp(), uid,
    346                                     AppOpsManager.MODE_ALLOWED);
    347                         }
    348                     } else {
    349                         // Enable the app op.
    350                         mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED);
    351                     }
    352 
    353                     // Mark that the permission should not be be granted on upgrade
    354                     // when the app begins supporting runtime permissions.
    355                     if (permission.shouldRevokeOnUpgrade()) {
    356                         permission.setRevokeOnUpgrade(false);
    357                         mPackageManager.updatePermissionFlags(permission.getName(),
    358                                 mPackageInfo.packageName,
    359                                 PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE,
    360                                 0, mUserHandle);
    361                     }
    362 
    363                     // Legacy apps do not know that they have to retry access to a
    364                     // resource due to changes in runtime permissions (app ops in this
    365                     // case). Therefore, we restart them on app op change, so they
    366                     // can pick up the change.
    367                     mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE);
    368                 }
    369             }
    370         }
    371 
    372         return true;
    373     }
    374 
    375     public boolean revokeRuntimePermissions(boolean fixedByTheUser) {
    376         final boolean isSharedUser = mPackageInfo.sharedUserId != null;
    377         final int uid = mPackageInfo.applicationInfo.uid;
    378 
    379         // We toggle permissions only to apps that support runtime
    380         // permissions, otherwise we toggle the app op corresponding
    381         // to the permission if the permission is granted to the app.
    382         for (Permission permission : mPermissions.values()) {
    383             if (mAppSupportsRuntimePermissions) {
    384                 // Do not touch permissions fixed by the system.
    385                 if (permission.isSystemFixed()) {
    386                     return false;
    387                 }
    388 
    389                 // Revoke the permission if needed.
    390                 if (permission.isGranted()) {
    391                     permission.setGranted(false);
    392                     mPackageManager.revokeRuntimePermission(mPackageInfo.packageName,
    393                             permission.getName(), mUserHandle);
    394                 }
    395 
    396                 // Update the permission flags.
    397                 if (fixedByTheUser) {
    398                     // Take a note that the user fixed the permission.
    399                     if (permission.isUserSet() || !permission.isUserFixed()) {
    400                         permission.setUserSet(false);
    401                         permission.setUserFixed(true);
    402                         mPackageManager.updatePermissionFlags(permission.getName(),
    403                                 mPackageInfo.packageName,
    404                                 PackageManager.FLAG_PERMISSION_USER_SET
    405                                         | PackageManager.FLAG_PERMISSION_USER_FIXED,
    406                                 PackageManager.FLAG_PERMISSION_USER_FIXED,
    407                                 mUserHandle);
    408                     }
    409                 } else {
    410                     if (!permission.isUserSet()) {
    411                         permission.setUserSet(true);
    412                         // Take a note that the user already chose once.
    413                         mPackageManager.updatePermissionFlags(permission.getName(),
    414                                 mPackageInfo.packageName,
    415                                 PackageManager.FLAG_PERMISSION_USER_SET,
    416                                 PackageManager.FLAG_PERMISSION_USER_SET,
    417                                 mUserHandle);
    418                     }
    419                 }
    420             } else {
    421                 // Legacy apps cannot have a non-granted permission but just in case.
    422                 // Also if the permission has no corresponding app op, then it is a
    423                 // third-party one and we do not offer toggling of such permissions.
    424                 if (!permission.isGranted() || !permission.hasAppOp()) {
    425                     continue;
    426                 }
    427 
    428                 if (permission.isAppOpAllowed()) {
    429                     permission.setAppOpAllowed(false);
    430                     // It this is a shared user we want to enable the app op for all
    431                     // packages the the shared user to match the behavior of this
    432                     // shared user having a runtime permission.
    433                     if (isSharedUser) {
    434                         String[] packageNames = mPackageManager.getPackagesForUid(uid);
    435                         for (String packageName : packageNames) {
    436                             // Disable the app op.
    437                             mAppOps.setUidMode(permission.getAppOp(), uid,
    438                                     AppOpsManager.MODE_IGNORED);
    439                         }
    440                     } else {
    441                         // Disable the app op.
    442                         mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_IGNORED);
    443                     }
    444 
    445                     // Mark that the permission should not be granted on upgrade
    446                     // when the app begins supporting runtime permissions.
    447                     if (!permission.shouldRevokeOnUpgrade()) {
    448                         permission.setRevokeOnUpgrade(true);
    449                         mPackageManager.updatePermissionFlags(permission.getName(),
    450                                 mPackageInfo.packageName,
    451                                 PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE,
    452                                 PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE,
    453                                 mUserHandle);
    454                     }
    455 
    456                     // Disabling an app op may put the app in a situation in which it
    457                     // has a handle to state it shouldn't have, so we have to kill the
    458                     // app. This matches the revoke runtime permission behavior.
    459                     mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE);
    460                 }
    461             }
    462         }
    463 
    464         return true;
    465     }
    466 
    467     public void setPolicyFixed() {
    468         final int permissionCount = mPermissions.size();
    469         for (int i = 0; i < permissionCount; i++) {
    470             Permission permission = mPermissions.valueAt(i);
    471             permission.setPolicyFixed(true);
    472             mPackageManager.updatePermissionFlags(permission.getName(),
    473                     mPackageInfo.packageName,
    474                     PackageManager.FLAG_PERMISSION_POLICY_FIXED,
    475                     PackageManager.FLAG_PERMISSION_POLICY_FIXED,
    476                     mUserHandle);
    477         }
    478     }
    479 
    480     public List<Permission> getPermissions() {
    481         return new ArrayList<>(mPermissions.values());
    482     }
    483 
    484     public int getFlags() {
    485         int flags = 0;
    486         final int permissionCount = mPermissions.size();
    487         for (int i = 0; i < permissionCount; i++) {
    488             Permission permission = mPermissions.valueAt(i);
    489             flags |= permission.getFlags();
    490         }
    491         return flags;
    492     }
    493 
    494     public boolean isUserFixed() {
    495         final int permissionCount = mPermissions.size();
    496         for (int i = 0; i < permissionCount; i++) {
    497             Permission permission = mPermissions.valueAt(i);
    498             if (!permission.isUserFixed()) {
    499                 return false;
    500             }
    501         }
    502         return true;
    503     }
    504 
    505     public boolean isPolicyFixed() {
    506         final int permissionCount = mPermissions.size();
    507         for (int i = 0; i < permissionCount; i++) {
    508             Permission permission = mPermissions.valueAt(i);
    509             if (permission.isPolicyFixed()) {
    510                 return true;
    511             }
    512         }
    513         return false;
    514     }
    515 
    516     public boolean isUserSet() {
    517         final int permissionCount = mPermissions.size();
    518         for (int i = 0; i < permissionCount; i++) {
    519             Permission permission = mPermissions.valueAt(i);
    520             if (!permission.isUserSet()) {
    521                 return false;
    522             }
    523         }
    524         return true;
    525     }
    526 
    527     public boolean isSystemFixed() {
    528         final int permissionCount = mPermissions.size();
    529         for (int i = 0; i < permissionCount; i++) {
    530             Permission permission = mPermissions.valueAt(i);
    531             if (permission.isSystemFixed()) {
    532                 return true;
    533             }
    534         }
    535         return false;
    536     }
    537 
    538     @Override
    539     public int compareTo(AppPermissionGroup another) {
    540         final int result = mLabel.toString().compareTo(another.mLabel.toString());
    541         if (result == 0) {
    542             // Unbadged before badged.
    543             return mPackageInfo.applicationInfo.uid
    544                     - another.mPackageInfo.applicationInfo.uid;
    545         }
    546         return result;
    547     }
    548 
    549     @Override
    550     public boolean equals(Object obj) {
    551         if (this == obj) {
    552             return true;
    553         }
    554 
    555         if (obj == null) {
    556             return false;
    557         }
    558 
    559         if (getClass() != obj.getClass()) {
    560             return false;
    561         }
    562 
    563         AppPermissionGroup other = (AppPermissionGroup) obj;
    564 
    565         if (mName == null) {
    566             if (other.mName != null) {
    567                 return false;
    568             }
    569         } else if (!mName.equals(other.mName)) {
    570             return false;
    571         }
    572 
    573         return true;
    574     }
    575 
    576     @Override
    577     public int hashCode() {
    578         return mName != null ? mName.hashCode() : 0;
    579     }
    580 
    581     @Override
    582     public String toString() {
    583         StringBuilder builder = new StringBuilder();
    584         builder.append(getClass().getSimpleName());
    585         builder.append("{name=").append(mName);
    586         if (!mPermissions.isEmpty()) {
    587             builder.append(", <has permissions>}");
    588         } else {
    589             builder.append('}');
    590         }
    591         return builder.toString();
    592     }
    593 
    594     private void addPermission(Permission permission) {
    595         mPermissions.put(permission.getName(), permission);
    596     }
    597 }
    598