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