/* * Copyright (C) 2020 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.managedprovisioning.task; import static android.app.AppOpsManager.OP_INTERACT_ACROSS_PROFILES; import android.Manifest; import android.app.AppOpsManager; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.CrossProfileApps; import android.content.pm.PackageManager; import android.os.UserHandle; import android.os.UserManager; import android.util.ArraySet; import com.android.managedprovisioning.analytics.ProvisioningAnalyticsTracker; import com.android.managedprovisioning.common.ProvisionLogger; import com.android.managedprovisioning.model.ProvisioningParams; import com.android.managedprovisioning.task.interactacrossprofiles.CrossProfileAppsSnapshot; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Task which resets the {@code INTERACT_ACROSS_USERS} app op when the OEM whitelist is changed. */ public class UpdateInteractAcrossProfilesAppOpTask extends AbstractProvisioningTask { private final CrossProfileAppsSnapshot mCrossProfileAppsSnapshot; private final CrossProfileApps mCrossProfileApps; private final DevicePolicyManager mDevicePolicyManager; private final AppOpsManager mAppOpsManager; private final PackageManager mPackageManager; private final UserManager mUserManager; public UpdateInteractAcrossProfilesAppOpTask(Context context, ProvisioningParams provisioningParams, Callback callback, ProvisioningAnalyticsTracker provisioningAnalyticsTracker) { super(context, provisioningParams, callback, provisioningAnalyticsTracker); mCrossProfileAppsSnapshot = new CrossProfileAppsSnapshot(context); mCrossProfileApps = context.getSystemService(CrossProfileApps.class); mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class); mAppOpsManager = context.getSystemService(AppOpsManager.class); mPackageManager = context.getPackageManager(); mUserManager = context.getSystemService(UserManager.class); } @Override public void run(int userId) { Set previousCrossProfileApps = mCrossProfileAppsSnapshot.hasSnapshot(userId) ? mCrossProfileAppsSnapshot.getSnapshot(userId) : new ArraySet<>(); mCrossProfileAppsSnapshot.takeNewSnapshot(userId); Set currentCrossProfileApps = mCrossProfileAppsSnapshot.getSnapshot(userId); updateAfterOtaChanges(previousCrossProfileApps, currentCrossProfileApps); } private void updateAfterOtaChanges( Set previousCrossProfilePackages, Set currentCrossProfilePackages) { mCrossProfileApps.resetInteractAcrossProfilesAppOps( previousCrossProfilePackages, currentCrossProfilePackages); Set newCrossProfilePackages = new HashSet<>(currentCrossProfilePackages); newCrossProfilePackages.removeAll(previousCrossProfilePackages); grantNewConfigurableDefaultCrossProfilePackages(newCrossProfilePackages); reapplyCrossProfileAppsPermission(); } private void grantNewConfigurableDefaultCrossProfilePackages( Set newCrossProfilePackages) { final String op = AppOpsManager.permissionToOp(Manifest.permission.INTERACT_ACROSS_PROFILES); for (String crossProfilePackageName : newCrossProfilePackages) { if (!mCrossProfileApps.canConfigureInteractAcrossProfiles(crossProfilePackageName)) { continue; } try { final int uid = mPackageManager.getPackageUid( crossProfilePackageName, /* flags= */ 0); if (appOpIsChangedFromDefault(op, uid, crossProfilePackageName)) { continue; } } catch (PackageManager.NameNotFoundException e) { ProvisionLogger.loge("Missing package, this should not happen.", e); continue; } mCrossProfileApps.setInteractAcrossProfilesAppOp(crossProfilePackageName, AppOpsManager.MODE_ALLOWED); } } /** * Iterate over all apps and reapply the app-op permission. * *

This is to fix an issue that existed in Android 11 where the appop was set per-package * instead of per-UID causing issues for applications with shared UIDs. */ private void reapplyCrossProfileAppsPermission() { final Set uids = getUidsWithNonDefaultMode(); reapplyCrossProfileAppsPermissionForUids(uids); } private Set getUidsWithNonDefaultMode() { final String op = AppOpsManager.permissionToOp( Manifest.permission.INTERACT_ACROSS_PROFILES); final Set uids = new HashSet<>(); for (ApplicationInfo appInfo : getAllInstalledApps()) { if (appOpIsChangedFromDefault( op, appInfo.uid, appInfo.packageName)) { uids.add(appInfo.uid); } } return uids; } private Set getAllInstalledApps() { final Set apps = new HashSet<>(); List profiles = mUserManager.getAllProfiles(); for (UserHandle profile : profiles) { if (profile.getIdentifier() != mContext.getUserId() && !mUserManager.isManagedProfile(profile.getIdentifier())) { continue; } try { apps.addAll( mContext.createPackageContextAsUser( /* packageName= */ "android", /* flags= */ 0, profile) .getPackageManager().getInstalledApplications(/* flags= */ 0)); } catch (PackageManager.NameNotFoundException ignored) { // Should never happen. } } return apps; } private void reapplyCrossProfileAppsPermissionForUids(Set uids) { for (int uid : uids) { reapplyCrossProfileAppsPermissionForUid(uid); } } private void reapplyCrossProfileAppsPermissionForUid(int uid) { final String op = AppOpsManager.permissionToOp( Manifest.permission.INTERACT_ACROSS_PROFILES); final String[] packages = mPackageManager.getPackagesForUid(uid); final int consolidatedUidMode = getConsolidatedModeForPackagesInUid(uid, packages, op); setAppOpForPackagesInUid(uid, packages, consolidatedUidMode); } private int getConsolidatedModeForPackagesInUid(int uid, String[] packages, String op) { int uidMode = AppOpsManager.MODE_DEFAULT; for (String packageName : packages) { if (mCrossProfileApps.canConfigureInteractAcrossProfiles(packageName)) { final int packageMode = mAppOpsManager.unsafeCheckOpNoThrow(op, uid, packageName); if (shouldUpdateUidMode(packageMode, uidMode)) { uidMode = packageMode; } } } return uidMode; } private boolean shouldUpdateUidMode(int packageMode, @AppOpsManager.Mode int uidMode) { if (!appOpIsChangedFromDefault(packageMode)) { return false; } if (!appOpIsChangedFromDefault(uidMode)) { return true; } if (packageMode == AppOpsManager.MODE_ALLOWED) { return true; } return false; } private void setAppOpForPackagesInUid( int uid, String[] packages, @AppOpsManager.Mode int mode) { for (String packageName : packages) { setInteractAcrossProfilesAppOpForPackage(uid, packageName, mode); } } /** * Sets the package appop to default mode and the uid appop to {@code mode}. */ private void setInteractAcrossProfilesAppOpForPackage( int uid, String packageName, @AppOpsManager.Mode int mode) { mAppOpsManager.setMode( OP_INTERACT_ACROSS_PROFILES, uid, packageName, AppOpsManager.opToDefaultMode(OP_INTERACT_ACROSS_PROFILES)); mAppOpsManager.setUidMode(OP_INTERACT_ACROSS_PROFILES, uid, mode); } private boolean appOpIsChangedFromDefault(String op, int uid, String packageName) { return mAppOpsManager.unsafeCheckOpNoThrow(op, uid, packageName) != AppOpsManager.MODE_DEFAULT; } private boolean appOpIsChangedFromDefault(int mode) { return mode != AppOpsManager.MODE_DEFAULT; } }