/* * 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 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()); } @VisibleForTesting AppStateAppOpsBridge(Context context, ApplicationsState appState, Callback callback, int appOpsOpCode, 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 = new int[] {appOpsOpCode}; 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 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> entries = getEntries(); // Load state info. loadPermissionsStates(entries); loadAppOpsStates(entries); // Map states to application info. List 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); ArrayMap userMap = entries.get(userId); app.extraInfo = userMap != null ? userMap.get(app.info.packageName) : 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> getEntries() { try { Set packagesSet = new HashSet<>(); for (String permission : mPermissions) { String[] pkgs = mIPackageManager.getAppOpPermissionPackages(permission); if (pkgs != null) { packagesSet.addAll(Arrays.asList(pkgs)); } } if (packagesSet.isEmpty()) { // No packages are requesting permission as specified by mPermissions. return null; } // Create a sparse array that maps profileIds to an ArrayMap that maps package names to // an associated PermissionState object SparseArray> entries = new SparseArray<>(); for (final UserHandle profile : mProfiles) { final ArrayMap entriesForProfile = new ArrayMap<>(); final int profileId = profile.getIdentifier(); 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); } } } 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> 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 entriesForProfile = entries.get(profileId); if (entriesForProfile == null) { continue; } @SuppressWarnings("unchecked") final List 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 " + mPermissions, e); return; } } /* * This method will set the appOpMode field of the associated PermissionState, which describes * a particular package. */ private void loadAppOpsStates(SparseArray> entries) { // Find out which packages have been granted permission from AppOps. final List 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 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 " + 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> entries = getEntries(); if (entries == null) { return 0; } final ArrayMap entriesForProfile = entries.get(mUserManager .getUserHandle()); if (entriesForProfile == null) { return 0; } return entriesForProfile.size(); } public int getNumPackagesAllowedByAppOps() { SparseArray> entries = getEntries(); if (entries == null) { return 0; } loadPermissionsStates(entries); loadAppOpsStates(entries); final ArrayMap entriesForProfile = entries.get(mUserManager .getUserHandle()); if (entriesForProfile == null) { return 0; } Collection 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; } } }