blob: fc7dad300b27e3330f4d5ed134d523c75451a636 [file] [log] [blame]
Rubin Xu55d22d42014-09-24 19:56:58 +01001/*
2 * Copyright (C) 2012 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
17package com.android.cts.verifier.managedprovisioning;
18
19import android.app.AlertDialog;
Rubin Xu55d22d42014-09-24 19:56:58 +010020import android.app.admin.DevicePolicyManager;
21import android.content.ActivityNotFoundException;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.DialogInterface;
25import android.content.Intent;
Rubin Xu55d22d42014-09-24 19:56:58 +010026import android.content.pm.PackageManager;
27import android.os.Bundle;
28import android.provider.Settings;
29import android.util.Log;
Alexandra Gherghina9c1c5a52014-12-09 12:55:23 +000030import android.view.LayoutInflater;
Rubin Xu55d22d42014-09-24 19:56:58 +010031import android.view.View;
32import android.view.ViewGroup;
33import android.view.View.OnClickListener;
34import android.widget.ArrayAdapter;
Alexandra Gherghina9c1c5a52014-12-09 12:55:23 +000035import android.widget.ImageView;
Rubin Xu55d22d42014-09-24 19:56:58 +010036import android.widget.ListView;
37import android.widget.TextView;
38import android.widget.Toast;
39
40import com.android.cts.verifier.PassFailButtons;
41import com.android.cts.verifier.R;
42import com.android.cts.verifier.TestListActivity;
43
44import java.util.ArrayList;
45import java.util.List;
46
47/**
48 * CTS verifier test for BYOD managed provisioning flow.
49 * This activity is responsible for starting the managed provisioning flow and verify the outcome of provisioning.
50 * It performs the following verifications:
51 * Full disk encryption is enabled.
52 * Profile owner is correctly installed.
53 * Profile owner shows up in the Settings app.
54 * Badged work apps show up in launcher.
55 * The first two verifications are performed automatically, by interacting with profile owner using
56 * cross-profile intents, while the last two are carried out manually by the user.
57 */
58public class ByodFlowTestActivity extends PassFailButtons.ListActivity {
59
60 private final String TAG = "ByodFlowTestActivity";
61 private static final int REQUEST_STATUS = 1;
62
63 private ComponentName mAdminReceiverComponent;
64
Rubin Xuc2b00d82015-02-02 11:30:26 +000065 private TestAdapter mTestListAdapter;
Rubin Xu55d22d42014-09-24 19:56:58 +010066 private View mStartProvisioningButton;
67 private List<TestItem> mTests = new ArrayList<TestItem>();
68
69 protected DevicePolicyManager mDevicePolicyManager;
70
71 private TestItem mProfileOwnerInstalled;
Rubin Xuc2b00d82015-02-02 11:30:26 +000072 private TestItem mProfileAccountVisibleTest;
Rubin Xu55d22d42014-09-24 19:56:58 +010073 private TestItem mDeviceAdminVisibleTest;
74 private TestItem mWorkAppVisibleTest;
Alexandra Gherghinaaa24ed72014-10-08 01:11:32 +010075 private TestItem mCrossProfileIntentFiltersTest;
Robin Lee020f1852015-01-15 11:56:05 +000076 private TestItem mDisableNonMarketTest;
77 private TestItem mEnableNonMarketTest;
Alexandra Gherghina2c672b72015-01-22 11:20:23 +000078 private TestItem mWorkNotificationBadgedTest;
Rubin Xuc2b00d82015-02-02 11:30:26 +000079 private TestItem mAppSettingsVisibleTest;
80 private TestItem mLocationSettingsVisibleTest;
81 private TestItem mCredSettingsVisibleTest;
82 private TestItem mPrintSettingsVisibleTest;
83
84 private int mCurrentTestPosition;
Rubin Xu55d22d42014-09-24 19:56:58 +010085
86 @Override
87 protected void onCreate(Bundle savedInstanceState) {
88 super.onCreate(savedInstanceState);
89 mAdminReceiverComponent = new ComponentName(this, DeviceAdminTestReceiver.class.getName());
90 mDevicePolicyManager = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
91 disableComponent();
92
93 setContentView(R.layout.provisioning_byod);
94 setInfoResources(R.string.provisioning_byod, R.string.provisioning_byod_info, -1);
95 setPassFailButtonClickListeners();
96 getPassButton().setEnabled(false);
97 setResult(RESULT_CANCELED);
98
99 setupTests();
100
Rubin Xuc2b00d82015-02-02 11:30:26 +0000101 mTestListAdapter = new TestAdapter(this);
102 setListAdapter(mTestListAdapter);
103 mTestListAdapter.addAll(mTests);
104
105 mCurrentTestPosition = 0;
Rubin Xu55d22d42014-09-24 19:56:58 +0100106
107 mStartProvisioningButton = findViewById(R.id.byod_start);
108 mStartProvisioningButton.setOnClickListener(new OnClickListener() {
109 @Override
110 public void onClick(View v) {
111 startByodProvisioning();
112 }
113 });
114
115 // If we are started by managed provisioning (fresh managed provisioning after encryption
116 // reboot), redirect the user back to the main test list. This is because the test result
117 // is only saved by the parent TestListActivity, and if we did allow the user to proceed
118 // here, the test result would be lost when this activity finishes.
119 if (ByodHelperActivity.ACTION_PROFILE_OWNER_STATUS.equals(getIntent().getAction())) {
120 startActivity(new Intent(this, TestListActivity.class));
121 // Calling super.finish() because we delete managed profile in our overridden of finish(),
122 // which is not what we want to do here.
123 super.finish();
124 } else {
125 queryProfileOwner(false);
126 }
127 }
128
129 @Override
130 protected void onNewIntent(Intent intent) {
131 // This is called when managed provisioning completes successfully without reboot.
132 super.onNewIntent(intent);
133 if (ByodHelperActivity.ACTION_PROFILE_OWNER_STATUS.equals(intent.getAction())) {
134 handleStatusUpdate(intent);
135 }
136 }
137
138 @Override
139 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
140 // Called after queryProfileOwner()
141 super.onActivityResult(requestCode, resultCode, data);
142 if (requestCode == REQUEST_STATUS && resultCode == RESULT_OK) {
143 handleStatusUpdate(data);
144 }
145 }
146
147 private void handleStatusUpdate(Intent data) {
148 boolean provisioned = data.getBooleanExtra(ByodHelperActivity.EXTRA_PROVISIONED, false);
149 setTestResult(mProfileOwnerInstalled, provisioned ? TestResult.Passed : TestResult.Failed);
150 }
151
152 @Override
153 public void finish() {
154 // Pass and fail buttons are known to call finish() when clicked, and this is when we want to
155 // clean up the provisioned profile.
156 requestDeleteProfileOwner();
157 super.finish();
158 }
159
160 private void setupTests() {
161 mProfileOwnerInstalled = new TestItem(this, R.string.provisioning_byod_profileowner) {
162 @Override
163 public void performTest(ByodFlowTestActivity activity) {
164 queryProfileOwner(true);
165 }
166 };
167
Alexandra Gherghina9c1c5a52014-12-09 12:55:23 +0000168 /*
169 * To keep the image in this test up to date, use the instructions in
170 * {@link ByodIconSamplerActivity}.
171 */
Alexandra Gherghina2c672b72015-01-22 11:20:23 +0000172 mWorkAppVisibleTest = new TestItemWithIcon(this,
173 R.string.provisioning_byod_workapps_visible,
Rubin Xuc2b00d82015-02-02 11:30:26 +0000174 R.string.provisioning_byod_workapps_visible_instruction,
Alexandra Gherghina2c672b72015-01-22 11:20:23 +0000175 new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME),
176 R.drawable.badged_icon);
177
178 mWorkNotificationBadgedTest = new TestItemWithIcon(this,
179 R.string.provisioning_byod_work_notification,
180 R.string.provisioning_byod_work_notification_instruction,
181 new Intent(WorkNotificationTestActivity.ACTION_WORK_NOTIFICATION),
182 R.drawable.ic_corp_icon);
Rubin Xu55d22d42014-09-24 19:56:58 +0100183
Robin Lee020f1852015-01-15 11:56:05 +0000184 mDisableNonMarketTest = new TestItem(this, R.string.provisioning_byod_nonmarket_deny,
185 R.string.provisioning_byod_nonmarket_deny_info,
186 new Intent(ByodHelperActivity.ACTION_INSTALL_APK)
187 .putExtra(ByodHelperActivity.EXTRA_ALLOW_NON_MARKET_APPS, false));
188
189 mEnableNonMarketTest = new TestItem(this, R.string.provisioning_byod_nonmarket_allow,
190 R.string.provisioning_byod_nonmarket_allow_info,
191 new Intent(ByodHelperActivity.ACTION_INSTALL_APK)
192 .putExtra(ByodHelperActivity.EXTRA_ALLOW_NON_MARKET_APPS, true));
193
Rubin Xuc2b00d82015-02-02 11:30:26 +0000194 mProfileAccountVisibleTest = new TestItem(this, R.string.provisioning_byod_profile_visible,
195 R.string.provisioning_byod_profile_visible_instruction,
196 new Intent(Settings.ACTION_SETTINGS));
197
198 mAppSettingsVisibleTest = new TestItem(this, R.string.provisioning_byod_app_settings,
199 R.string.provisioning_byod_app_settings_instruction,
200 new Intent(Settings.ACTION_APPLICATION_SETTINGS));
201
202 mDeviceAdminVisibleTest = new TestItem(this, R.string.provisioning_byod_admin_visible,
203 R.string.provisioning_byod_admin_visible_instruction,
204 new Intent(Settings.ACTION_SECURITY_SETTINGS));
205
206 mCredSettingsVisibleTest = new TestItem(this, R.string.provisioning_byod_cred_settings,
207 R.string.provisioning_byod_cred_settings_instruction,
208 new Intent(Settings.ACTION_SECURITY_SETTINGS));
209
210 mLocationSettingsVisibleTest = new TestItem(this,
211 R.string.provisioning_byod_location_settings,
212 R.string.provisioning_byod_location_settings_instruction,
213 new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
214
215 mPrintSettingsVisibleTest = new TestItem(this, R.string.provisioning_byod_print_settings,
216 R.string.provisioning_byod_print_settings_instruction,
217 new Intent(Settings.ACTION_PRINT_SETTINGS));
218
Alexandra Gherghinaaa24ed72014-10-08 01:11:32 +0100219 Intent intent = new Intent(CrossProfileTestActivity.ACTION_CROSS_PROFILE);
Rubin Xuc2b00d82015-02-02 11:30:26 +0000220 Intent chooser = Intent.createChooser(intent,
221 getResources().getString(R.string.provisioning_cross_profile_chooser));
Alexandra Gherghinaaa24ed72014-10-08 01:11:32 +0100222 mCrossProfileIntentFiltersTest = new TestItem(this,
223 R.string.provisioning_byod_cross_profile,
224 R.string.provisioning_byod_cross_profile_instruction,
225 chooser);
226
Rubin Xu55d22d42014-09-24 19:56:58 +0100227 mTests.add(mProfileOwnerInstalled);
Rubin Xuc2b00d82015-02-02 11:30:26 +0000228
229 // Badge related tests
Rubin Xu55d22d42014-09-24 19:56:58 +0100230 mTests.add(mWorkAppVisibleTest);
Alexandra Gherghina2c672b72015-01-22 11:20:23 +0000231 mTests.add(mWorkNotificationBadgedTest);
Rubin Xuc2b00d82015-02-02 11:30:26 +0000232
233 // Settings related tests.
234 mTests.add(mProfileAccountVisibleTest);
235 mTests.add(mDeviceAdminVisibleTest);
236 mTests.add(mCredSettingsVisibleTest);
237 mTests.add(mAppSettingsVisibleTest);
238 mTests.add(mLocationSettingsVisibleTest);
239 mTests.add(mPrintSettingsVisibleTest);
240
Alexandra Gherghinaaa24ed72014-10-08 01:11:32 +0100241 mTests.add(mCrossProfileIntentFiltersTest);
Robin Lee020f1852015-01-15 11:56:05 +0000242 mTests.add(mDisableNonMarketTest);
243 mTests.add(mEnableNonMarketTest);
Rubin Xu55d22d42014-09-24 19:56:58 +0100244 }
245
246 @Override
247 protected void onListItemClick(ListView l, View v, int position, long id) {
248 super.onListItemClick(l, v, position, id);
Rubin Xuc2b00d82015-02-02 11:30:26 +0000249 mCurrentTestPosition = position;
Rubin Xu55d22d42014-09-24 19:56:58 +0100250 TestItem test = (TestItem) getListAdapter().getItem(position);
251 test.performTest(this);
252 }
253
254 private void showManualTestDialog(final TestItem test) {
Alexandra Gherghina9c1c5a52014-12-09 12:55:23 +0000255 AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this)
Rubin Xu55d22d42014-09-24 19:56:58 +0100256 .setIcon(android.R.drawable.ic_dialog_info)
Alexandra Gherghina9c1c5a52014-12-09 12:55:23 +0000257 .setTitle(R.string.provisioning_byod)
Rubin Xu55d22d42014-09-24 19:56:58 +0100258 .setNeutralButton(R.string.provisioning_byod_go, null)
Alexandra Gherghina9c1c5a52014-12-09 12:55:23 +0000259 .setPositiveButton(R.string.pass_button_text, new AlertDialog.OnClickListener() {
Rubin Xu55d22d42014-09-24 19:56:58 +0100260 @Override
261 public void onClick(DialogInterface dialog, int which) {
Alexandra Gherghina2c672b72015-01-22 11:20:23 +0000262 clearRemainingState(test);
Rubin Xu55d22d42014-09-24 19:56:58 +0100263 setTestResult(test, TestResult.Passed);
264 }
265 })
Alexandra Gherghina9c1c5a52014-12-09 12:55:23 +0000266 .setNegativeButton(R.string.fail_button_text, new AlertDialog.OnClickListener() {
Rubin Xu55d22d42014-09-24 19:56:58 +0100267 @Override
268 public void onClick(DialogInterface dialog, int which) {
Alexandra Gherghina2c672b72015-01-22 11:20:23 +0000269 clearRemainingState(test);
Rubin Xu55d22d42014-09-24 19:56:58 +0100270 setTestResult(test, TestResult.Failed);
271 }
Alexandra Gherghina9c1c5a52014-12-09 12:55:23 +0000272 });
273 View customView = test.getCustomView();
274 if (customView != null) {
275 dialogBuilder.setView(customView);
276 } else {
277 dialogBuilder.setMessage(test.getManualTestInstruction());
278 }
Rubin Xuc2b00d82015-02-02 11:30:26 +0000279 final AlertDialog dialog = dialogBuilder.show();
Alexandra Gherghina9c1c5a52014-12-09 12:55:23 +0000280 // Note: Setting the OnClickListener on the Dialog rather than the Builder, prevents the
281 // dialog being dismissed on onClick.
Rubin Xu55d22d42014-09-24 19:56:58 +0100282 dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(new OnClickListener() {
283 @Override
284 public void onClick(View v) {
Rubin Xuc2b00d82015-02-02 11:30:26 +0000285 try {
286 ByodFlowTestActivity.this.startActivity(test.getManualTestIntent());
287 } catch (ActivityNotFoundException e) {
288 Toast.makeText(ByodFlowTestActivity.this,
289 "Cannot start " + test.getManualTestIntent(), Toast.LENGTH_LONG).show();
290 setTestResult(test, TestResult.Failed);
291 dialog.dismiss();
292 }
Rubin Xu55d22d42014-09-24 19:56:58 +0100293 }
294 });
295 }
296
Alexandra Gherghina2c672b72015-01-22 11:20:23 +0000297 private void clearRemainingState(final TestItem test) {
298 if (WorkNotificationTestActivity.ACTION_WORK_NOTIFICATION.equals(
299 test.getManualTestIntent().getAction())) {
Rubin Xuc2b00d82015-02-02 11:30:26 +0000300 try {
301 ByodFlowTestActivity.this.startActivity(new Intent(
302 WorkNotificationTestActivity.ACTION_CLEAR_WORK_NOTIFICATION));
303 } catch (ActivityNotFoundException e) {
304 // User shouldn't run this test before work profile is set up.
305 }
Alexandra Gherghina2c672b72015-01-22 11:20:23 +0000306 }
307 }
308
Rubin Xu55d22d42014-09-24 19:56:58 +0100309 private void setTestResult(TestItem test, TestResult result) {
310 test.setPassFailState(result);
311
312 boolean testSucceeds = true;
313 for(TestItem aTest : mTests) {
314 testSucceeds &= (aTest.getPassFailState() == TestResult.Passed);
315 }
316 getPassButton().setEnabled(testSucceeds);
Rubin Xuc2b00d82015-02-02 11:30:26 +0000317 mTestListAdapter.notifyDataSetChanged();
318
319 this.getListView().smoothScrollToPosition(mCurrentTestPosition + 1);
Rubin Xu55d22d42014-09-24 19:56:58 +0100320 }
321
322 private void startByodProvisioning() {
323 Intent sending = new Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE);
Sudheer Shankab64e0dc2015-03-23 16:42:35 +0000324 sending.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
325 mAdminReceiverComponent);
Rubin Xu55d22d42014-09-24 19:56:58 +0100326
327 if (sending.resolveActivity(getPackageManager()) != null) {
328 // ManagedProvisioning must be started with startActivityForResult, but we don't
329 // care about the result, so passing 0 as a requestCode
330 startActivityForResult(sending, 0);
331 } else {
332 showToast(R.string.provisioning_byod_disabled);
333 }
334 }
335
336 private void queryProfileOwner(boolean showToast) {
337 try {
338 Intent intent = new Intent(ByodHelperActivity.ACTION_QUERY_PROFILE_OWNER);
339 startActivityForResult(intent, REQUEST_STATUS);
340 }
341 catch (ActivityNotFoundException e) {
342 Log.d(TAG, "queryProfileOwner: ActivityNotFoundException", e);
343 setTestResult(mProfileOwnerInstalled, TestResult.Failed);
344 if (showToast) {
345 showToast(R.string.provisioning_byod_no_activity);
346 }
347 }
348 }
349
350 private void requestDeleteProfileOwner() {
351 try {
352 Intent intent = new Intent(ByodHelperActivity.ACTION_REMOVE_PROFILE_OWNER);
353 startActivity(intent);
354 showToast(R.string.provisioning_byod_delete_profile);
355 }
356 catch (ActivityNotFoundException e) {
357 Log.d(TAG, "requestDeleteProfileOwner: ActivityNotFoundException", e);
358 }
359 }
360
361 private void disableComponent() {
362 // Disable app components in the current profile, so only the counterpart in the other profile
363 // can respond (via cross-profile intent filter)
364 getPackageManager().setComponentEnabledSetting(new ComponentName(
365 this, ByodHelperActivity.class),
366 PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
367 PackageManager.DONT_KILL_APP);
Alexandra Gherghina2c672b72015-01-22 11:20:23 +0000368 getPackageManager().setComponentEnabledSetting(new ComponentName(
369 this, WorkNotificationTestActivity.class),
370 PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
371 PackageManager.DONT_KILL_APP);
Rubin Xu55d22d42014-09-24 19:56:58 +0100372 }
373
Rubin Xu55d22d42014-09-24 19:56:58 +0100374 private void showToast(int messageId) {
375 String message = getString(messageId);
376 Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
377 }
378
379 enum TestResult {
380 Unknown, Failed, Passed
381 }
382
383 static class TestItem {
384
385 private String mDisplayName;
386 private TestResult mPassed;
387 private boolean mManualTest;
388 private String mManualInstruction;
389 private Intent mManualIntent;
390
391 public TestItem(Context context, int nameResId) {
392 mDisplayName = context.getString(nameResId);
393 mPassed = TestResult.Unknown;
394 mManualTest = false;
395 }
396
397 public void performTest(ByodFlowTestActivity activity) {
398 if (isManualTest()) {
399 activity.showManualTestDialog(this);
400 }
401 }
402
403 public TestItem(Context context, int nameResId, int testInstructionResId, Intent testIntent) {
404 mDisplayName = context.getString(nameResId);
405 mPassed = TestResult.Unknown;
406 mManualTest = true;
407 mManualInstruction = context.getString(testInstructionResId);
408 mManualIntent = testIntent;
409 }
410
411 @Override
412 public String toString() {
413 return mDisplayName;
414 }
415
416 TestResult getPassFailState() {
417 return mPassed;
418 }
419
420 void setPassFailState(TestResult state) {
421 mPassed = state;
422 }
423
424 public boolean isManualTest() {
425 return mManualTest;
426 }
427
428 public String getManualTestInstruction() {
429 return mManualInstruction;
430 }
431
432 public Intent getManualTestIntent() {
433 return mManualIntent;
434 }
Alexandra Gherghina9c1c5a52014-12-09 12:55:23 +0000435
436 public View getCustomView() {
437 return null;
438 }
Rubin Xu55d22d42014-09-24 19:56:58 +0100439 }
440
Alexandra Gherghina2c672b72015-01-22 11:20:23 +0000441 static class TestItemWithIcon extends TestItem {
442
443 private int mImageResId;
444 private Context mContext;
445
446 public TestItemWithIcon(Context context, int nameResId, int testInstructionResId,
447 Intent testIntent, int imageResId) {
448 super(context, nameResId, testInstructionResId, testIntent);
449 mContext = context;
450 mImageResId = imageResId;
451 }
452
453 @Override
454 public View getCustomView() {
455 LayoutInflater layoutInflater = LayoutInflater.from(mContext);
456 View view = layoutInflater.inflate(R.layout.byod_custom_view,
457 null /* root */);
458 ((ImageView) view.findViewById(R.id.sample_icon)).setImageResource(mImageResId);
459 ((TextView) view.findViewById(R.id.message)).setText(getManualTestInstruction());
460 return view;
461 }
462 }
463
Rubin Xu55d22d42014-09-24 19:56:58 +0100464 static class TestAdapter extends ArrayAdapter<TestItem> {
465
466 public TestAdapter(Context context) {
467 super(context, android.R.layout.simple_list_item_1);
468 }
469
470 @Override
471 public View getView(int position, View convertView, ViewGroup parent) {
472 TextView view = (TextView) super.getView(position, convertView, parent);
473
474 TestItem item = getItem(position);
475 int backgroundResource = 0;
476 int iconResource = 0;
477 if (item.getPassFailState() == TestResult.Passed) {
478 backgroundResource = R.drawable.test_pass_gradient;
479 iconResource = R.drawable.fs_good;
480 } else if (item.getPassFailState() == TestResult.Failed){
481 backgroundResource = R.drawable.test_fail_gradient;
482 iconResource = R.drawable.fs_error;
483 }
484 view.setBackgroundResource(backgroundResource);
485 view.setPadding(10, 0, 10, 0);
486 view.setCompoundDrawablePadding(10);
487 view.setCompoundDrawablesWithIntrinsicBounds(0, 0, iconResource, 0);
488
489 return view;
490 }
491 }
Rubin Xu55d22d42014-09-24 19:56:58 +0100492}