• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.CREATE_USERS;
20 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
21 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
22 import static android.os.Build.VERSION.SDK_INT;
23 
24 import static com.android.bedstead.nene.permissions.CommonPermissions.FORCE_DEVICE_POLICY_MANAGER_LOGS;
25 import static com.android.bedstead.nene.permissions.CommonPermissions.MANAGE_DEVICE_ADMINS;
26 import static com.android.bedstead.nene.permissions.CommonPermissions.MANAGE_PROFILE_AND_DEVICE_OWNERS;
27 import static com.android.bedstead.nene.permissions.CommonPermissions.MANAGE_ROLE_HOLDERS;
28 import static com.android.bedstead.nene.utils.Versions.T;
29 
30 import static org.junit.Assert.fail;
31 
32 import android.annotation.TargetApi;
33 import android.app.admin.DevicePolicyManager;
34 import android.app.role.RoleManager;
35 import android.content.ComponentName;
36 import android.content.Intent;
37 import android.content.pm.PackageManager;
38 import android.content.pm.ResolveInfo;
39 import android.os.Build;
40 import android.util.Log;
41 
42 import androidx.annotation.Nullable;
43 
44 import com.android.bedstead.nene.TestApis;
45 import com.android.bedstead.nene.annotations.Experimental;
46 import com.android.bedstead.nene.exceptions.AdbException;
47 import com.android.bedstead.nene.exceptions.AdbParseException;
48 import com.android.bedstead.nene.exceptions.NeneException;
49 import com.android.bedstead.nene.packages.Package;
50 import com.android.bedstead.nene.permissions.PermissionContext;
51 import com.android.bedstead.nene.users.UserReference;
52 import com.android.bedstead.nene.utils.Poll;
53 import com.android.bedstead.nene.utils.Retry;
54 import com.android.bedstead.nene.utils.ShellCommand;
55 import com.android.bedstead.nene.utils.ShellCommandUtils;
56 import com.android.bedstead.nene.utils.Versions;
57 import com.android.compatibility.common.util.BlockingCallback;
58 
59 import java.time.Duration;
60 import java.util.Collection;
61 import java.util.List;
62 import java.util.Map;
63 import java.util.Set;
64 
65 
66 /**
67  * Test APIs related to device policy.
68  */
69 public final class DevicePolicy {
70 
71     public static final DevicePolicy sInstance = new DevicePolicy();
72 
73     private static final String LOG_TAG = "DevicePolicy";
74 
75     private final AdbDevicePolicyParser mParser;
76 
77     private DeviceOwner mCachedDeviceOwner;
78     private Map<UserReference, ProfileOwner> mCachedProfileOwners;
79 
DevicePolicy()80     private DevicePolicy() {
81         mParser = AdbDevicePolicyParser.get(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         try {
101             Retry.logic(command::execute)
102                     .terminalException((ex) -> {
103                         if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S)) {
104                             return false; // Just retry on old versions as we don't have stderr
105                         }
106                         if (ex instanceof AdbException) {
107                             String error = ((AdbException) ex).error();
108                             if (error.contains("is being removed")) {
109                                 return false;
110                             }
111                         }
112 
113                         // Assume all other errors are terminal
114                         return true;
115                     })
116                     .timeout(Duration.ofMinutes(5))
117                     .run();
118         } catch (Throwable e) {
119             throw new NeneException("Could not set profile owner for user "
120                     + user + " component " + profileOwnerComponent, e);
121         }
122 
123         Poll.forValue("Profile Owner", () -> TestApis.devicePolicy().getProfileOwner(user))
124                 .toNotBeNull()
125                 .errorOnFail()
126                 .await();
127 
128         return new ProfileOwner(user,
129                 TestApis.packages().find(
130                         profileOwnerComponent.getPackageName()), profileOwnerComponent);
131     }
132 
133     /**
134      * Get the profile owner for the instrumented user.
135      */
getProfileOwner()136     public ProfileOwner getProfileOwner() {
137         return getProfileOwner(TestApis.users().instrumented());
138     }
139 
140     /**
141      * Get the profile owner for a given {@link UserReference}.
142      */
getProfileOwner(UserReference user)143     public ProfileOwner getProfileOwner(UserReference user) {
144         if (user == null) {
145             throw new NullPointerException();
146         }
147         fillCache();
148         return mCachedProfileOwners.get(user);
149     }
150 
151     /**
152      * Set the device owner.
153      */
setDeviceOwner(ComponentName deviceOwnerComponent)154     public DeviceOwner setDeviceOwner(ComponentName deviceOwnerComponent) {
155         if (deviceOwnerComponent == null) {
156             throw new NullPointerException();
157         }
158 
159         if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S)) {
160             return setDeviceOwnerPreS(deviceOwnerComponent);
161         }
162 
163         DevicePolicyManager devicePolicyManager =
164                 TestApis.context().instrumentedContext()
165                         .getSystemService(DevicePolicyManager.class);
166         UserReference user = TestApis.users().system();
167 
168         boolean dpmUserSetupComplete = user.getSetupComplete();
169         Boolean currentUserSetupComplete = null;
170 
171         try {
172             user.setSetupComplete(false);
173 
174             try (PermissionContext p =
175                          TestApis.permissions().withPermission(
176                                  MANAGE_PROFILE_AND_DEVICE_OWNERS, MANAGE_DEVICE_ADMINS,
177                                  INTERACT_ACROSS_USERS_FULL, INTERACT_ACROSS_USERS, CREATE_USERS)) {
178 
179                 // TODO(b/187925230): If it fails, we check for terminal failure states - and if not
180                 //  we retry because if the DO/PO was recently removed, it can take some time
181                 //  to be allowed to set it again
182                 Retry.logic(() -> {
183                             devicePolicyManager.setActiveAdmin(deviceOwnerComponent,
184                                     /* refreshing= */ true, user.id());
185                             setDeviceOwnerOnly(devicePolicyManager,
186                                     deviceOwnerComponent, "Nene", user.id());
187                 }).terminalException((e) -> checkForTerminalDeviceOwnerFailures(
188                         user, deviceOwnerComponent, /* allowAdditionalUsers= */ true))
189                         .timeout(Duration.ofMinutes(5))
190                         .run();
191             } catch (Throwable e) {
192                 throw new NeneException("Error setting device owner", e);
193             }
194         } finally {
195             user.setSetupComplete(dpmUserSetupComplete);
196             if (currentUserSetupComplete != null) {
197                 TestApis.users().current().setSetupComplete(currentUserSetupComplete);
198             }
199         }
200 
201         Package deviceOwnerPackage = TestApis.packages().find(
202                 deviceOwnerComponent.getPackageName());
203 
204         Poll.forValue("Device Owner", () -> TestApis.devicePolicy().getDeviceOwner())
205                 .toNotBeNull()
206                 .errorOnFail()
207                 .await();
208 
209         return new DeviceOwner(user, deviceOwnerPackage, deviceOwnerComponent);
210     }
211 
212     /**
213      * Set Device Owner without changing any other device state.
214      *
215      * <p>This is used instead of {@link DevicePolicyManager#setDeviceOwner(ComponentName)} directly
216      * because on S_V2 and above, that method can also set profile owners and install packages in
217      * some circumstances.
218      */
setDeviceOwnerOnly(DevicePolicyManager devicePolicyManager, ComponentName component, String name, int deviceOwnerUserId)219     private void setDeviceOwnerOnly(DevicePolicyManager devicePolicyManager,
220             ComponentName component, String name, int deviceOwnerUserId) {
221         if (Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S_V2)) {
222             devicePolicyManager.setDeviceOwnerOnly(component, name, deviceOwnerUserId);
223         } else {
224             devicePolicyManager.setDeviceOwner(component, name, deviceOwnerUserId);
225         }
226     }
227 
228     /**
229      * Resets organization ID via @TestApi.
230      * @param user whose organization ID to clear
231      */
clearOrganizationId(UserReference user)232     public void clearOrganizationId(UserReference user) {
233         try (PermissionContext p =
234                      TestApis.permissions().withPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)) {
235             DevicePolicyManager devicePolicyManager =
236                     TestApis.context().instrumentedContextAsUser(user)
237                             .getSystemService(DevicePolicyManager.class);
238             devicePolicyManager.clearOrganizationId();
239         }
240     }
241 
setDeviceOwnerPreS(ComponentName deviceOwnerComponent)242     private DeviceOwner setDeviceOwnerPreS(ComponentName deviceOwnerComponent) {
243         UserReference user = TestApis.users().system();
244 
245         ShellCommand.Builder command = ShellCommand.builderForUser(
246                 user, "dpm set-device-owner")
247                 .addOperand(deviceOwnerComponent.flattenToShortString())
248                 .validate(ShellCommandUtils::startsWithSuccess);
249         // TODO(b/187925230): If it fails, we check for terminal failure states - and if not
250         //  we retry because if the device owner was recently removed, it can take some time
251         //  to be allowed to set it again
252 
253         try {
254             Retry.logic(command::execute)
255                 .terminalException((e) -> checkForTerminalDeviceOwnerFailures(
256                             user, deviceOwnerComponent, /* allowAdditionalUsers= */ false))
257                     .timeout(Duration.ofMinutes(5))
258                     .run();
259         } catch (Throwable e) {
260             throw new NeneException("Error setting device owner", e);
261         }
262 
263         return new DeviceOwner(user,
264                 TestApis.packages().find(
265                         deviceOwnerComponent.getPackageName()), deviceOwnerComponent);
266     }
267 
checkForTerminalDeviceOwnerFailures( UserReference user, ComponentName deviceOwnerComponent, boolean allowAdditionalUsers)268     private boolean checkForTerminalDeviceOwnerFailures(
269             UserReference user, ComponentName deviceOwnerComponent, boolean allowAdditionalUsers) {
270         DeviceOwner deviceOwner = getDeviceOwner();
271         if (deviceOwner != null) {
272             // TODO(scottjonathan): Should we actually fail here if the component name is the
273             //  same?
274 
275             throw new NeneException(
276                     "Could not set device owner for user " + user
277                             + " as a device owner is already set: " + deviceOwner);
278         }
279 
280         Package pkg = TestApis.packages().find(
281                 deviceOwnerComponent.getPackageName());
282         if (!TestApis.packages().installedForUser(user).contains(pkg)) {
283             throw new NeneException(
284                     "Could not set device owner for user " + user
285                             + " as the package " + pkg + " is not installed");
286         }
287 
288         if (!componentCanBeSetAsDeviceAdmin(deviceOwnerComponent, user)) {
289             throw new NeneException("Could not set device owner for user "
290                     + user + " as component " + deviceOwnerComponent + " is not valid");
291         }
292 
293         if (!allowAdditionalUsers) {
294             Collection<UserReference> users = TestApis.users().all();
295 
296             if (users.size() > 1) {
297                 throw new NeneException("Could not set device owner for user "
298                         + user + " as there are already additional users on the device: " + users);
299             }
300 
301         }
302         // TODO(scottjonathan): Check accounts
303 
304         return false;
305     }
306 
componentCanBeSetAsDeviceAdmin(ComponentName component, UserReference user)307     private boolean componentCanBeSetAsDeviceAdmin(ComponentName component, UserReference user) {
308         PackageManager packageManager =
309                 TestApis.context().instrumentedContext().getPackageManager();
310         Intent intent = new Intent("android.app.action.DEVICE_ADMIN_ENABLED");
311         intent.setComponent(component);
312 
313         try (PermissionContext p =
314                      TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
315             List<ResolveInfo> r =
316                     packageManager.queryBroadcastReceiversAsUser(
317                             intent, /* flags= */ 0, user.userHandle());
318             return (!r.isEmpty());
319         }
320     }
321 
322     /**
323      * Get the device owner.
324      */
325     @Nullable
getDeviceOwner()326     public DeviceOwner getDeviceOwner() {
327         fillCache();
328         return mCachedDeviceOwner;
329     }
330 
fillCache()331     private void fillCache() {
332         int retries = 5;
333         while (true) {
334             try {
335                 // TODO: Replace use of adb on supported versions of Android
336                 String devicePolicyDumpsysOutput =
337                         ShellCommand.builder("dumpsys device_policy").execute();
338                 AdbDevicePolicyParser.ParseResult result = mParser.parse(devicePolicyDumpsysOutput);
339 
340                 mCachedDeviceOwner = result.mDeviceOwner;
341                 mCachedProfileOwners = result.mProfileOwners;
342 
343                 return;
344             } catch (AdbParseException e) {
345                 if (e.adbOutput().contains("DUMP TIMEOUT") && retries-- > 0) {
346                     // Sometimes this call times out - just retry
347                     Log.e(LOG_TAG, "Dump timeout when filling cache, retrying", e);
348                 } else {
349                     throw new NeneException("Error filling cache", e);
350                 }
351             } catch (AdbException e) {
352                 throw new NeneException("Error filling cache", e);
353             }
354         }
355     }
356 
357     /** See {@link android.app.admin.DevicePolicyManager#getPolicyExemptApps()}. */
358     @Experimental
getPolicyExemptApps()359     public Set<String> getPolicyExemptApps() {
360         try (PermissionContext p = TestApis.permissions().withPermission(MANAGE_DEVICE_ADMINS)) {
361             return TestApis.context()
362                     .instrumentedContext()
363                     .getSystemService(DevicePolicyManager.class)
364                     .getPolicyExemptApps();
365         }
366     }
367 
368     @Experimental
forceNetworkLogs()369     public void forceNetworkLogs() {
370         try (PermissionContext p = TestApis.permissions().withPermission(FORCE_DEVICE_POLICY_MANAGER_LOGS)) {
371             long throttle = TestApis.context()
372                     .instrumentedContext()
373                     .getSystemService(DevicePolicyManager.class)
374                     .forceNetworkLogs();
375 
376             if (throttle == -1) {
377                 throw new NeneException("Error forcing network logs: returned -1");
378             }
379             if (throttle == 0) {
380                 return;
381             }
382             try {
383                 Thread.sleep(throttle);
384             } catch (InterruptedException e) {
385                 throw new NeneException("Error waiting for network log throttle", e);
386             }
387 
388             forceNetworkLogs();
389         }
390     }
391 
392     /**
393      * Sets the provided {@code packageName} as a device policy management role holder.
394      */
395     @TargetApi(Build.VERSION_CODES.TIRAMISU)
396     @Experimental
setDevicePolicyManagementRoleHolder(String packageName)397     public void setDevicePolicyManagementRoleHolder(String packageName)
398             throws InterruptedException {
399         if (!Versions.meetsMinimumSdkVersionRequirement(T)) {
400             return;
401         }
402         try (PermissionContext p = TestApis.permissions().withPermission(
403                 MANAGE_ROLE_HOLDERS)) {
404             DefaultBlockingCallback blockingCallback = new DefaultBlockingCallback();
405             RoleManager roleManager = TestApis.context().instrumentedContext()
406                     .getSystemService(RoleManager.class);
407             TestApis.roles().setBypassingRoleQualification(true);
408             roleManager.addRoleHolderAsUser(
409                     RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT,
410                     packageName,
411                     /* flags= */ 0,
412                     TestApis.context().instrumentationContext().getUser(),
413                     TestApis.context().instrumentedContext().getMainExecutor(),
414                     blockingCallback::triggerCallback);
415 
416             boolean success = blockingCallback.await();
417             if (!success) {
418                 fail("Could not set role holder of "
419                         + RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT + ".");
420             }
421         }
422     }
423 
424     /**
425      * Unsets the provided {@code packageName} as a device policy management role holder.
426      */
427     @TargetApi(Build.VERSION_CODES.TIRAMISU)
428     @Experimental
unsetDevicePolicyManagementRoleHolder(String packageName)429     public void unsetDevicePolicyManagementRoleHolder(String packageName)
430             throws InterruptedException {
431         if (!Versions.meetsMinimumSdkVersionRequirement(T)) {
432             return;
433         }
434         try (PermissionContext p = TestApis.permissions().withPermission(
435                 MANAGE_ROLE_HOLDERS)) {
436             DefaultBlockingCallback blockingCallback = new DefaultBlockingCallback();
437             RoleManager roleManager = TestApis.context().instrumentedContext()
438                     .getSystemService(RoleManager.class);
439             roleManager.removeRoleHolderAsUser(
440                     RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT,
441                     packageName,
442                     /* flags= */ 0,
443                     TestApis.context().instrumentationContext().getUser(),
444                     TestApis.context().instrumentedContext().getMainExecutor(),
445                     blockingCallback::triggerCallback);
446             TestApis.roles().setBypassingRoleQualification(false);
447 
448             boolean success = blockingCallback.await();
449             if (!success) {
450                 fail("Failed to clear the role holder of "
451                         + RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT + ".");
452             }
453         }
454     }
455 
456     private static class DefaultBlockingCallback extends BlockingCallback<Boolean> {
triggerCallback(Boolean success)457         public void triggerCallback(Boolean success) {
458             callbackTriggered(success);
459         }
460     }
461 }
462