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