1 /* 2 * Copyright (C) 2021 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.bedstead.nene.devicepolicy; 18 19 import static android.Manifest.permission.INTERACT_ACROSS_USERS; 20 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; 21 import static android.Manifest.permission.WRITE_SECURE_SETTINGS; 22 import static android.os.Build.VERSION.SDK_INT; 23 24 import static com.android.bedstead.nene.permissions.Permissions.MANAGE_DEVICE_ADMINS; 25 import static com.android.bedstead.nene.permissions.Permissions.MANAGE_PROFILE_AND_DEVICE_OWNERS; 26 27 import android.app.admin.DevicePolicyManager; 28 import android.content.ComponentName; 29 import android.content.Intent; 30 import android.content.pm.PackageManager; 31 import android.content.pm.ResolveInfo; 32 import android.os.Build; 33 import android.provider.Settings; 34 import android.util.Log; 35 36 import androidx.annotation.Nullable; 37 38 import com.android.bedstead.nene.TestApis; 39 import com.android.bedstead.nene.annotations.Experimental; 40 import com.android.bedstead.nene.exceptions.AdbException; 41 import com.android.bedstead.nene.exceptions.AdbParseException; 42 import com.android.bedstead.nene.exceptions.NeneException; 43 import com.android.bedstead.nene.packages.PackageReference; 44 import com.android.bedstead.nene.permissions.PermissionContext; 45 import com.android.bedstead.nene.users.User; 46 import com.android.bedstead.nene.users.UserReference; 47 import com.android.bedstead.nene.utils.ShellCommand; 48 import com.android.bedstead.nene.utils.ShellCommandUtils; 49 import com.android.bedstead.nene.utils.Versions; 50 import com.android.compatibility.common.util.PollingCheck; 51 52 import java.util.Arrays; 53 import java.util.Collection; 54 import java.util.HashSet; 55 import java.util.List; 56 import java.util.Map; 57 import java.util.Set; 58 59 60 /** 61 * Test APIs related to device policy. 62 */ 63 public final class DevicePolicy { 64 65 private static final String LOG_TAG = "DevicePolicy"; 66 67 private static final String USER_SETUP_COMPLETE_KEY = "user_setup_complete"; 68 69 private final TestApis mTestApis; 70 private final AdbDevicePolicyParser mParser; 71 72 private DeviceOwner mCachedDeviceOwner; 73 private Map<UserReference, ProfileOwner> mCachedProfileOwners; 74 DevicePolicy(TestApis testApis)75 public DevicePolicy(TestApis testApis) { 76 if (testApis == null) { 77 throw new NullPointerException(); 78 } 79 80 mTestApis = testApis; 81 mParser = AdbDevicePolicyParser.get(mTestApis, SDK_INT); 82 } 83 84 /** 85 * Set the profile owner for a given {@link UserReference}. 86 */ setProfileOwner(UserReference user, ComponentName profileOwnerComponent)87 public ProfileOwner setProfileOwner(UserReference user, ComponentName profileOwnerComponent) { 88 if (user == null || profileOwnerComponent == null) { 89 throw new NullPointerException(); 90 } 91 92 ShellCommand.Builder command = 93 ShellCommand.builderForUser(user, "dpm set-profile-owner") 94 .addOperand(profileOwnerComponent.flattenToShortString()) 95 .validate(ShellCommandUtils::startsWithSuccess); 96 97 // TODO(b/187925230): If it fails, we check for terminal failure states - and if not 98 // we retry because if the profile owner was recently removed, it can take some time 99 // to be allowed to set it again 100 retryIfNotTerminal( 101 () -> command.executeOrThrowNeneException("Could not set profile owner for user " 102 + user + " component " + profileOwnerComponent), 103 () -> checkForTerminalProfileOwnerFailures(user, profileOwnerComponent)); 104 return new ProfileOwner(mTestApis, user, 105 mTestApis.packages().find( 106 profileOwnerComponent.getPackageName()), profileOwnerComponent); 107 } 108 checkForTerminalProfileOwnerFailures( UserReference user, ComponentName profileOwnerComponent)109 private void checkForTerminalProfileOwnerFailures( 110 UserReference user, ComponentName profileOwnerComponent) { 111 ProfileOwner profileOwner = getProfileOwner(user); 112 if (profileOwner != null) { 113 // TODO(scottjonathan): Should we actually fail here if the component name is the 114 // same? 115 116 throw new NeneException( 117 "Could not set profile owner for user " + user 118 + " as a profile owner is already set: " + profileOwner); 119 } 120 121 PackageReference pkg = mTestApis.packages().find( 122 profileOwnerComponent.getPackageName()); 123 if (!mTestApis.packages().installedForUser(user).contains(pkg)) { 124 throw new NeneException( 125 "Could not set profile owner for user " + user 126 + " as the package " + pkg + " is not installed"); 127 } 128 129 if (!componentCanBeSetAsDeviceAdmin(profileOwnerComponent, user)) { 130 throw new NeneException("Could not set profile owner for user " 131 + user + " as component " + profileOwnerComponent + " is not valid"); 132 } 133 } 134 135 /** 136 * Get the profile owner for a given {@link UserReference}. 137 */ getProfileOwner(UserReference user)138 public ProfileOwner getProfileOwner(UserReference user) { 139 if (user == null) { 140 throw new NullPointerException(); 141 } 142 fillCache(); 143 return mCachedProfileOwners.get(user); 144 } 145 146 /** 147 * Set the device owner. 148 */ setDeviceOwner(UserReference user, ComponentName deviceOwnerComponent)149 public DeviceOwner setDeviceOwner(UserReference user, ComponentName deviceOwnerComponent) { 150 if (user == null || deviceOwnerComponent == null) { 151 throw new NullPointerException(); 152 } 153 154 if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S)) { 155 return setDeviceOwnerPreS(user, deviceOwnerComponent); 156 } 157 158 DevicePolicyManager devicePolicyManager = 159 mTestApis.context().instrumentedContext() 160 .getSystemService(DevicePolicyManager.class); 161 162 boolean userSetupComplete = getUserSetupComplete(); 163 164 try { 165 setUserSetupComplete(false); 166 167 try (PermissionContext p = 168 mTestApis.permissions().withPermission( 169 MANAGE_PROFILE_AND_DEVICE_OWNERS, MANAGE_DEVICE_ADMINS, 170 INTERACT_ACROSS_USERS_FULL, INTERACT_ACROSS_USERS)) { 171 devicePolicyManager.setActiveAdmin(deviceOwnerComponent, 172 /* refreshing= */ true, user.id()); 173 174 // TODO(b/187925230): If it fails, we check for terminal failure states - and if not 175 // we retry because if the DO/PO was recently removed, it can take some time 176 // to be allowed to set it again 177 retryIfNotTerminal( 178 () -> devicePolicyManager.setDeviceOwner( 179 deviceOwnerComponent, "Nene", user.id()), 180 () -> checkForTerminalDeviceOwnerFailures( 181 user, deviceOwnerComponent, /* allowAdditionalUsers= */ true)); 182 } catch (IllegalArgumentException | IllegalStateException | SecurityException e) { 183 throw new NeneException("Error setting device owner", e); 184 } 185 } finally { 186 setUserSetupComplete(userSetupComplete); 187 } 188 189 return new DeviceOwner(mTestApis, user, 190 mTestApis.packages().find( 191 deviceOwnerComponent.getPackageName()), deviceOwnerComponent); 192 } 193 194 /** 195 * Runs {@code operation}. If it fails, runs {@code terminalCheck} and then retries 196 * {@code operation} until it does not fail or for a maximum of 30 seconds. 197 * 198 * <p>The {@code operation} is considered to be successful if it does not throw an exception 199 */ retryIfNotTerminal( Runnable operation, Runnable terminalCheck, Class<? extends RuntimeException>... exceptions)200 private void retryIfNotTerminal( 201 Runnable operation, Runnable terminalCheck, 202 Class<? extends RuntimeException>... exceptions) { 203 Set<Class<? extends RuntimeException>> exceptionSet = 204 new HashSet<>(Arrays.asList(exceptions)); 205 try { 206 operation.run(); 207 } catch (RuntimeException e) { 208 if (!exceptionSet.contains(e.getClass())) { 209 throw e; 210 } 211 212 terminalCheck.run(); 213 214 try { 215 PollingCheck.waitFor(30_000, () -> { 216 try { 217 operation.run(); 218 return true; 219 } catch (RuntimeException e2) { 220 if (!exceptionSet.contains(e2.getClass())) { 221 throw e2; 222 } 223 return false; 224 } 225 }); 226 } catch (AssertionError e3) { 227 operation.run(); 228 } 229 } 230 } 231 232 setUserSetupComplete(boolean complete)233 private void setUserSetupComplete(boolean complete) { 234 DevicePolicyManager devicePolicyManager = 235 mTestApis.context().instrumentedContext() 236 .getSystemService(DevicePolicyManager.class); 237 try (PermissionContext p = mTestApis.permissions().withPermission( 238 WRITE_SECURE_SETTINGS, MANAGE_PROFILE_AND_DEVICE_OWNERS, 239 INTERACT_ACROSS_USERS_FULL)) { 240 Settings.Secure.putInt(mTestApis.context().androidContextAsUser( 241 mTestApis.users().system()).getContentResolver(), 242 USER_SETUP_COMPLETE_KEY, complete ? 1 : 0); 243 devicePolicyManager.forceUpdateUserSetupComplete(mTestApis.users().system().id()); 244 } 245 } 246 getUserSetupComplete()247 private boolean getUserSetupComplete() { 248 return Settings.Secure.getInt( 249 mTestApis.context().instrumentedContext().getContentResolver(), 250 USER_SETUP_COMPLETE_KEY, /* def= */ 0) == 1; 251 } 252 setDeviceOwnerPreS(UserReference user, ComponentName deviceOwnerComponent)253 private DeviceOwner setDeviceOwnerPreS(UserReference user, ComponentName deviceOwnerComponent) { 254 ShellCommand.Builder command = ShellCommand.builderForUser( 255 user, "dpm set-device-owner") 256 .addOperand(deviceOwnerComponent.flattenToShortString()) 257 .validate(ShellCommandUtils::startsWithSuccess); 258 259 // TODO(b/187925230): If it fails, we check for terminal failure states - and if not 260 // we retry because if the device owner was recently removed, it can take some time 261 // to be allowed to set it again 262 retryIfNotTerminal( 263 () -> command.executeOrThrowNeneException("Could not set device owner for user " 264 + user + " component " + deviceOwnerComponent), 265 () -> checkForTerminalDeviceOwnerFailures( 266 user, deviceOwnerComponent, /* allowAdditionalUsers= */ false)); 267 268 return new DeviceOwner(mTestApis, user, 269 mTestApis.packages().find( 270 deviceOwnerComponent.getPackageName()), deviceOwnerComponent); 271 } 272 checkForTerminalDeviceOwnerFailures( UserReference user, ComponentName deviceOwnerComponent, boolean allowAdditionalUsers)273 private void checkForTerminalDeviceOwnerFailures( 274 UserReference user, ComponentName deviceOwnerComponent, boolean allowAdditionalUsers) { 275 DeviceOwner deviceOwner = getDeviceOwner(); 276 if (deviceOwner != null) { 277 // TODO(scottjonathan): Should we actually fail here if the component name is the 278 // same? 279 280 throw new NeneException( 281 "Could not set device owner for user " + user 282 + " as a device owner is already set: " + deviceOwner); 283 } 284 285 PackageReference pkg = mTestApis.packages().find( 286 deviceOwnerComponent.getPackageName()); 287 if (!mTestApis.packages().installedForUser(user).contains(pkg)) { 288 throw new NeneException( 289 "Could not set device owner for user " + user 290 + " as the package " + pkg + " is not installed"); 291 } 292 293 if (!componentCanBeSetAsDeviceAdmin(deviceOwnerComponent, user)) { 294 throw new NeneException("Could not set device owner for user " 295 + user + " as component " + deviceOwnerComponent + " is not valid"); 296 } 297 298 if (!allowAdditionalUsers) { 299 Collection<User> users = mTestApis.users().all(); 300 301 if (users.size() > 1) { 302 throw new NeneException("Could not set device owner for user " 303 + user + " as there are already additional users on the device: " + users); 304 } 305 306 } 307 // TODO(scottjonathan): Check accounts 308 } 309 componentCanBeSetAsDeviceAdmin(ComponentName component, UserReference user)310 private boolean componentCanBeSetAsDeviceAdmin(ComponentName component, UserReference user) { 311 PackageManager packageManager = 312 mTestApis.context().instrumentedContext().getPackageManager(); 313 Intent intent = new Intent("android.app.action.DEVICE_ADMIN_ENABLED"); 314 intent.setComponent(component); 315 316 try (PermissionContext p = 317 mTestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) { 318 List<ResolveInfo> r = 319 packageManager.queryBroadcastReceiversAsUser( 320 intent, /* flags= */ 0, user.userHandle()); 321 return (!r.isEmpty()); 322 } 323 } 324 325 /** 326 * Get the device owner. 327 */ 328 @Nullable getDeviceOwner()329 public DeviceOwner getDeviceOwner() { 330 fillCache(); 331 return mCachedDeviceOwner; 332 } 333 fillCache()334 private void fillCache() { 335 int retries = 5; 336 while (true) { 337 try { 338 // TODO: Replace use of adb on supported versions of Android 339 String devicePolicyDumpsysOutput = 340 ShellCommand.builder("dumpsys device_policy").execute(); 341 AdbDevicePolicyParser.ParseResult result = mParser.parse(devicePolicyDumpsysOutput); 342 343 mCachedDeviceOwner = result.mDeviceOwner; 344 mCachedProfileOwners = result.mProfileOwners; 345 return; 346 } catch (AdbParseException e) { 347 if (e.adbOutput().contains("DUMP TIMEOUT") && retries-- > 0) { 348 // Sometimes this call times out - just retry 349 Log.e(LOG_TAG, "Dump timeout when filling cache, retrying", e); 350 } else { 351 throw new NeneException("Error filling cache", e); 352 } 353 } catch (AdbException e) { 354 throw new NeneException("Error filling cache", e); 355 } 356 } 357 } 358 359 /** See {@link android.app.admin.DevicePolicyManager#getPolicyExemptApps()}. */ 360 @Experimental getPolicyExemptApps()361 public Set<String> getPolicyExemptApps() { 362 try (PermissionContext p = mTestApis.permissions().withPermission(MANAGE_DEVICE_ADMINS)) { 363 return mTestApis.context() 364 .instrumentedContext() 365 .getSystemService(DevicePolicyManager.class) 366 .getPolicyExemptApps(); 367 } 368 } 369 } 370