• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 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.car.settings.applications.specialaccess;
18 
19 import android.app.AppGlobals;
20 import android.app.AppOpsManager;
21 import android.content.Context;
22 import android.content.pm.IPackageManager;
23 import android.content.pm.PackageInfo;
24 import android.content.pm.PackageManager;
25 import android.os.RemoteException;
26 import android.os.UserHandle;
27 import android.os.UserManager;
28 import android.util.ArrayMap;
29 import android.util.SparseArray;
30 
31 import androidx.annotation.VisibleForTesting;
32 
33 import com.android.car.settings.common.Logger;
34 import com.android.internal.util.ArrayUtils;
35 import com.android.settingslib.applications.ApplicationsState.AppEntry;
36 
37 import java.util.List;
38 import java.util.Map;
39 
40 /**
41  * Bridges {@link AppOpsManager} app operation permission information into {@link
42  * AppEntry#extraInfo} as {@link PermissionState} objects.
43  */
44 public class AppStateAppOpsBridge implements AppEntryListManager.ExtraInfoBridge {
45 
46     private static final Logger LOG = new Logger(AppStateAppOpsBridge.class);
47 
48     private final Context mContext;
49     private final IPackageManager mIPackageManager;
50     private final List<UserHandle> mProfiles;
51     private final AppOpsManager mAppOpsManager;
52     private final int mAppOpsOpCode;
53     private final String mPermission;
54 
55     /**
56      * Constructor.
57      *
58      * @param appOpsOpCode the {@link AppOpsManager} op code constant to fetch information for.
59      * @param permission   the {@link android.Manifest.permission} required to perform the
60      *                     operation.
61      */
AppStateAppOpsBridge(Context context, int appOpsOpCode, String permission)62     public AppStateAppOpsBridge(Context context, int appOpsOpCode, String permission) {
63         this(context, appOpsOpCode, permission, AppGlobals.getPackageManager());
64     }
65 
66     @VisibleForTesting
AppStateAppOpsBridge(Context context, int appOpsOpCode, String permission, IPackageManager packageManager)67     AppStateAppOpsBridge(Context context, int appOpsOpCode, String permission,
68             IPackageManager packageManager) {
69         mContext = context;
70         mIPackageManager = packageManager;
71         mProfiles = UserManager.get(context).getUserProfiles();
72         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
73         mAppOpsOpCode = appOpsOpCode;
74         mPermission = permission;
75     }
76 
77     @Override
loadExtraInfo(List<AppEntry> entries)78     public void loadExtraInfo(List<AppEntry> entries) {
79         SparseArray<Map<String, PermissionState>> packageToStatesMapByProfileId =
80                 getPackageToStateMapsByProfileId();
81         loadAppOpModes(packageToStatesMapByProfileId);
82 
83         for (AppEntry entry : entries) {
84             Map<String, PermissionState> packageStatesMap = packageToStatesMapByProfileId.get(
85                     UserHandle.getUserId(entry.info.uid));
86             entry.extraInfo = (packageStatesMap != null) ? packageStatesMap.get(
87                     entry.info.packageName) : null;
88         }
89     }
90 
getPackageToStateMapsByProfileId()91     private SparseArray<Map<String, PermissionState>> getPackageToStateMapsByProfileId() {
92         SparseArray<Map<String, PermissionState>> entries = new SparseArray<>();
93         try {
94             for (UserHandle profile : mProfiles) {
95                 int profileId = profile.getIdentifier();
96                 List<PackageInfo> packageInfos = getPackageInfos(profileId);
97                 Map<String, PermissionState> entriesForProfile = new ArrayMap<>();
98                 entries.put(profileId, entriesForProfile);
99                 for (PackageInfo packageInfo : packageInfos) {
100                     boolean isAvailable = mIPackageManager.isPackageAvailable(
101                             packageInfo.packageName,
102                             profileId);
103                     if (shouldIgnorePackage(packageInfo) || !isAvailable) {
104                         LOG.d("Ignoring " + packageInfo.packageName + " isAvailable="
105                                 + isAvailable);
106                         continue;
107                     }
108                     PermissionState newEntry = new PermissionState();
109                     newEntry.mRequestedPermissions = packageInfo.requestedPermissions;
110                     entriesForProfile.put(packageInfo.packageName, newEntry);
111                 }
112             }
113         } catch (RemoteException e) {
114             LOG.w("PackageManager is dead. Can't get list of packages requesting "
115                     + mPermission, e);
116         }
117         return entries;
118     }
119 
120     @SuppressWarnings("unchecked") // safe by specification.
getPackageInfos(int profileId)121     private List<PackageInfo> getPackageInfos(int profileId) throws RemoteException {
122         return mIPackageManager.getPackagesHoldingPermissions(new String[]{mPermission},
123                 PackageManager.GET_PERMISSIONS, profileId).getList();
124     }
125 
shouldIgnorePackage(PackageInfo packageInfo)126     private boolean shouldIgnorePackage(PackageInfo packageInfo) {
127         return packageInfo.packageName.equals("android")
128                 || packageInfo.packageName.equals(mContext.getPackageName())
129                 || !ArrayUtils.contains(packageInfo.requestedPermissions, mPermission);
130     }
131 
132     /** Sets the {@link PermissionState#mAppOpMode} field. */
loadAppOpModes( SparseArray<Map<String, PermissionState>> packageToStateMapsByProfileId)133     private void loadAppOpModes(
134             SparseArray<Map<String, PermissionState>> packageToStateMapsByProfileId) {
135         // Find out which packages have been granted permission from AppOps.
136         List<AppOpsManager.PackageOps> packageOps = mAppOpsManager.getPackagesForOps(
137                 new int[]{mAppOpsOpCode});
138         if (packageOps == null) {
139             return;
140         }
141         for (AppOpsManager.PackageOps packageOp : packageOps) {
142             int userId = UserHandle.getUserId(packageOp.getUid());
143             Map<String, PermissionState> packageStateMap = packageToStateMapsByProfileId.get(
144                     userId);
145             if (packageStateMap == null) {
146                 // Profile is not for the current user.
147                 continue;
148             }
149             PermissionState permissionState = packageStateMap.get(packageOp.getPackageName());
150             if (permissionState == null) {
151                 LOG.w("AppOp permission exists for package " + packageOp.getPackageName()
152                         + " of user " + userId + " but package doesn't exist or did not request "
153                         + mPermission + " access");
154                 continue;
155             }
156             if (packageOp.getOps().size() < 1) {
157                 LOG.w("No AppOps permission exists for package " + packageOp.getPackageName());
158                 continue;
159             }
160             permissionState.mAppOpMode = packageOp.getOps().get(0).getMode();
161         }
162     }
163 
164     /**
165      * Data class for use in {@link AppEntry#extraInfo} which indicates whether
166      * the app operation used to construct the data bridge is permitted for the associated
167      * application.
168      */
169     public static class PermissionState {
170         private String[] mRequestedPermissions;
171         private int mAppOpMode = AppOpsManager.MODE_DEFAULT;
172 
173         /** Returns {@code true} if the entry's application is allowed to perform the operation. */
isPermissible()174         public boolean isPermissible() {
175             // Default behavior is permissible as long as the package requested this permission.
176             if (mAppOpMode == AppOpsManager.MODE_DEFAULT) {
177                 return true;
178             }
179             return mAppOpMode == AppOpsManager.MODE_ALLOWED;
180         }
181 
182         /** Returns the permissions requested by the entry's application. */
getRequestedPermissions()183         public String[] getRequestedPermissions() {
184             return mRequestedPermissions;
185         }
186     }
187 }
188