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 */
16package com.android.settings.applications;
17
18import android.Manifest;
19import android.app.AppGlobals;
20import android.app.AppOpsManager;
21import android.app.AppOpsManager.PackageOps;
22import android.content.Context;
23import android.content.pm.IPackageManager;
24import android.content.pm.PackageInfo;
25import android.content.pm.PackageManager;
26import android.os.RemoteException;
27import android.os.UserHandle;
28import android.os.UserManager;
29import android.util.ArrayMap;
30import android.util.Log;
31import android.util.SparseArray;
32
33import com.android.settingslib.applications.ApplicationsState;
34import com.android.settingslib.applications.ApplicationsState.AppEntry;
35import com.android.settingslib.applications.ApplicationsState.AppFilter;
36
37import java.util.Arrays;
38import java.util.Collection;
39import java.util.HashSet;
40import java.util.List;
41import java.util.Set;
42
43/*
44 * Connects app ops info to the ApplicationsState. Makes use of AppOpsManager to
45 * determine further permission level.
46 */
47public abstract class AppStateAppOpsBridge extends AppStateBaseBridge {
48
49    private static final String TAG = "AppStateAppOpsBridge";
50
51    private final IPackageManager mIPackageManager;
52    private final UserManager mUserManager;
53    private final List<UserHandle> mProfiles;
54    private final AppOpsManager mAppOpsManager;
55    private final Context mContext;
56    private final int[] mAppOpsOpCodes;
57    private final String[] mPermissions;
58
59    public AppStateAppOpsBridge(Context context, ApplicationsState appState, Callback callback,
60            int appOpsOpCode, String[] permissions) {
61        super(appState, callback);
62        mContext = context;
63        mIPackageManager = AppGlobals.getPackageManager();
64        mUserManager = UserManager.get(context);
65        mProfiles = mUserManager.getUserProfiles();
66        mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
67        mAppOpsOpCodes = new int[] {appOpsOpCode};
68        mPermissions = permissions;
69    }
70
71    private boolean isThisUserAProfileOfCurrentUser(final int userId) {
72        final int profilesMax = mProfiles.size();
73        for (int i = 0; i < profilesMax; i++) {
74            if (mProfiles.get(i).getIdentifier() == userId) {
75                return true;
76            }
77        }
78        return false;
79    }
80
81    protected abstract void updateExtraInfo(AppEntry app, String pkg, int uid);
82
83    private boolean doesAnyPermissionMatch(String permissionToMatch, String[] permissions) {
84        for (String permission : permissions) {
85            if (permissionToMatch.equals(permission)) {
86                return true;
87            }
88        }
89        return false;
90    }
91
92    public PermissionState getPermissionInfo(String pkg, int uid) {
93        PermissionState permissionState = new PermissionState(pkg, new UserHandle(UserHandle
94                .getUserId(uid)));
95        try {
96            permissionState.packageInfo = mIPackageManager.getPackageInfo(pkg,
97                    PackageManager.GET_PERMISSIONS, permissionState.userHandle.getIdentifier());
98            // Check static permission state (whatever that is declared in package manifest)
99            String[] requestedPermissions = permissionState.packageInfo.requestedPermissions;
100            int[] permissionFlags = permissionState.packageInfo.requestedPermissionsFlags;
101            if (requestedPermissions != null) {
102                for (int i = 0; i < requestedPermissions.length; i++) {
103                    if (doesAnyPermissionMatch(requestedPermissions[i], mPermissions)) {
104                        permissionState.permissionDeclared = true;
105                        if ((permissionFlags[i] & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) {
106                            permissionState.staticPermissionGranted = true;
107                            break;
108                        }
109                    }
110                }
111            }
112            // Check app op state.
113            List<PackageOps> ops = mAppOpsManager.getOpsForPackage(uid, pkg, mAppOpsOpCodes);
114            if (ops != null && ops.size() > 0 && ops.get(0).getOps().size() > 0) {
115                permissionState.appOpMode = ops.get(0).getOps().get(0).getMode();
116            }
117        } catch (RemoteException e) {
118            Log.w(TAG, "PackageManager is dead. Can't get package info " + pkg, e);
119        }
120        return permissionState;
121    }
122
123    @Override
124    protected void loadAllExtraInfo() {
125        SparseArray<ArrayMap<String, PermissionState>> entries = getEntries();
126
127        // Load state info.
128        loadPermissionsStates(entries);
129        loadAppOpsStates(entries);
130
131        // Map states to application info.
132        List<AppEntry> apps = mAppSession.getAllApps();
133        final int N = apps.size();
134        for (int i = 0; i < N; i++) {
135            AppEntry app = apps.get(i);
136            int userId = UserHandle.getUserId(app.info.uid);
137            ArrayMap<String, PermissionState> userMap = entries.get(userId);
138            app.extraInfo = userMap != null ? userMap.get(app.info.packageName) : null;
139        }
140    }
141
142    /*
143     * Gets a sparse array that describes every user on the device and all the associated packages
144     * of each user, together with the packages available for that user.
145     */
146    private SparseArray<ArrayMap<String, PermissionState>> getEntries() {
147        try {
148            Set<String> packagesSet = new HashSet<>();
149            for (String permission : mPermissions) {
150                String[] pkgs = mIPackageManager.getAppOpPermissionPackages(permission);
151                if (pkgs != null) {
152                    packagesSet.addAll(Arrays.asList(pkgs));
153                }
154            }
155
156            if (packagesSet.isEmpty()) {
157                // No packages are requesting permission as specified by mPermissions.
158                return null;
159            }
160
161            // Create a sparse array that maps profileIds to an ArrayMap that maps package names to
162            // an associated PermissionState object
163            SparseArray<ArrayMap<String, PermissionState>> entries = new SparseArray<>();
164            for (final UserHandle profile : mProfiles) {
165                final ArrayMap<String, PermissionState> entriesForProfile = new ArrayMap<>();
166                final int profileId = profile.getIdentifier();
167                entries.put(profileId, entriesForProfile);
168                for (final String packageName : packagesSet) {
169                    final boolean isAvailable = mIPackageManager.isPackageAvailable(packageName,
170                            profileId);
171                    if (!shouldIgnorePackage(packageName) && isAvailable) {
172                        final PermissionState newEntry = new PermissionState(packageName, profile);
173                        entriesForProfile.put(packageName, newEntry);
174                    }
175                }
176            }
177
178            return entries;
179        } catch (RemoteException e) {
180            Log.w(TAG, "PackageManager is dead. Can't get list of packages requesting "
181                    + mPermissions[0], e);
182            return null;
183        }
184    }
185
186    /*
187     * This method will set the packageInfo and staticPermissionGranted field of the associated
188     * PermissionState, which describes a particular package.
189     */
190    private void loadPermissionsStates(SparseArray<ArrayMap<String, PermissionState>> entries) {
191         // Load the packages that have been granted the permission specified in mPermission.
192        try {
193            for (final UserHandle profile : mProfiles) {
194                final int profileId = profile.getIdentifier();
195                final ArrayMap<String, PermissionState> entriesForProfile = entries.get(profileId);
196                if (entriesForProfile == null) {
197                    continue;
198                }
199                @SuppressWarnings("unchecked")
200                final List<PackageInfo> packageInfos = mIPackageManager
201                        .getPackagesHoldingPermissions(mPermissions, 0, profileId).getList();
202                final int packageInfoCount = packageInfos != null ? packageInfos.size() : 0;
203                for (int i = 0; i < packageInfoCount; i++) {
204                    final PackageInfo packageInfo = packageInfos.get(i);
205                    final PermissionState pe = entriesForProfile.get(packageInfo.packageName);
206                    if (pe != null) {
207                        pe.packageInfo = packageInfo;
208                        pe.staticPermissionGranted = true;
209                    }
210                }
211            }
212        } catch (RemoteException e) {
213            Log.w(TAG, "PackageManager is dead. Can't get list of packages granted "
214                    + mPermissions, e);
215            return;
216        }
217    }
218
219    /*
220     * This method will set the appOpMode field of the associated PermissionState, which describes
221     * a particular package.
222     */
223    private void loadAppOpsStates(SparseArray<ArrayMap<String, PermissionState>> entries) {
224        // Find out which packages have been granted permission from AppOps.
225        final List<AppOpsManager.PackageOps> packageOps = mAppOpsManager.getPackagesForOps(
226                mAppOpsOpCodes);
227        final int packageOpsCount = packageOps != null ? packageOps.size() : 0;
228        for (int i = 0; i < packageOpsCount; i++) {
229            final AppOpsManager.PackageOps packageOp = packageOps.get(i);
230            final int userId = UserHandle.getUserId(packageOp.getUid());
231            if (!isThisUserAProfileOfCurrentUser(userId)) {
232                // This AppOp does not belong to any of this user's profiles.
233                continue;
234            }
235
236            final ArrayMap<String, PermissionState> entriesForProfile = entries.get(userId);
237            if (entriesForProfile == null) {
238                continue;
239            }
240            final PermissionState pe = entriesForProfile.get(packageOp.getPackageName());
241            if (pe == null) {
242                Log.w(TAG, "AppOp permission exists for package " + packageOp.getPackageName()
243                        + " of user " + userId + " but package doesn't exist or did not request "
244                        + mPermissions + " access");
245                continue;
246            }
247
248            if (packageOp.getOps().size() < 1) {
249                Log.w(TAG, "No AppOps permission exists for package " + packageOp.getPackageName());
250                continue;
251            }
252            pe.appOpMode = packageOp.getOps().get(0).getMode();
253        }
254    }
255
256    /*
257     * Check for packages that should be ignored for further processing
258     */
259    private boolean shouldIgnorePackage(String packageName) {
260        return packageName.equals("android") || packageName.equals(mContext.getPackageName());
261    }
262
263    public int getNumPackagesDeclaredPermission() {
264        SparseArray<ArrayMap<String, PermissionState>> entries = getEntries();
265        if (entries == null) {
266            return 0;
267        }
268        final ArrayMap<String, PermissionState> entriesForProfile = entries.get(mUserManager
269                .getUserHandle());
270        if (entriesForProfile == null) {
271            return 0;
272        }
273        return entriesForProfile.size();
274    }
275
276    public int getNumPackagesAllowedByAppOps() {
277        SparseArray<ArrayMap<String, PermissionState>> entries = getEntries();
278        if (entries == null) {
279            return 0;
280        }
281        loadPermissionsStates(entries);
282        loadAppOpsStates(entries);
283        final ArrayMap<String, PermissionState> entriesForProfile = entries.get(mUserManager
284                .getUserHandle());
285        if (entriesForProfile == null) {
286            return 0;
287        }
288        Collection<PermissionState> permStates = entriesForProfile.values();
289        int result = 0;
290        for (PermissionState permState : permStates) {
291            if (permState.isPermissible()) {
292                result++;
293            }
294        }
295        return result;
296    }
297
298    public static class PermissionState {
299        public final String packageName;
300        public final UserHandle userHandle;
301        public PackageInfo packageInfo;
302        public boolean staticPermissionGranted;
303        public boolean permissionDeclared;
304        public int appOpMode;
305
306        public PermissionState(String packageName, UserHandle userHandle) {
307            this.packageName = packageName;
308            this.appOpMode = AppOpsManager.MODE_DEFAULT;
309            this.userHandle = userHandle;
310        }
311
312        public boolean isPermissible() {
313            // defining the default behavior as permissible as long as the package requested this
314            // permission (this means pre-M gets approval during install time; M apps gets approval
315            // during runtime.
316            if (appOpMode == AppOpsManager.MODE_DEFAULT) {
317                return staticPermissionGranted;
318            }
319            return appOpMode == AppOpsManager.MODE_ALLOWED;
320        }
321    }
322}
323