1 /* 2 * Copyright (C) 2020 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 */ 16 17 package com.android.managedprovisioning.task; 18 19 import static android.app.AppOpsManager.OP_INTERACT_ACROSS_PROFILES; 20 21 import android.Manifest; 22 import android.app.AppOpsManager; 23 import android.app.admin.DevicePolicyManager; 24 import android.content.Context; 25 import android.content.pm.ApplicationInfo; 26 import android.content.pm.CrossProfileApps; 27 import android.content.pm.PackageManager; 28 import android.os.UserHandle; 29 import android.os.UserManager; 30 import android.util.ArraySet; 31 32 import com.android.managedprovisioning.analytics.ProvisioningAnalyticsTracker; 33 import com.android.managedprovisioning.common.ProvisionLogger; 34 import com.android.managedprovisioning.model.ProvisioningParams; 35 import com.android.managedprovisioning.task.interactacrossprofiles.CrossProfileAppsSnapshot; 36 37 import java.util.HashSet; 38 import java.util.List; 39 import java.util.Set; 40 41 /** 42 * Task which resets the {@code INTERACT_ACROSS_USERS} app op when the OEM whitelist is changed. 43 */ 44 public class UpdateInteractAcrossProfilesAppOpTask extends AbstractProvisioningTask { 45 46 private final CrossProfileAppsSnapshot mCrossProfileAppsSnapshot; 47 private final CrossProfileApps mCrossProfileApps; 48 private final DevicePolicyManager mDevicePolicyManager; 49 private final AppOpsManager mAppOpsManager; 50 private final PackageManager mPackageManager; 51 private final UserManager mUserManager; 52 UpdateInteractAcrossProfilesAppOpTask(Context context, ProvisioningParams provisioningParams, Callback callback, ProvisioningAnalyticsTracker provisioningAnalyticsTracker)53 public UpdateInteractAcrossProfilesAppOpTask(Context context, 54 ProvisioningParams provisioningParams, 55 Callback callback, 56 ProvisioningAnalyticsTracker provisioningAnalyticsTracker) { 57 super(context, provisioningParams, callback, provisioningAnalyticsTracker); 58 mCrossProfileAppsSnapshot = new CrossProfileAppsSnapshot(context); 59 mCrossProfileApps = context.getSystemService(CrossProfileApps.class); 60 mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class); 61 mAppOpsManager = context.getSystemService(AppOpsManager.class); 62 mPackageManager = context.getPackageManager(); 63 mUserManager = context.getSystemService(UserManager.class); 64 } 65 66 @Override run(int userId)67 public void run(int userId) { 68 ProvisionLogger.logi("Running UpdateInteractAcrossProfilesAppOpTask."); 69 Set<String> previousCrossProfileApps = 70 mCrossProfileAppsSnapshot.hasSnapshot(userId) ? 71 mCrossProfileAppsSnapshot.getSnapshot(userId) : 72 new ArraySet<>(); 73 ProvisionLogger.logi("Previous cross profile apps snapshot: " + previousCrossProfileApps); 74 75 mCrossProfileAppsSnapshot.takeNewSnapshot(userId); 76 Set<String> currentCrossProfileApps = mCrossProfileAppsSnapshot.getSnapshot(userId); 77 ProvisionLogger.logi("current cross profile apps snapshot: " + currentCrossProfileApps); 78 79 updateAfterOtaChanges(previousCrossProfileApps, currentCrossProfileApps); 80 } 81 updateAfterOtaChanges( Set<String> previousCrossProfilePackages, Set<String> currentCrossProfilePackages)82 private void updateAfterOtaChanges( 83 Set<String> previousCrossProfilePackages, Set<String> currentCrossProfilePackages) { 84 mCrossProfileApps.resetInteractAcrossProfilesAppOps( 85 previousCrossProfilePackages, currentCrossProfilePackages); 86 Set<String> newCrossProfilePackages = new HashSet<>(currentCrossProfilePackages); 87 newCrossProfilePackages.removeAll(previousCrossProfilePackages); 88 89 grantNewConfigurableDefaultCrossProfilePackages(newCrossProfilePackages); 90 reapplyCrossProfileAppsPermission(); 91 } 92 grantNewConfigurableDefaultCrossProfilePackages( Set<String> newCrossProfilePackages)93 private void grantNewConfigurableDefaultCrossProfilePackages( 94 Set<String> newCrossProfilePackages) { 95 ProvisionLogger.logi("Granting INTERACT_ACROSS_PROFILES to the new default cross profile " 96 + "apps: " + newCrossProfilePackages); 97 98 final String op = 99 AppOpsManager.permissionToOp(Manifest.permission.INTERACT_ACROSS_PROFILES); 100 for (String crossProfilePackageName : newCrossProfilePackages) { 101 if (!mCrossProfileApps.canConfigureInteractAcrossProfiles(crossProfilePackageName)) { 102 ProvisionLogger.logi("Can't grant appop to unconfigurable app: " 103 + crossProfilePackageName); 104 continue; 105 } 106 try { 107 final int uid = mPackageManager.getPackageUid( 108 crossProfilePackageName, /* flags= */ 0); 109 if (appOpIsChangedFromDefault(op, uid, crossProfilePackageName)) { 110 ProvisionLogger.logi("Can't change appop from non default value: (" 111 + crossProfilePackageName + ", " + mAppOpsManager.unsafeCheckOpNoThrow( 112 op, uid, crossProfilePackageName) + ")"); 113 continue; 114 } 115 } catch (PackageManager.NameNotFoundException e) { 116 ProvisionLogger.loge("Missing package, this should not happen.", e); 117 continue; 118 } 119 ProvisionLogger.logi("Setting appop to allowed for " + crossProfilePackageName); 120 mCrossProfileApps.setInteractAcrossProfilesAppOp(crossProfilePackageName, 121 AppOpsManager.MODE_ALLOWED); 122 } 123 } 124 125 /** 126 * Iterate over all apps and reapply the app-op permission. 127 * 128 * <p>This is to fix an issue that existed in Android 11 where the appop was set per-package 129 * instead of per-UID causing issues for applications with shared UIDs. 130 */ reapplyCrossProfileAppsPermission()131 private void reapplyCrossProfileAppsPermission() { 132 ProvisionLogger.logi("Reapplying INTERACT_ACROSS_PROFILES for apps with non default " 133 + "values."); 134 final Set<Integer> uids = getUidsWithNonDefaultMode(); 135 reapplyCrossProfileAppsPermissionForUids(uids); 136 } 137 getUidsWithNonDefaultMode()138 private Set<Integer> getUidsWithNonDefaultMode() { 139 final String op = AppOpsManager.permissionToOp( 140 Manifest.permission.INTERACT_ACROSS_PROFILES); 141 final Set<Integer> uids = new HashSet<>(); 142 for (ApplicationInfo appInfo : getAllInstalledApps()) { 143 if (appOpIsChangedFromDefault( 144 op, appInfo.uid, appInfo.packageName)) { 145 uids.add(appInfo.uid); 146 } 147 } 148 return uids; 149 } 150 getAllInstalledApps()151 private Set<ApplicationInfo> getAllInstalledApps() { 152 final Set<ApplicationInfo> apps = new HashSet<>(); 153 List<UserHandle> profiles = mUserManager.getAllProfiles(); 154 for (UserHandle profile : profiles) { 155 if (profile.getIdentifier() != mContext.getUserId() 156 && !mUserManager.isManagedProfile(profile.getIdentifier())) { 157 continue; 158 } 159 try { 160 apps.addAll( 161 mContext.createPackageContextAsUser( 162 /* packageName= */ "android", /* flags= */ 0, profile) 163 .getPackageManager().getInstalledApplications(/* flags= */ 0)); 164 } catch (PackageManager.NameNotFoundException ignored) { 165 // Should never happen. 166 } 167 } 168 return apps; 169 } 170 reapplyCrossProfileAppsPermissionForUids(Set<Integer> uids)171 private void reapplyCrossProfileAppsPermissionForUids(Set<Integer> uids) { 172 for (int uid : uids) { 173 reapplyCrossProfileAppsPermissionForUid(uid); 174 } 175 } 176 reapplyCrossProfileAppsPermissionForUid(int uid)177 private void reapplyCrossProfileAppsPermissionForUid(int uid) { 178 final String op = AppOpsManager.permissionToOp( 179 Manifest.permission.INTERACT_ACROSS_PROFILES); 180 final String[] packages = mPackageManager.getPackagesForUid(uid); 181 final int consolidatedUidMode = getConsolidatedModeForPackagesInUid(uid, packages, op); 182 setAppOpForPackagesInUid(uid, packages, consolidatedUidMode); 183 } 184 getConsolidatedModeForPackagesInUid(int uid, String[] packages, String op)185 private int getConsolidatedModeForPackagesInUid(int uid, String[] packages, String op) { 186 int uidMode = AppOpsManager.MODE_DEFAULT; 187 for (String packageName : packages) { 188 if (mCrossProfileApps.canConfigureInteractAcrossProfiles(packageName)) { 189 final int packageMode = mAppOpsManager.unsafeCheckOpNoThrow(op, uid, packageName); 190 if (shouldUpdateUidMode(packageMode, uidMode)) { 191 uidMode = packageMode; 192 } 193 } 194 } 195 return uidMode; 196 } 197 shouldUpdateUidMode(int packageMode, @AppOpsManager.Mode int uidMode)198 private boolean shouldUpdateUidMode(int packageMode, @AppOpsManager.Mode int uidMode) { 199 if (!appOpIsChangedFromDefault(packageMode)) { 200 return false; 201 } 202 if (!appOpIsChangedFromDefault(uidMode)) { 203 return true; 204 } 205 if (packageMode == AppOpsManager.MODE_ALLOWED) { 206 return true; 207 } 208 return false; 209 } 210 setAppOpForPackagesInUid( int uid, String[] packages, @AppOpsManager.Mode int mode)211 private void setAppOpForPackagesInUid( 212 int uid, String[] packages, @AppOpsManager.Mode int mode) { 213 for (String packageName : packages) { 214 setInteractAcrossProfilesAppOpForPackage(uid, packageName, mode); 215 } 216 } 217 218 /** 219 * Sets the package appop to default mode and the uid appop to {@code mode}. 220 */ setInteractAcrossProfilesAppOpForPackage( int uid, String packageName, @AppOpsManager.Mode int mode)221 private void setInteractAcrossProfilesAppOpForPackage( 222 int uid, String packageName, @AppOpsManager.Mode int mode) { 223 final String op = 224 AppOpsManager.permissionToOp(Manifest.permission.INTERACT_ACROSS_PROFILES); 225 int previousMode = mAppOpsManager.unsafeCheckOpNoThrow(op, uid, packageName); 226 mAppOpsManager.setMode( 227 OP_INTERACT_ACROSS_PROFILES, uid, packageName, 228 AppOpsManager.opToDefaultMode(OP_INTERACT_ACROSS_PROFILES)); 229 mAppOpsManager.setUidMode(OP_INTERACT_ACROSS_PROFILES, uid, mode); 230 int currentMode = mAppOpsManager.unsafeCheckOpNoThrow(op, uid, packageName); 231 ProvisionLogger.logi( 232 "Reapplying INTERACT_ACROSS_PROFILES for " + packageName + " with uid + " + uid 233 + ", previous mode: " + previousMode + ", current mode is " + currentMode); 234 } 235 appOpIsChangedFromDefault(String op, int uid, String packageName)236 private boolean appOpIsChangedFromDefault(String op, int uid, String packageName) { 237 return mAppOpsManager.unsafeCheckOpNoThrow(op, uid, packageName) 238 != AppOpsManager.MODE_DEFAULT; 239 } 240 appOpIsChangedFromDefault(int mode)241 private boolean appOpIsChangedFromDefault(int mode) { 242 return mode != AppOpsManager.MODE_DEFAULT; 243 } 244 } 245