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