| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.settings.applications; |
| |
| import android.app.AppGlobals; |
| import android.app.AppOpsManager; |
| import android.app.AppOpsManager.PackageOps; |
| import android.content.Context; |
| import android.content.pm.IPackageManager; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageManager; |
| import android.os.RemoteException; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.util.ArrayMap; |
| import android.util.Log; |
| import android.util.SparseArray; |
| |
| import androidx.annotation.VisibleForTesting; |
| |
| import com.android.settingslib.applications.ApplicationsState; |
| import com.android.settingslib.applications.ApplicationsState.AppEntry; |
| |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| /* |
| * Connects app ops info to the ApplicationsState. Makes use of AppOpsManager to |
| * determine further permission level. |
| */ |
| public abstract class AppStateAppOpsBridge extends AppStateBaseBridge { |
| |
| private static final String TAG = "AppStateAppOpsBridge"; |
| |
| private final IPackageManager mIPackageManager; |
| private final UserManager mUserManager; |
| private final List<UserHandle> mProfiles; |
| private final AppOpsManager mAppOpsManager; |
| private final Context mContext; |
| private final int[] mAppOpsOpCodes; |
| private final String[] mPermissions; |
| |
| public AppStateAppOpsBridge(Context context, ApplicationsState appState, Callback callback, |
| int appOpsOpCode, String[] permissions) { |
| this(context, appState, callback, appOpsOpCode, permissions, |
| AppGlobals.getPackageManager()); |
| } |
| |
| AppStateAppOpsBridge(Context context, ApplicationsState appState, Callback callback, |
| int[] appOpsOpCodes, String[] permissions) { |
| this(context, appState, callback, appOpsOpCodes, permissions, |
| AppGlobals.getPackageManager()); |
| } |
| |
| @VisibleForTesting |
| AppStateAppOpsBridge(Context context, ApplicationsState appState, Callback callback, |
| int appOpsOpCode, String[] permissions, IPackageManager packageManager) { |
| this(context, appState, callback, new int[]{appOpsOpCode}, permissions, |
| packageManager); |
| } |
| |
| AppStateAppOpsBridge(Context context, ApplicationsState appState, Callback callback, |
| int[] appOpsOpCodes, String[] permissions, IPackageManager packageManager) { |
| super(appState, callback); |
| mContext = context; |
| mIPackageManager = packageManager; |
| mUserManager = UserManager.get(context); |
| mProfiles = mUserManager.getUserProfiles(); |
| mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); |
| mAppOpsOpCodes = appOpsOpCodes; |
| mPermissions = permissions; |
| } |
| |
| private boolean isThisUserAProfileOfCurrentUser(final int userId) { |
| final int profilesMax = mProfiles.size(); |
| for (int i = 0; i < profilesMax; i++) { |
| if (mProfiles.get(i).getIdentifier() == userId) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| protected abstract void updateExtraInfo(AppEntry app, String pkg, int uid); |
| |
| private boolean doesAnyPermissionMatch(String permissionToMatch, String[] permissions) { |
| for (String permission : permissions) { |
| if (permissionToMatch.equals(permission)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public PermissionState getPermissionInfo(String pkg, int uid) { |
| PermissionState permissionState = new PermissionState(pkg, new UserHandle(UserHandle |
| .getUserId(uid))); |
| try { |
| permissionState.packageInfo = mIPackageManager.getPackageInfo(pkg, |
| PackageManager.GET_PERMISSIONS | PackageManager.MATCH_ANY_USER, |
| permissionState.userHandle.getIdentifier()); |
| if (permissionState.packageInfo != null) { |
| // Check static permission state (whatever that is declared in package manifest) |
| String[] requestedPermissions = permissionState.packageInfo.requestedPermissions; |
| int[] permissionFlags = permissionState.packageInfo.requestedPermissionsFlags; |
| if (requestedPermissions != null) { |
| for (int i = 0; i < requestedPermissions.length; i++) { |
| if (doesAnyPermissionMatch(requestedPermissions[i], mPermissions)) { |
| permissionState.permissionDeclared = true; |
| if ((permissionFlags[i] & PackageInfo.REQUESTED_PERMISSION_GRANTED) |
| != 0) { |
| permissionState.staticPermissionGranted = true; |
| break; |
| } |
| } |
| } |
| } |
| } |
| // Check app op state. |
| List<PackageOps> ops = mAppOpsManager.getOpsForPackage(uid, pkg, mAppOpsOpCodes); |
| if (ops != null && ops.size() > 0 && ops.get(0).getOps().size() > 0) { |
| permissionState.appOpMode = ops.get(0).getOps().get(0).getMode(); |
| } |
| } catch (RemoteException e) { |
| Log.w(TAG, "PackageManager is dead. Can't get package info " + pkg, e); |
| } |
| return permissionState; |
| } |
| |
| @Override |
| protected void loadAllExtraInfo() { |
| SparseArray<ArrayMap<String, PermissionState>> entries = getEntries(); |
| |
| // Load state info. |
| loadPermissionsStates(entries); |
| loadAppOpsStates(entries); |
| |
| // Map states to application info. |
| List<AppEntry> apps = mAppSession.getAllApps(); |
| final int N = apps.size(); |
| for (int i = 0; i < N; i++) { |
| AppEntry app = apps.get(i); |
| int userId = UserHandle.getUserId(app.info.uid); |
| if (entries != null) { |
| ArrayMap<String, PermissionState> userMap = entries.get(userId); |
| app.extraInfo = userMap != null ? userMap.get(app.info.packageName) : null; |
| } else { |
| app.extraInfo = null; |
| } |
| } |
| } |
| |
| /* |
| * Gets a sparse array that describes every user on the device and all the associated packages |
| * of each user, together with the packages available for that user. |
| */ |
| private SparseArray<ArrayMap<String, PermissionState>> getEntries() { |
| try { |
| // Create a sparse array that maps profileIds to an ArrayMap that maps package names to |
| // an associated PermissionState object |
| SparseArray<ArrayMap<String, PermissionState>> entries = new SparseArray<>(); |
| for (final UserHandle profile : mProfiles) { |
| final int profileId = profile.getIdentifier(); |
| final Set<String> packagesSet = new HashSet<>(); |
| for (String permission : mPermissions) { |
| final String[] pkgs = mIPackageManager.getAppOpPermissionPackages( |
| permission, profileId); |
| if (pkgs != null) { |
| packagesSet.addAll(Arrays.asList(pkgs)); |
| } |
| } |
| if (packagesSet.isEmpty()) { |
| // No packages are requesting permission as specified by mPermissions. |
| continue; |
| } |
| |
| final ArrayMap<String, PermissionState> entriesForProfile = new ArrayMap<>(); |
| entries.put(profileId, entriesForProfile); |
| for (final String packageName : packagesSet) { |
| final boolean isAvailable = mIPackageManager.isPackageAvailable(packageName, |
| profileId); |
| if (!shouldIgnorePackage(packageName) && isAvailable) { |
| final PermissionState newEntry = new PermissionState(packageName, profile); |
| entriesForProfile.put(packageName, newEntry); |
| } |
| } |
| } |
| if (entries.size() == 0) { |
| return null; |
| } |
| |
| return entries; |
| } catch (RemoteException e) { |
| Log.w(TAG, "PackageManager is dead. Can't get list of packages requesting " |
| + mPermissions[0], e); |
| return null; |
| } |
| } |
| |
| /* |
| * This method will set the packageInfo and staticPermissionGranted field of the associated |
| * PermissionState, which describes a particular package. |
| */ |
| private void loadPermissionsStates(SparseArray<ArrayMap<String, PermissionState>> entries) { |
| // Load the packages that have been granted the permission specified in mPermission. |
| if (entries == null) { |
| return; |
| } |
| |
| try { |
| for (final UserHandle profile : mProfiles) { |
| final int profileId = profile.getIdentifier(); |
| final ArrayMap<String, PermissionState> entriesForProfile = entries.get(profileId); |
| if (entriesForProfile == null) { |
| continue; |
| } |
| @SuppressWarnings("unchecked") final List<PackageInfo> packageInfos = |
| mIPackageManager |
| .getPackagesHoldingPermissions(mPermissions, 0, |
| profileId).getList(); |
| final int packageInfoCount = packageInfos != null ? packageInfos.size() : 0; |
| for (int i = 0; i < packageInfoCount; i++) { |
| final PackageInfo packageInfo = packageInfos.get(i); |
| final PermissionState pe = entriesForProfile.get(packageInfo.packageName); |
| if (pe != null) { |
| pe.packageInfo = packageInfo; |
| pe.staticPermissionGranted = true; |
| } |
| } |
| } |
| } catch (RemoteException e) { |
| Log.w(TAG, "PackageManager is dead. Can't get list of packages granted " |
| + Arrays.toString(mPermissions), e); |
| return; |
| } |
| } |
| |
| /* |
| * This method will set the appOpMode field of the associated PermissionState, which describes |
| * a particular package. |
| */ |
| private void loadAppOpsStates(SparseArray<ArrayMap<String, PermissionState>> entries) { |
| if (entries == null) { |
| return; |
| } |
| |
| // Find out which packages have been granted permission from AppOps. |
| final List<AppOpsManager.PackageOps> packageOps = mAppOpsManager.getPackagesForOps( |
| mAppOpsOpCodes); |
| final int packageOpsCount = packageOps != null ? packageOps.size() : 0; |
| for (int i = 0; i < packageOpsCount; i++) { |
| final AppOpsManager.PackageOps packageOp = packageOps.get(i); |
| final int userId = UserHandle.getUserId(packageOp.getUid()); |
| if (!isThisUserAProfileOfCurrentUser(userId)) { |
| // This AppOp does not belong to any of this user's profiles. |
| continue; |
| } |
| |
| final ArrayMap<String, PermissionState> entriesForProfile = entries.get(userId); |
| if (entriesForProfile == null) { |
| continue; |
| } |
| final PermissionState pe = entriesForProfile.get(packageOp.getPackageName()); |
| if (pe == null) { |
| Log.w(TAG, "AppOp permission exists for package " + packageOp.getPackageName() |
| + " of user " + userId + " but package doesn't exist or did not request " |
| + Arrays.toString(mPermissions) + " access"); |
| continue; |
| } |
| |
| if (packageOp.getOps().size() < 1) { |
| Log.w(TAG, "No AppOps permission exists for package " + packageOp.getPackageName()); |
| continue; |
| } |
| pe.appOpMode = packageOp.getOps().get(0).getMode(); |
| } |
| } |
| |
| /* |
| * Check for packages that should be ignored for further processing |
| */ |
| private boolean shouldIgnorePackage(String packageName) { |
| return packageName.equals("android") || packageName.equals(mContext.getPackageName()); |
| } |
| |
| public int getNumPackagesDeclaredPermission() { |
| SparseArray<ArrayMap<String, PermissionState>> entries = getEntries(); |
| if (entries == null) { |
| return 0; |
| } |
| final ArrayMap<String, PermissionState> entriesForProfile = |
| entries.get(mUserManager.getProcessUserId()); |
| if (entriesForProfile == null) { |
| return 0; |
| } |
| return entriesForProfile.size(); |
| } |
| |
| public int getNumPackagesAllowedByAppOps() { |
| SparseArray<ArrayMap<String, PermissionState>> entries = getEntries(); |
| if (entries == null) { |
| return 0; |
| } |
| loadPermissionsStates(entries); |
| loadAppOpsStates(entries); |
| final ArrayMap<String, PermissionState> entriesForProfile = |
| entries.get(mUserManager.getProcessUserId()); |
| if (entriesForProfile == null) { |
| return 0; |
| } |
| Collection<PermissionState> permStates = entriesForProfile.values(); |
| int result = 0; |
| for (PermissionState permState : permStates) { |
| if (permState.isPermissible()) { |
| result++; |
| } |
| } |
| return result; |
| } |
| |
| public static class PermissionState { |
| public final String packageName; |
| public final UserHandle userHandle; |
| public PackageInfo packageInfo; |
| public boolean staticPermissionGranted; |
| public boolean permissionDeclared; |
| public int appOpMode; |
| |
| public PermissionState(String packageName, UserHandle userHandle) { |
| this.packageName = packageName; |
| this.appOpMode = AppOpsManager.MODE_DEFAULT; |
| this.userHandle = userHandle; |
| } |
| |
| public boolean isPermissible() { |
| // defining the default behavior as permissible as long as the package requested this |
| // permission (this means pre-M gets approval during install time; M apps gets approval |
| // during runtime). |
| if (appOpMode == AppOpsManager.MODE_DEFAULT) { |
| return staticPermissionGranted; |
| } |
| return appOpMode == AppOpsManager.MODE_ALLOWED; |
| } |
| } |
| } |