1 /*
2  * Copyright (C) 2019 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.managedprovisioning.provisioning;
18 
19 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE;
20 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
21 
22 import static com.android.managedprovisioning.provisioning.AbstractProvisioningActivity.CANCEL_PROVISIONING_DIALOG_OK;
23 import static com.android.managedprovisioning.provisioning.AbstractProvisioningActivity.ERROR_DIALOG_OK;
24 import static com.android.managedprovisioning.provisioning.AbstractProvisioningActivity.ERROR_DIALOG_RESET;
25 import static com.android.managedprovisioning.provisioning.ProvisioningActivity.RESULT_CODE_DEVICE_OWNER_SET;
26 import static com.android.managedprovisioning.provisioning.ProvisioningActivity.RESULT_CODE_WORK_PROFILE_CREATED;
27 
28 import static com.google.common.truth.Truth.assertThat;
29 
30 import static org.mockito.Mockito.never;
31 import static org.mockito.Mockito.verify;
32 import static org.robolectric.Shadows.shadowOf;
33 import static org.robolectric.shadows.ShadowUserManager.FLAG_MANAGED_PROFILE;
34 
35 import android.app.Activity;
36 import android.app.Application;
37 import android.app.DialogFragment;
38 import android.app.Fragment;
39 import android.app.admin.DevicePolicyManager;
40 import android.content.ComponentName;
41 import android.content.Context;
42 import android.content.Intent;
43 import android.graphics.Bitmap;
44 import android.graphics.Color;
45 import android.graphics.PorterDuffColorFilter;
46 import android.graphics.drawable.BitmapDrawable;
47 import android.graphics.drawable.Drawable;
48 import android.graphics.drawable.VectorDrawable;
49 import android.net.Uri;
50 import android.os.UserManager;
51 import android.widget.ImageView;
52 
53 import androidx.test.core.content.pm.ApplicationInfoBuilder;
54 import androidx.test.core.content.pm.PackageInfoBuilder;
55 
56 import com.android.managedprovisioning.R;
57 import com.android.managedprovisioning.model.ProvisioningParams;
58 
59 import org.junit.Ignore;
60 import org.junit.Test;
61 import org.junit.runner.RunWith;
62 import org.mockito.Mockito;
63 import org.robolectric.Robolectric;
64 import org.robolectric.RobolectricTestRunner;
65 import org.robolectric.RuntimeEnvironment;
66 
67 import java.io.ByteArrayInputStream;
68 import java.io.ByteArrayOutputStream;
69 import java.io.InputStream;
70 import java.util.List;
71 
72 /**
73  * Robolectric tests for {@link ProvisioningActivity}.
74  */
75 @RunWith(RobolectricTestRunner.class)
76 public class ProvisioningActivityRoboTest {
77 
78     private static final String ADMIN_PACKAGE = "com.test.admin";
79     private static final ComponentName ADMIN = new ComponentName(ADMIN_PACKAGE, ".Receiver");
80     private static final ProvisioningParams DEVICE_OWNER_PARAMS = new ProvisioningParams.Builder()
81             .setProvisioningAction(ACTION_PROVISION_MANAGED_DEVICE)
82             .setDeviceAdminComponentName(ADMIN)
83             .build();
84     private static final ProvisioningParams PROFILE_OWNER_PARAMS = new ProvisioningParams.Builder()
85             .setProvisioningAction(ACTION_PROVISION_MANAGED_PROFILE)
86             .setDeviceAdminComponentName(ADMIN)
87             .build();
88     private static final ProvisioningParams QR_PROVISIONING_PARAMS_DO = new ProvisioningParams.Builder()
89             .setProvisioningAction(ACTION_PROVISION_MANAGED_DEVICE)
90             .setDeviceAdminComponentName(ADMIN)
91             .setIsOrganizationOwnedProvisioning(true)
92             .setFlowType(ProvisioningParams.FLOW_TYPE_ADMIN_INTEGRATED)
93             .build();
94     private static final ProvisioningParams QR_PROVISIONING_PARAMS_PO = new ProvisioningParams.Builder()
95             .setProvisioningAction(ACTION_PROVISION_MANAGED_PROFILE)
96             .setDeviceAdminComponentName(ADMIN)
97             .setIsOrganizationOwnedProvisioning(true)
98             .setFlowType(ProvisioningParams.FLOW_TYPE_ADMIN_INTEGRATED)
99             .build();
100     private static final Intent PROFILE_OWNER_INTENT = new Intent()
101             .putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, PROFILE_OWNER_PARAMS);
102     private static final Intent DEVICE_OWNER_INTENT = new Intent()
103             .putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, DEVICE_OWNER_PARAMS);
104     private static final Intent ADMIN_INTEGRATED_FLOW_INTENT_PO = new Intent()
105             .putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, QR_PROVISIONING_PARAMS_PO);
106     private static final Intent ADMIN_INTEGRATED_FLOW_INTENT_DO = new Intent()
107             .putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, QR_PROVISIONING_PARAMS_DO);
108     private static final int ERROR_MESSAGE_ID = R.string.managed_provisioning_error_text;
109     private static final int DEFAULT_LOGO_COLOR = -15043608;
110     private static final int CUSTOM_COLOR = Color.parseColor("#d40000");
111     private static final Uri LOGO_URI = Uri.parse("http://logo");
112 
113     private ProvisioningManager mMockProvisioningManager = Mockito.mock(ProvisioningManager.class);
114     private Application mContext = RuntimeEnvironment.application;
115 
116     @Test
error_noFactoryReset_showsDialogue()117     public void error_noFactoryReset_showsDialogue() {
118         final ProvisioningActivity activity =
119                 Robolectric.buildActivity(ProvisioningActivity.class, PROFILE_OWNER_INTENT).get();
120 
121         activity.error(R.string.cant_set_up_device, ERROR_MESSAGE_ID, /* resetRequired= */ false);
122 
123         final Fragment dialog = activity.getFragmentManager().findFragmentByTag(ERROR_DIALOG_OK);
124         assertThat(dialog).isNotNull();
125     }
126 
127     @Test
error_noFactoryReset_doesNotReset()128     public void error_noFactoryReset_doesNotReset() throws Exception {
129         final ProvisioningActivity activity =
130                 Robolectric.buildActivity(ProvisioningActivity.class, PROFILE_OWNER_INTENT).get();
131         activity.error(R.string.cant_set_up_device, ERROR_MESSAGE_ID, /* resetRequired= */ false);
132 
133         final Fragment dialog = activity.getFragmentManager().findFragmentByTag(ERROR_DIALOG_OK);
134         clickOnPositiveButton(activity, (DialogFragment) dialog);
135 
136         final List<Intent> intents = shadowOf(mContext).getBroadcastIntents();
137         assertThat(intentsContainsAction(intents, Intent.ACTION_FACTORY_RESET)).isFalse();
138     }
139 
140     @Test
error_factoryReset_showsDialogue()141     public void error_factoryReset_showsDialogue() {
142         final ProvisioningActivity activity =
143                 Robolectric.buildActivity(ProvisioningActivity.class, PROFILE_OWNER_INTENT).get();
144 
145         activity.error(R.string.cant_set_up_device, ERROR_MESSAGE_ID, /* resetRequired= */ true);
146 
147         final Fragment dialog = activity.getFragmentManager().findFragmentByTag(ERROR_DIALOG_RESET);
148         assertThat(dialog).isNotNull();
149     }
150 
151     @Test
error_factoryReset_resets()152     public void error_factoryReset_resets() throws Exception {
153         DevicePolicyManager devicePolicyManager =
154                 mContext.getSystemService(DevicePolicyManager.class);
155         final ProvisioningActivity activity =
156                 Robolectric.buildActivity(ProvisioningActivity.class, PROFILE_OWNER_INTENT).get();
157         activity.error(R.string.cant_set_up_device, ERROR_MESSAGE_ID, /* resetRequired= */ true);
158 
159         final Fragment dialog = activity.getFragmentManager().findFragmentByTag(ERROR_DIALOG_RESET);
160         clickOnPositiveButton(activity, (DialogFragment) dialog);
161 
162         assertThat(shadowOf(devicePolicyManager).getWipeCalledTimes()).isEqualTo(1);
163     }
164 
165     @Ignore("b/181326453")
166     @Test
profileOwnerIntent_usesDefaultLogo()167     public void profileOwnerIntent_usesDefaultLogo() throws Throwable {
168         final ProvisioningActivity activity =
169                 Robolectric.buildActivity(ProvisioningActivity.class, PROFILE_OWNER_INTENT)
170                         .setup().get();
171 
172         assertUsesDefaultLogo(activity);
173     }
174 
175     @Ignore("b/181326453")
176     @Test
profileOwnerIntent_setCustomLogo_usesCustomLogo()177     public void profileOwnerIntent_setCustomLogo_usesCustomLogo() throws Throwable {
178         setupCustomLogo(mContext, LOGO_URI);
179 
180         final ProvisioningActivity activity =
181                 Robolectric.buildActivity(ProvisioningActivity.class, PROFILE_OWNER_INTENT)
182                         .setup().get();
183 
184         assertUsesCustomLogo(activity);
185     }
186 
187     @Ignore("b/181326453")
188     @Test
deviceOwnerIntent_usesDefaultLogo()189     public void deviceOwnerIntent_usesDefaultLogo() throws Throwable {
190         final ProvisioningActivity activity =
191                 Robolectric.buildActivity(ProvisioningActivity.class, DEVICE_OWNER_INTENT)
192                         .setup().get();
193 
194         assertUsesDefaultLogo(activity);
195     }
196 
197     @Ignore("b/181326453")
198     @Test
deviceOwnerIntent_setCustomLogo_usesCustomLogo()199     public void deviceOwnerIntent_setCustomLogo_usesCustomLogo() throws Throwable {
200         setupCustomLogo(mContext, LOGO_URI);
201 
202         final ProvisioningActivity activity =
203                 Robolectric.buildActivity(ProvisioningActivity.class, DEVICE_OWNER_INTENT)
204                         .setup().get();
205 
206         assertUsesCustomLogo(activity);
207     }
208 
209     @Ignore("b/181326453")
210     @Test
managedProfileIntent_defaultColor_colorCorrect()211     public void managedProfileIntent_defaultColor_colorCorrect() {
212         assertColorsCorrect(
213                 PROFILE_OWNER_INTENT,
214                 DEFAULT_LOGO_COLOR);
215     }
216 
217     @Ignore("b/181326453")
218     @Test
deviceOwnerIntent_defaultColor_colorCorrect()219     public void deviceOwnerIntent_defaultColor_colorCorrect() {
220         assertColorsCorrect(
221                 DEVICE_OWNER_INTENT,
222                 DEFAULT_LOGO_COLOR);
223     }
224 
225     @Ignore("b/181326453")
226     @Test
activity_profileOwner_backPressed_showsCancelDialog()227     public void activity_profileOwner_backPressed_showsCancelDialog() throws Throwable {
228         final ProvisioningActivity activity =
229                 Robolectric.buildActivity(ProvisioningActivity.class, PROFILE_OWNER_INTENT)
230                         .setup().get();
231 
232         activity.onBackPressed();
233 
234         final Fragment dialog =
235                 activity.getFragmentManager().findFragmentByTag(CANCEL_PROVISIONING_DIALOG_OK);
236         assertThat(dialog).isNotNull();
237     }
238 
239     @Ignore("b/181326453")
240     @Test
activity_profileOwner_backPressed_doNotCancel_doesNotFinishActivity()241     public void activity_profileOwner_backPressed_doNotCancel_doesNotFinishActivity() {
242         final ProvisioningActivity activity =
243                 Robolectric.buildActivity(ProvisioningActivity.class, PROFILE_OWNER_INTENT)
244                         .setup().get();
245 
246         activity.onBackPressed();
247         final Fragment dialog =
248                 activity.getFragmentManager().findFragmentByTag(CANCEL_PROVISIONING_DIALOG_OK);
249         clickOnNegativeButton(activity, (DialogFragment) dialog);
250 
251         assertThat(activity.isFinishing()).isFalse();
252     }
253 
254     @Ignore("b/181326453")
255     @Test
activity_profileOwner_backPressed_doNotCancel_doesNotCancelProvisioning()256     public void activity_profileOwner_backPressed_doNotCancel_doesNotCancelProvisioning() {
257         final ProvisioningActivity activity =
258                 Robolectric.buildActivity(ProvisioningActivity.class, PROFILE_OWNER_INTENT)
259                         .setup().get();
260         activity.setProvisioningManager(mMockProvisioningManager);
261 
262         activity.onBackPressed();
263         final Fragment dialog =
264                 activity.getFragmentManager().findFragmentByTag(CANCEL_PROVISIONING_DIALOG_OK);
265         clickOnNegativeButton(activity, (DialogFragment) dialog);
266 
267         verify(mMockProvisioningManager, never()).cancelProvisioning();
268     }
269 
270     @Ignore("b/181326453")
271     @Test
activity_profileOwner_backPressed_cancel_doesFinishActivity()272     public void activity_profileOwner_backPressed_cancel_doesFinishActivity() {
273         final ProvisioningActivity activity =
274                 Robolectric.buildActivity(ProvisioningActivity.class, PROFILE_OWNER_INTENT)
275                         .setup().get();
276 
277         activity.onBackPressed();
278         final Fragment dialog =
279                 activity.getFragmentManager().findFragmentByTag(CANCEL_PROVISIONING_DIALOG_OK);
280         clickOnPositiveButton(activity, (DialogFragment) dialog);
281 
282         assertThat(activity.isFinishing()).isTrue();
283     }
284 
285     @Ignore("b/181326453")
286     @Test
activity_profileOwner_backPressed_cancel_doesCancelProvisioning()287     public void activity_profileOwner_backPressed_cancel_doesCancelProvisioning() {
288         final ProvisioningActivity activity =
289                 Robolectric.buildActivity(ProvisioningActivity.class, PROFILE_OWNER_INTENT)
290                         .setup().get();
291         activity.setProvisioningManager(mMockProvisioningManager);
292 
293         activity.onBackPressed();
294         final Fragment dialog =
295                 activity.getFragmentManager().findFragmentByTag(CANCEL_PROVISIONING_DIALOG_OK);
296         clickOnPositiveButton(activity, (DialogFragment) dialog);
297 
298         verify(mMockProvisioningManager).cancelProvisioning();
299     }
300 
301     @Ignore("b/181326453")
302     @Test
activity_profileOwner_adminIntegrated_returnsIntermediateResult()303     public void activity_profileOwner_adminIntegrated_returnsIntermediateResult() {
304         final ProvisioningActivity activity =
305                 Robolectric.buildActivity(ProvisioningActivity.class,
306                         ADMIN_INTEGRATED_FLOW_INTENT_PO)
307                         .setup().get();
308         shadowOf(activity.getPackageManager())
309                 .installPackage(
310                         PackageInfoBuilder.newBuilder()
311                                 .setPackageName(ADMIN_PACKAGE)
312                                 .setApplicationInfo(ApplicationInfoBuilder.newBuilder()
313                                         .setPackageName(ADMIN_PACKAGE)
314                                         .build())
315                                 .build());
316 
317 
318         activity.preFinalizationCompleted();
319         activity.onAllTransitionsShown();
320 
321         UserManager userManager = activity.getSystemService(UserManager.class);
322         shadowOf(userManager).addProfile(0, 10, "profile name", FLAG_MANAGED_PROFILE);
323 
324         activity.onNextButtonClicked();
325 
326         assertThat(activity.isFinishing()).isTrue();
327         assertThat(shadowOf(activity).getResultCode()).isEqualTo(RESULT_CODE_WORK_PROFILE_CREATED);
328     }
329 
330     @Ignore("b/181326453")
331     @Test
activity_deviceOwner_adminIntegrated_returnsIntermediateResult()332     public void activity_deviceOwner_adminIntegrated_returnsIntermediateResult() {
333         final ProvisioningActivity activity =
334                 Robolectric.buildActivity(ProvisioningActivity.class,
335                         ADMIN_INTEGRATED_FLOW_INTENT_DO)
336                         .setup().get();
337         shadowOf(activity.getPackageManager())
338                 .installPackage(
339                         PackageInfoBuilder.newBuilder()
340                                 .setPackageName(ADMIN_PACKAGE)
341                                 .setApplicationInfo(ApplicationInfoBuilder.newBuilder()
342                                         .setPackageName(ADMIN_PACKAGE)
343                                         .build())
344                                 .build());
345 
346 
347         activity.preFinalizationCompleted();
348         activity.onAllTransitionsShown();
349 
350         activity.onNextButtonClicked();
351 
352         assertThat(activity.isFinishing()).isTrue();
353         assertThat(shadowOf(activity).getResultCode()).isEqualTo(RESULT_CODE_DEVICE_OWNER_SET);
354     }
355 
356     @Ignore("b/181326453")
357     @Test
activity_deviceOwner_notAdminIntegrated_returnsOk()358     public void activity_deviceOwner_notAdminIntegrated_returnsOk() {
359         final ProvisioningActivity activity =
360                 Robolectric.buildActivity(ProvisioningActivity.class,
361                         DEVICE_OWNER_INTENT)
362                         .setup().get();
363         shadowOf(activity.getPackageManager())
364                 .installPackage(
365                         PackageInfoBuilder.newBuilder()
366                                 .setPackageName(ADMIN_PACKAGE)
367                                 .setApplicationInfo(ApplicationInfoBuilder.newBuilder()
368                                         .setPackageName(ADMIN_PACKAGE)
369                                         .build())
370                                 .build());
371 
372 
373         activity.preFinalizationCompleted();
374         activity.onAllTransitionsShown();
375 
376         activity.onNextButtonClicked();
377 
378         assertThat(activity.isFinishing()).isTrue();
379         assertThat(shadowOf(activity).getResultCode()).isEqualTo(Activity.RESULT_OK);
380     }
381 
createProvisioningIntent(String action)382     private Intent createProvisioningIntent(String action) {
383         final ProvisioningParams provisioningParams = new ProvisioningParams.Builder()
384                 .setProvisioningAction(action)
385                 .setDeviceAdminComponentName(ADMIN)
386                 .build();
387 
388         final Intent intent = new Intent();
389         intent.putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, provisioningParams);
390         return intent;
391     }
392 
assertColorsCorrect(Intent intent, int logoColor)393     private void assertColorsCorrect(Intent intent, int logoColor) {
394         final ProvisioningActivity activity =
395                 Robolectric.buildActivity(ProvisioningActivity.class, intent)
396                         .setup().get();
397 
398         assertDefaultLogoColorCorrect(activity, logoColor);
399     }
400 
assertDefaultLogoColorCorrect(Activity activity, int targetColor)401     private void assertDefaultLogoColorCorrect(Activity activity, int targetColor) {
402         Drawable actualLogo =
403                 ((ImageView) activity.findViewById(R.id.sud_layout_icon)).getDrawable();
404         PorterDuffColorFilter colorFilter = (PorterDuffColorFilter) actualLogo.getColorFilter();
405 
406         assertThat(colorFilter.getColor()).isEqualTo(targetColor);
407     }
408 
intentsContainsAction(List<Intent> intents, String action)409     private static boolean intentsContainsAction(List<Intent> intents, String action) {
410         return intents.stream().anyMatch(intent -> intent.getAction().equals(action));
411     }
412 
clickOnOkButton(ProvisioningActivity activity, DialogFragment dialog)413     private void clickOnOkButton(ProvisioningActivity activity, DialogFragment dialog) {
414         // TODO(135181317): This should be replaced by
415         //  activity.findViewById(android.R.id.button1).performClick();
416         activity.onPositiveButtonClick(dialog);
417     }
418 
setupCustomLogo(Context context, Uri logoUri)419     private static void setupCustomLogo(Context context, Uri logoUri) {
420         Bitmap bitmap = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
421         InputStream inputStream = bitmapToInputStream(bitmap);
422         shadowOf(context.getContentResolver()).registerInputStream(logoUri, inputStream);
423     }
424 
bitmapToInputStream(Bitmap bitmap)425     private static InputStream bitmapToInputStream(Bitmap bitmap) {
426         ByteArrayOutputStream bos = new ByteArrayOutputStream();
427         bitmap.compress(Bitmap.CompressFormat.PNG, 0 /* ignored for PNG */, bos);
428         byte[] bitmapdata = bos.toByteArray();
429         return new ByteArrayInputStream(bitmapdata);
430     }
431 
assertUsesDefaultLogo(Activity activity)432     private static void assertUsesDefaultLogo(Activity activity) {
433         final ImageView imageView = activity.findViewById(R.id.sud_layout_icon);
434         // We default to a vector logo
435         assertThat(imageView.getDrawable()).isInstanceOf(VectorDrawable.class);
436     }
437 
assertUsesCustomLogo(Activity activity)438     private static void assertUsesCustomLogo(Activity activity) {
439         final ImageView imageView = activity.findViewById(R.id.sud_layout_icon);
440         // The custom logo we have set is a bitmap
441         assertThat(imageView.getDrawable()).isInstanceOf(BitmapDrawable.class);
442     }
443 
clickOnPositiveButton(ProvisioningActivity activity, DialogFragment dialog)444     private void clickOnPositiveButton(ProvisioningActivity activity, DialogFragment dialog) {
445         // TODO(135181317): This should be replaced by
446         //  activity.findViewById(android.R.id.button1).performClick();
447 
448         activity.onPositiveButtonClick(dialog);
449     }
450 
clickOnNegativeButton(ProvisioningActivity activity, DialogFragment dialog)451     private void clickOnNegativeButton(ProvisioningActivity activity, DialogFragment dialog) {
452         // TODO(135181317): This should be replaced by
453         //  activity.findViewById(android.R.id.button2).performClick();
454         activity.onNegativeButtonClick(dialog);
455     }
456 }
457