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.remotedpc;
18 
19 import static android.os.UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES;
20 
21 import static com.android.bedstead.permissions.CommonPermissions.MANAGE_PROFILE_AND_DEVICE_OWNERS;
22 import static com.android.bedstead.nene.users.UserType.MANAGED_PROFILE_TYPE_NAME;
23 
24 import android.app.admin.DevicePolicyManager;
25 import android.app.admin.ManagedProfileProvisioningParams;
26 import android.app.admin.ProvisioningException;
27 import android.content.ComponentName;
28 import android.os.Build;
29 import android.os.UserHandle;
30 import android.util.Log;
31 
32 import androidx.annotation.Nullable;
33 
34 import com.android.bedstead.nene.TestApis;
35 import com.android.bedstead.nene.annotations.Experimental;
36 import com.android.bedstead.nene.devicepolicy.DeviceOwner;
37 import com.android.bedstead.nene.devicepolicy.DevicePolicyController;
38 import com.android.bedstead.nene.devicepolicy.ProfileOwner;
39 import com.android.bedstead.nene.exceptions.NeneException;
40 import com.android.bedstead.permissions.PermissionContext;
41 import com.android.bedstead.nene.users.UserReference;
42 import com.android.bedstead.nene.utils.Versions;
43 import com.android.bedstead.testapp.TestApp;
44 import com.android.bedstead.testapp.TestAppProvider;
45 import com.android.bedstead.testapp.TestAppQueryBuilder;
46 
47 /** Entry point to RemoteDPC. */
48 public class RemoteDpc extends RemotePolicyManager {
49 
50     public static final String REMOTE_DPC_APP_PACKAGE_NAME_OR_PREFIX = "com.android.cts.RemoteDPC";
51     private static final String TEST_APP_CLASS_NAME =
52             "com.android.bedstead.testapp.BaseTestAppDeviceAdminReceiver";
53     private static final String LOG_TAG = "RemoteDpc";
54 
55     private static final DevicePolicyManager sDevicePolicyManager =
56             TestApis.context().instrumentedContext().getSystemService(DevicePolicyManager.class);
57     private static final TestAppProvider sTestAppProvider = new TestAppProvider();
58 
59     private boolean mShouldRemoveUserWhenRemoved = false;
60 
61     /**
62      * Get the {@link RemoteDpc} instance for the Device Owner.
63      *
64      * <p>This will return {@code null} if there is no Device Owner or it is not a RemoteDPC app.
65      */
66     @Nullable
deviceOwner()67     public static RemoteDpc deviceOwner() {
68         DeviceOwner deviceOwner = TestApis.devicePolicy().getDeviceOwner();
69         if (!isRemoteDpc(deviceOwner)) {
70             return null;
71         }
72 
73         TestApp remoteDpcTestApp = new TestAppProvider().query().wherePackageName()
74                 .isEqualTo(deviceOwner.componentName().getPackageName())
75                 .get();
76         return new RemoteDpc(remoteDpcTestApp, deviceOwner);
77     }
78 
79     /**
80      * Get the {@link RemoteDpc} instance for the Profile Owner of the current user.
81      *
82      * <p>This will return null if there is no Profile Owner or it is not a RemoteDPC app.
83      */
84     @Nullable
profileOwner()85     public static RemoteDpc profileOwner() {
86         return profileOwner(TestApis.users().instrumented());
87     }
88 
89     /**
90      * Get the {@link RemoteDpc} instance for the Profile Owner of the given {@code profile}.
91      *
92      * <p>This will return null if there is no Profile Owner or it is not a RemoteDPC app.
93      */
94     @Nullable
profileOwner(UserHandle profile)95     public static RemoteDpc profileOwner(UserHandle profile) {
96         if (profile == null) {
97             throw new NullPointerException();
98         }
99 
100         return profileOwner(TestApis.users().find(profile));
101     }
102 
103     /**
104      * Get the {@link RemoteDpc} instance for the Profile Owner of the given {@code profile}.
105      *
106      * <p>This will return null if there is no Profile Owner or it is not a RemoteDPC app.
107      */
108     @Nullable
profileOwner(UserReference profile)109     public static RemoteDpc profileOwner(UserReference profile) {
110         if (profile == null) {
111             throw new NullPointerException();
112         }
113 
114         ProfileOwner profileOwner = TestApis.devicePolicy().getProfileOwner(profile);
115         if (!isRemoteDpc(profileOwner)) {
116             return null;
117         }
118 
119         TestApp remoteDpcTestApp = new TestAppProvider().query().wherePackageName()
120                 .isEqualTo(profileOwner.componentName().getPackageName())
121                 .get();
122         return new RemoteDpc(remoteDpcTestApp, profileOwner);
123     }
124 
125     /**
126      * Get the most specific {@link RemoteDpc} instance for the current user.
127      *
128      * <p>If the user has a RemoteDPC Profile Owner, this will refer to that. If it does not but
129      * has a RemoteDPC Device Owner it will refer to that. Otherwise it will return null.
130      */
131     @Nullable
any()132     public static RemoteDpc any() {
133         return any(TestApis.users().instrumented());
134     }
135 
136     /**
137      * Get the most specific {@link RemoteDpc} instance for the current user.
138      *
139      * <p>If the user has a RemoteDPC Profile Owner, this will refer to that. If it does not but
140      * has a RemoteDPC Device Owner it will refer to that. Otherwise it will return null.
141      */
142     @Nullable
any(UserHandle user)143     public static RemoteDpc any(UserHandle user) {
144         if (user == null) {
145             throw new NullPointerException();
146         }
147 
148         return any(TestApis.users().find(user));
149     }
150 
151     /**
152      * Get the most specific {@link RemoteDpc} instance for the current user.
153      *
154      * <p>If the user has a RemoteDPC Profile Owner, this will refer to that. If it does not but
155      * has a RemoteDPC Device Owner it will refer to that. Otherwise it will return null.
156      */
157     @Nullable
any(UserReference user)158     public static RemoteDpc any(UserReference user) {
159         RemoteDpc remoteDPC = profileOwner(user);
160         if (remoteDPC != null) {
161             return remoteDPC;
162         }
163         return deviceOwner();
164     }
165 
166     /**
167      * Get the {@link RemoteDpc} controller for the given {@link DevicePolicyController}.
168      */
forDevicePolicyController(DevicePolicyController controller)169     public static RemoteDpc forDevicePolicyController(DevicePolicyController controller) {
170         if (controller == null) {
171             throw new NullPointerException();
172         }
173 
174         if (isRemoteDpc(controller)) {
175             TestApp remoteDpcTestApp = new TestAppProvider().query().wherePackageName()
176                     .isEqualTo(controller.componentName().getPackageName())
177                     .get();
178 
179             return new RemoteDpc(remoteDpcTestApp, controller);
180         }
181 
182         throw new IllegalStateException("DevicePolicyController is not a RemoteDPC: "
183                 + controller);
184     }
185 
186     /**
187      * Set RemoteDPC as the Device Owner.
188      */
setAsDeviceOwner()189     public static RemoteDpc setAsDeviceOwner() {
190         return setAsDeviceOwner(new TestAppProvider().query().wherePackageName()
191                 .isEqualTo(REMOTE_DPC_APP_PACKAGE_NAME_OR_PREFIX));
192     }
193 
194     /**
195      * Sets RemoteDPC as the Device Owner on the system user based on TestAppQuery
196      */
setAsDeviceOwner(TestAppQueryBuilder dpcQuery)197     public static RemoteDpc setAsDeviceOwner(TestAppQueryBuilder dpcQuery) {
198         return setAsDeviceOwner(dpcQuery, TestApis.users().system());
199     }
200 
201     /**
202      * Sets RemoteDPC as the Device Owner on the given user based on TestAppQuery
203      */
setAsDeviceOwner(TestAppQueryBuilder dpcQuery, UserReference user)204     public static RemoteDpc setAsDeviceOwner(TestAppQueryBuilder dpcQuery, UserReference user) {
205         // We make sure that the query has RemoteDpc filter specified,
206         // this is useful for the case where the user calls the method directly
207         // and does not specify the RemoteDpc filter.
208         dpcQuery = enforceRemoteDpcPackageFilter(dpcQuery);
209 
210         DeviceOwner currentDeviceOwner = TestApis.devicePolicy().getDeviceOwner();
211         if (matchesRemoteDpcQuery(currentDeviceOwner, dpcQuery)) {
212             return RemoteDpc.forDevicePolicyController(currentDeviceOwner);
213         }
214 
215         if (currentDeviceOwner != null) {
216             currentDeviceOwner.remove();
217         }
218 
219         TestApp testApp = dpcQuery.get();
220         testApp.install(user);
221         Log.i(LOG_TAG, "Installing RemoteDPC app: " + testApp.packageName());
222         ComponentName componentName =
223                 new ComponentName(testApp.packageName(), TEST_APP_CLASS_NAME);
224         DeviceOwner deviceOwner = TestApis.devicePolicy().setDeviceOwner(componentName, user);
225         return new RemoteDpc(testApp, deviceOwner);
226     }
227 
228     /**
229      * Set any RemoteDPC as the Profile Owner of the instrumented user.
230      */
setAsProfileOwner()231     public static RemoteDpc setAsProfileOwner() {
232         return setAsProfileOwner(TestApis.users().instrumented());
233     }
234 
235     /**
236      * Set RemoteDPC that matches the query as the Profile Owner of the instrumented user.
237      */
setAsProfileOwner(TestAppQueryBuilder dpcQuery)238     public static RemoteDpc setAsProfileOwner(TestAppQueryBuilder dpcQuery) {
239         return setAsProfileOwner(TestApis.users().instrumented(), dpcQuery);
240     }
241 
242     /**
243      * Set any RemoteDPC as the Profile Owner.
244      */
setAsProfileOwner(UserHandle user)245     public static RemoteDpc setAsProfileOwner(UserHandle user) {
246         if (user == null) {
247             throw new NullPointerException();
248         }
249 
250         TestAppQueryBuilder anyRemoteDpcQuery = new TestAppProvider().query()
251                 .wherePackageName().startsWith(REMOTE_DPC_APP_PACKAGE_NAME_OR_PREFIX);
252         return setAsProfileOwner(TestApis.users().find(user), anyRemoteDpcQuery);
253     }
254 
255     /**
256      * Set RemoteDPC that matches the query as the Profile Owner.
257      */
setAsProfileOwner( UserHandle user, TestAppQueryBuilder dpcQuery)258     public static RemoteDpc setAsProfileOwner(
259             UserHandle user, TestAppQueryBuilder dpcQuery) {
260         if (user == null) {
261             throw new NullPointerException();
262         }
263         return setAsProfileOwner(TestApis.users().find(user), dpcQuery);
264     }
265 
266     /**
267      * Set RemoteDPC as the Profile Owner.
268      *
269      * <p>If called for Android versions prior to Q, an exception will be thrown if the user is not
270      * the instrumented user.
271      */
setAsProfileOwner(UserReference user)272     public static RemoteDpc setAsProfileOwner(UserReference user) {
273         if (user == null) {
274             throw new NullPointerException();
275         }
276 
277         TestAppQueryBuilder anyRemoteDpcQuery = new TestAppProvider().query()
278                 .wherePackageName().startsWith(REMOTE_DPC_APP_PACKAGE_NAME_OR_PREFIX);
279         return setAsProfileOwner(user, anyRemoteDpcQuery);
280     }
281 
282     /**
283      * Set RemoteDPC that matches the query as the Profile Owner.
284      *
285      * <p>If called for Android versions prior to Q, an exception will be thrown if the user is not
286      * the instrumented user.
287      */
setAsProfileOwner( UserReference user, TestAppQueryBuilder dpcQuery)288     public static RemoteDpc setAsProfileOwner(
289             UserReference user, TestAppQueryBuilder dpcQuery) {
290         // We make sure that the query has RemoteDpc filter specified,
291         // this is useful for the case where the user calls the method directly
292         // and does not specify the RemoteDpc filter.
293         dpcQuery = enforceRemoteDpcPackageFilter(dpcQuery);
294 
295         if (user == null) {
296             throw new NullPointerException();
297         }
298 
299         if (!user.equals(TestApis.users().instrumented())) {
300             if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.Q)) {
301                 throw new NeneException("Cannot use RemoteDPC across users prior to Q");
302             }
303         }
304 
305         ProfileOwner currentProfileOwner = TestApis.devicePolicy().getProfileOwner(user);
306         if (matchesRemoteDpcQuery(currentProfileOwner, dpcQuery)) {
307             return RemoteDpc.forDevicePolicyController(currentProfileOwner);
308         }
309 
310         return setAsProfileOwner(user, dpcQuery.get());
311     }
312 
313     /**
314      * Set specific RemoteDPC {@link TestApp} as the Profile Owner.
315      *
316      * <p>If called for Android versions prior to Q, an exception will be thrown if the user is not
317      * the instrumented user.
318      */
setAsProfileOwner( UserReference user, TestApp dpcTestApp)319     public static RemoteDpc setAsProfileOwner(
320             UserReference user, TestApp dpcTestApp) {
321         if (!dpcTestApp.pkg().packageName().startsWith(REMOTE_DPC_APP_PACKAGE_NAME_OR_PREFIX)) {
322             throw new IllegalArgumentException("setAsProfileOwner test app must be a RemoteDPC");
323         }
324 
325         if (user == null) {
326             throw new NullPointerException();
327         }
328 
329         if (!user.equals(TestApis.users().instrumented())) {
330             if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.Q)) {
331                 throw new NeneException("Cannot use RemoteDPC across users prior to Q");
332             }
333         }
334 
335         ProfileOwner currentProfileOwner = TestApis.devicePolicy().getProfileOwner(user);
336 
337         if (currentProfileOwner != null) {
338             currentProfileOwner.remove();
339         }
340 
341         // TODO(274125850): Figure out the core reason these users are stopped
342         if (!user.isRunning()) {
343             user.start();
344         }
345 
346         if (!dpcTestApp.installedOnUser(user)) {
347             Log.i(LOG_TAG, "Installing RemoteDPC app: " + dpcTestApp.packageName());
348             dpcTestApp.install(user);
349         }
350 
351         ComponentName componentName =
352                 new ComponentName(dpcTestApp.packageName(), TEST_APP_CLASS_NAME);
353         RemoteDpc remoteDpc = new RemoteDpc(
354                 dpcTestApp,
355                 TestApis.devicePolicy().setProfileOwner(user, componentName));
356 
357         // DISALLOW_INSTALL_UNKNOWN_SOURCES causes verification failures in work profiles
358         remoteDpc.devicePolicyManager()
359                 .clearUserRestriction(remoteDpc.componentName(), DISALLOW_INSTALL_UNKNOWN_SOURCES);
360 
361         return remoteDpc;
362     }
363 
364     /**
365      * Create a work profile of the instrumented user with RemoteDpc as the profile owner.
366      *
367      * <p>If autoclosed, the user will be removed along with the dpc.
368      *
369      * <p>If called for Android versions prior to Q an exception will be thrown
370      */
371     @Experimental
createWorkProfile()372     public static RemoteDpc createWorkProfile() {
373         return createWorkProfile(TestApis.users().instrumented());
374     }
375 
376     /**
377      * Create a work profile of the instrumented user with RemoteDpc as the profile owner.
378      *
379      * <p>If autoclosed, the user will be removed along with the dpc.
380      *
381      * <p>If called for Android versions prior to Q an exception will be thrown
382      */
383     @Experimental
createWorkProfile(TestAppQueryBuilder dpcQuery)384     public static RemoteDpc createWorkProfile(TestAppQueryBuilder dpcQuery) {
385         return createWorkProfile(TestApis.users().instrumented(), dpcQuery);
386     }
387 
388     /**
389      * Create a work profile with RemoteDpc as the profile owner.
390      *
391      * <p>If autoclosed, the user will be removed along with the dpc.
392      *
393      * <p>If called for Android versions prior to Q an exception will be thrown
394      */
395     @Experimental
createWorkProfile(UserReference parent)396     public static RemoteDpc createWorkProfile(UserReference parent) {
397         return createWorkProfile(parent, new TestAppProvider().query().wherePackageName()
398                 .isEqualTo(REMOTE_DPC_APP_PACKAGE_NAME_OR_PREFIX));
399     }
400 
401     /**
402      * Create a work profile with RemoteDpc as the profile owner.
403      *
404      * <p>If autoclosed, the user will be removed along with the dpc.
405      *
406      * <p>If called for Android versions prior to Q an exception will be thrown
407      */
408     @Experimental
createWorkProfile(UserReference parent, TestAppQueryBuilder dpcQuery)409     public static RemoteDpc createWorkProfile(UserReference parent, TestAppQueryBuilder dpcQuery) {
410         // It'd be ideal if this method could be in TestApis.devicePolicy() but the dependency
411         // direction wouldn't allow it
412         if (parent == null) {
413             throw new NullPointerException();
414         }
415 
416         if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.Q)) {
417             throw new NeneException("Cannot use RemoteDPC across users prior to Q");
418         }
419 
420         if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S)) {
421             UserReference profile = TestApis.users().createUser()
422                 .type(TestApis.users().supportedType(MANAGED_PROFILE_TYPE_NAME))
423                 .parent(parent)
424                 .createAndStart();
425 
426             return setAsProfileOwner(profile, dpcQuery);
427         }
428 
429         boolean removeFromParent = false;
430         TestApp testApp = dpcQuery.get();
431         if (!testApp.installedOnUser(parent)) {
432             Log.i(LOG_TAG, "Installing RemoteDPC app: " + testApp.packageName());
433             testApp.install(parent);
434         }
435 
436         try (PermissionContext p =
437                      TestApis.permissions().withPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)) {
438             RemoteDpc dpc = forDevicePolicyController(TestApis.devicePolicy().getProfileOwner(
439                     sDevicePolicyManager.createAndProvisionManagedProfile(
440                             new ManagedProfileProvisioningParams.Builder(
441                                     new ComponentName(testApp.packageName(), TEST_APP_CLASS_NAME),
442                                     "RemoteDPC").build())));
443 
444             dpc.devicePolicyManager().setProfileEnabled(dpc.componentName());
445 
446             dpc.mShouldRemoveUserWhenRemoved = true;
447             return dpc;
448 
449         } catch (ProvisioningException e) {
450             throw new NeneException("Error provisioning work profile", e);
451         } finally {
452             if (removeFromParent) {
453                 testApp.uninstall(parent);
454             }
455         }
456     }
457 
458     /**
459      * Check if the RemoteDpc matches the query
460      */
matchesRemoteDpcQuery( DevicePolicyController devicePolicyController, TestAppQueryBuilder dpcQuery)461     public static boolean matchesRemoteDpcQuery(
462             DevicePolicyController devicePolicyController,
463             TestAppQueryBuilder dpcQuery) {
464         if (isRemoteDpc(devicePolicyController)) {
465             RemoteDpc remoteDpc = RemoteDpc.forDevicePolicyController(devicePolicyController);
466             return dpcQuery.matches(remoteDpc.testApp());
467         }
468         return false;
469     }
470 
471     /**
472      * Check if dpc is a RemoteDpc
473      */
isRemoteDpc(DevicePolicyController controller)474     public static boolean isRemoteDpc(DevicePolicyController controller) {
475         return controller != null
476                 && controller.componentName().getPackageName()
477                 .startsWith(REMOTE_DPC_APP_PACKAGE_NAME_OR_PREFIX)
478                 && controller.componentName().getClassName().equals(TEST_APP_CLASS_NAME);
479     }
480 
enforceRemoteDpcPackageFilter( TestAppQueryBuilder dpcQuery)481     private static TestAppQueryBuilder enforceRemoteDpcPackageFilter(
482             TestAppQueryBuilder dpcQuery) {
483         return dpcQuery.wherePackageName()
484                 .startsWith(REMOTE_DPC_APP_PACKAGE_NAME_OR_PREFIX)
485                 .allowInternalBedsteadTestApps();
486     }
487 
488     private final DevicePolicyController mDevicePolicyController;
489 
RemoteDpc(TestApp remoteDpcTestApp, DevicePolicyController devicePolicyController)490     RemoteDpc(TestApp remoteDpcTestApp, DevicePolicyController devicePolicyController) {
491         super(remoteDpcTestApp, devicePolicyController == null ? null
492                 : devicePolicyController.user());
493         mDevicePolicyController = devicePolicyController;
494     }
495 
496     /**
497      * Get the {@link DevicePolicyController} for this instance of RemoteDPC.
498      */
devicePolicyController()499     public DevicePolicyController devicePolicyController() {
500         return mDevicePolicyController;
501     }
502 
503     /**
504      * Remove RemoteDPC as Device Owner or Profile Owner and uninstall the APK from the user.
505      */
remove()506     public void remove() {
507         if (mShouldRemoveUserWhenRemoved) {
508             mDevicePolicyController.user().remove();
509         } else {
510             mDevicePolicyController.remove();
511             TestApis.packages().find(mDevicePolicyController.componentName().getPackageName())
512                     .uninstall(mDevicePolicyController.user());
513         }
514     }
515 
516     @Override
close()517     public void close() {
518         remove();
519     }
520 
521     /**
522      * Get the {@link ComponentName} of the DPC.
523      */
524     @Override
componentName()525     public ComponentName componentName() {
526         return mDevicePolicyController.componentName();
527     }
528 
529     @Override
hashCode()530     public int hashCode() {
531         return mDevicePolicyController.hashCode();
532     }
533 
534     @Override
equals(Object obj)535     public boolean equals(Object obj) {
536         if (!(obj instanceof RemoteDpc)) {
537             return false;
538         }
539 
540         RemoteDpc other = (RemoteDpc) obj;
541         return other.mDevicePolicyController.equals(mDevicePolicyController);
542     }
543 
544     @Override
toString()545     public String toString() {
546         return "RemoteDpc{"
547                 + "devicePolicyController=" + mDevicePolicyController
548                 + ", testApp=" + super.toString()
549                 + '}';
550     }
551 }
552