blob: 4a3d0a19ebe2ad021212c6dc28e5ba017dd55a41 [file] [log] [blame]
/*
* Copyright 2019 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.car.settings.applications.specialaccess;
import android.app.AppGlobals;
import android.app.AppOpsManager;
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.SparseArray;
import androidx.annotation.VisibleForTesting;
import com.android.car.settings.common.Logger;
import com.android.internal.util.ArrayUtils;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
import java.util.List;
import java.util.Map;
/**
* Bridges {@link AppOpsManager} app operation permission information into {@link
* AppEntry#extraInfo} as {@link PermissionState} objects.
*/
public class AppStateAppOpsBridge implements AppEntryListManager.ExtraInfoBridge {
private static final Logger LOG = new Logger(AppStateAppOpsBridge.class);
private final Context mContext;
private final IPackageManager mIPackageManager;
private final List<UserHandle> mProfiles;
private final AppOpsManager mAppOpsManager;
private final int mAppOpsOpCode;
private final String mPermission;
/**
* Constructor.
*
* @param appOpsOpCode the {@link AppOpsManager} op code constant to fetch information for.
* @param permission the {@link android.Manifest.permission} required to perform the
* operation.
*/
public AppStateAppOpsBridge(Context context, int appOpsOpCode, String permission) {
this(context, appOpsOpCode, permission, AppGlobals.getPackageManager());
}
@VisibleForTesting
AppStateAppOpsBridge(Context context, int appOpsOpCode, String permission,
IPackageManager packageManager) {
mContext = context;
mIPackageManager = packageManager;
mProfiles = UserManager.get(context).getUserProfiles();
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mAppOpsOpCode = appOpsOpCode;
mPermission = permission;
}
@Override
public void loadExtraInfo(List<AppEntry> entries) {
SparseArray<Map<String, PermissionState>> packageToStatesMapByProfileId =
getPackageToStateMapsByProfileId();
loadAppOpModes(packageToStatesMapByProfileId);
for (AppEntry entry : entries) {
Map<String, PermissionState> packageStatesMap = packageToStatesMapByProfileId.get(
UserHandle.getUserId(entry.info.uid));
entry.extraInfo = (packageStatesMap != null) ? packageStatesMap.get(
entry.info.packageName) : null;
}
}
private SparseArray<Map<String, PermissionState>> getPackageToStateMapsByProfileId() {
SparseArray<Map<String, PermissionState>> entries = new SparseArray<>();
try {
for (UserHandle profile : mProfiles) {
int profileId = profile.getIdentifier();
List<PackageInfo> packageInfos = getPackageInfos(profileId);
Map<String, PermissionState> entriesForProfile = new ArrayMap<>();
entries.put(profileId, entriesForProfile);
for (PackageInfo packageInfo : packageInfos) {
boolean isAvailable = mIPackageManager.isPackageAvailable(
packageInfo.packageName,
profileId);
if (shouldIgnorePackage(packageInfo) || !isAvailable) {
LOG.d("Ignoring " + packageInfo.packageName + " isAvailable="
+ isAvailable);
continue;
}
PermissionState newEntry = new PermissionState();
newEntry.mRequestedPermissions = packageInfo.requestedPermissions;
entriesForProfile.put(packageInfo.packageName, newEntry);
}
}
} catch (RemoteException e) {
LOG.w("PackageManager is dead. Can't get list of packages requesting "
+ mPermission, e);
}
return entries;
}
@SuppressWarnings("unchecked") // safe by specification.
private List<PackageInfo> getPackageInfos(int profileId) throws RemoteException {
return mIPackageManager.getPackagesHoldingPermissions(new String[]{mPermission},
PackageManager.GET_PERMISSIONS, profileId).getList();
}
private boolean shouldIgnorePackage(PackageInfo packageInfo) {
return packageInfo.packageName.equals("android")
|| packageInfo.packageName.equals(mContext.getPackageName())
|| !ArrayUtils.contains(packageInfo.requestedPermissions, mPermission);
}
/** Sets the {@link PermissionState#mAppOpMode} field. */
private void loadAppOpModes(
SparseArray<Map<String, PermissionState>> packageToStateMapsByProfileId) {
// Find out which packages have been granted permission from AppOps.
List<AppOpsManager.PackageOps> packageOps = mAppOpsManager.getPackagesForOps(
new int[]{mAppOpsOpCode});
if (packageOps == null) {
return;
}
for (AppOpsManager.PackageOps packageOp : packageOps) {
int userId = UserHandle.getUserId(packageOp.getUid());
Map<String, PermissionState> packageStateMap = packageToStateMapsByProfileId.get(
userId);
if (packageStateMap == null) {
// Profile is not for the current user.
continue;
}
PermissionState permissionState = packageStateMap.get(packageOp.getPackageName());
if (permissionState == null) {
LOG.w("AppOp permission exists for package " + packageOp.getPackageName()
+ " of user " + userId + " but package doesn't exist or did not request "
+ mPermission + " access");
continue;
}
if (packageOp.getOps().size() < 1) {
LOG.w("No AppOps permission exists for package " + packageOp.getPackageName());
continue;
}
permissionState.mAppOpMode = packageOp.getOps().get(0).getMode();
}
}
/**
* Data class for use in {@link AppEntry#extraInfo} which indicates whether
* the app operation used to construct the data bridge is permitted for the associated
* application.
*/
public static class PermissionState {
private String[] mRequestedPermissions;
private int mAppOpMode = AppOpsManager.MODE_DEFAULT;
/** Returns {@code true} if the entry's application is allowed to perform the operation. */
public boolean isPermissible() {
// Default behavior is permissible as long as the package requested this permission.
if (mAppOpMode == AppOpsManager.MODE_DEFAULT) {
return true;
}
return mAppOpMode == AppOpsManager.MODE_ALLOWED;
}
/** Returns the permissions requested by the entry's application. */
public String[] getRequestedPermissions() {
return mRequestedPermissions;
}
}
}