blob: 12aa37bcb4c742f201d3af706d84a88da1e79d22 [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;
20import android.app.Dialog;
21import android.app.admin.DevicePolicyManager;
22import android.content.ActivityNotFoundException;
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.DialogInterface;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.content.pm.PackageManager;
29import android.os.Bundle;
30import android.provider.Settings;
31import android.util.Log;
Alexandra Gherghina9c1c5a52014-12-09 12:55:23 +000032import android.view.LayoutInflater;
Rubin Xu55d22d42014-09-24 19:56:58 +010033import android.view.View;
34import android.view.ViewGroup;
35import android.view.View.OnClickListener;
36import android.widget.ArrayAdapter;
Alexandra Gherghina9c1c5a52014-12-09 12:55:23 +000037import android.widget.ImageView;
Rubin Xu55d22d42014-09-24 19:56:58 +010038import android.widget.ListView;
39import android.widget.TextView;
40import android.widget.Toast;
41
42import com.android.cts.verifier.PassFailButtons;
43import com.android.cts.verifier.R;
44import com.android.cts.verifier.TestListActivity;
45
46import java.util.ArrayList;
47import java.util.List;
48
49/**
50 * CTS verifier test for BYOD managed provisioning flow.
51 * This activity is responsible for starting the managed provisioning flow and verify the outcome of provisioning.
52 * It performs the following verifications:
53 * Full disk encryption is enabled.
54 * Profile owner is correctly installed.
55 * Profile owner shows up in the Settings app.
56 * Badged work apps show up in launcher.
57 * The first two verifications are performed automatically, by interacting with profile owner using
58 * cross-profile intents, while the last two are carried out manually by the user.
59 */
60public class ByodFlowTestActivity extends PassFailButtons.ListActivity {
61
62 private final String TAG = "ByodFlowTestActivity";
63 private static final int REQUEST_STATUS = 1;
64
65 private ComponentName mAdminReceiverComponent;
66
67 private TestAdapter mAdapter;
68 private View mStartProvisioningButton;
69 private List<TestItem> mTests = new ArrayList<TestItem>();
70
71 protected DevicePolicyManager mDevicePolicyManager;
72
73 private TestItem mProfileOwnerInstalled;
Rubin Xu55d22d42014-09-24 19:56:58 +010074 private TestItem mProfileVisibleTest;
75 private TestItem mDeviceAdminVisibleTest;
76 private TestItem mWorkAppVisibleTest;
Alexandra Gherghinaaa24ed72014-10-08 01:11:32 +010077 private TestItem mCrossProfileIntentFiltersTest;
Robin Lee020f1852015-01-15 11:56:05 +000078 private TestItem mDisableNonMarketTest;
79 private TestItem mEnableNonMarketTest;
Rubin Xu55d22d42014-09-24 19:56:58 +010080
81 @Override
82 protected void onCreate(Bundle savedInstanceState) {
83 super.onCreate(savedInstanceState);
84 mAdminReceiverComponent = new ComponentName(this, DeviceAdminTestReceiver.class.getName());
85 mDevicePolicyManager = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
86 disableComponent();
87
88 setContentView(R.layout.provisioning_byod);
89 setInfoResources(R.string.provisioning_byod, R.string.provisioning_byod_info, -1);
90 setPassFailButtonClickListeners();
91 getPassButton().setEnabled(false);
92 setResult(RESULT_CANCELED);
93
94 setupTests();
95
96 mAdapter = new TestAdapter(this);
97 setListAdapter(mAdapter);
98 mAdapter.addAll(mTests);
99
100 mStartProvisioningButton = findViewById(R.id.byod_start);
101 mStartProvisioningButton.setOnClickListener(new OnClickListener() {
102 @Override
103 public void onClick(View v) {
104 startByodProvisioning();
105 }
106 });
107
108 // If we are started by managed provisioning (fresh managed provisioning after encryption
109 // reboot), redirect the user back to the main test list. This is because the test result
110 // is only saved by the parent TestListActivity, and if we did allow the user to proceed
111 // here, the test result would be lost when this activity finishes.
112 if (ByodHelperActivity.ACTION_PROFILE_OWNER_STATUS.equals(getIntent().getAction())) {
113 startActivity(new Intent(this, TestListActivity.class));
114 // Calling super.finish() because we delete managed profile in our overridden of finish(),
115 // which is not what we want to do here.
116 super.finish();
117 } else {
118 queryProfileOwner(false);
119 }
120 }
121
122 @Override
123 protected void onNewIntent(Intent intent) {
124 // This is called when managed provisioning completes successfully without reboot.
125 super.onNewIntent(intent);
126 if (ByodHelperActivity.ACTION_PROFILE_OWNER_STATUS.equals(intent.getAction())) {
127 handleStatusUpdate(intent);
128 }
129 }
130
131 @Override
132 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
133 // Called after queryProfileOwner()
134 super.onActivityResult(requestCode, resultCode, data);
135 if (requestCode == REQUEST_STATUS && resultCode == RESULT_OK) {
136 handleStatusUpdate(data);
137 }
138 }
139
140 private void handleStatusUpdate(Intent data) {
141 boolean provisioned = data.getBooleanExtra(ByodHelperActivity.EXTRA_PROVISIONED, false);
142 setTestResult(mProfileOwnerInstalled, provisioned ? TestResult.Passed : TestResult.Failed);
143 }
144
145 @Override
146 public void finish() {
147 // Pass and fail buttons are known to call finish() when clicked, and this is when we want to
148 // clean up the provisioned profile.
149 requestDeleteProfileOwner();
150 super.finish();
151 }
152
153 private void setupTests() {
154 mProfileOwnerInstalled = new TestItem(this, R.string.provisioning_byod_profileowner) {
155 @Override
156 public void performTest(ByodFlowTestActivity activity) {
157 queryProfileOwner(true);
158 }
159 };
160
Rubin Xu55d22d42014-09-24 19:56:58 +0100161 mProfileVisibleTest = new TestItem(this, R.string.provisioning_byod_profile_visible,
162 R.string.provisioning_byod_profile_visible_instruction,
163 new Intent(Settings.ACTION_SETTINGS));
164
165 mDeviceAdminVisibleTest = new TestItem(this, R.string.provisioning_byod_admin_visible,
166 R.string.provisioning_byod_admin_visible_instruction,
167 new Intent(Settings.ACTION_SECURITY_SETTINGS));
168
Alexandra Gherghina9c1c5a52014-12-09 12:55:23 +0000169 /*
170 * To keep the image in this test up to date, use the instructions in
171 * {@link ByodIconSamplerActivity}.
172 */
Rubin Xu55d22d42014-09-24 19:56:58 +0100173 mWorkAppVisibleTest = new TestItem(this, R.string.provisioning_byod_workapps_visible,
Alexandra Gherghina9c1c5a52014-12-09 12:55:23 +0000174 R.string.provisioning_byod_profile_visible_instruction,
175 new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)) {
176 @Override
177 public View getCustomView() {
178 LayoutInflater layoutInflater = LayoutInflater.from(getApplicationContext());
179 return layoutInflater.inflate(R.layout.byod_custom_view_badged_icons,
180 null /* root */);
181 }
182 };
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
Alexandra Gherghinaaa24ed72014-10-08 01:11:32 +0100194 Intent intent = new Intent(CrossProfileTestActivity.ACTION_CROSS_PROFILE);
195 Intent chooser = Intent.createChooser(intent, getResources().getString(R.string.provisioning_cross_profile_chooser));
196 mCrossProfileIntentFiltersTest = new TestItem(this,
197 R.string.provisioning_byod_cross_profile,
198 R.string.provisioning_byod_cross_profile_instruction,
199 chooser);
200
Rubin Xu55d22d42014-09-24 19:56:58 +0100201 mTests.add(mProfileOwnerInstalled);
202 mTests.add(mProfileVisibleTest);
203 mTests.add(mDeviceAdminVisibleTest);
204 mTests.add(mWorkAppVisibleTest);
Alexandra Gherghinaaa24ed72014-10-08 01:11:32 +0100205 mTests.add(mCrossProfileIntentFiltersTest);
Robin Lee020f1852015-01-15 11:56:05 +0000206 mTests.add(mDisableNonMarketTest);
207 mTests.add(mEnableNonMarketTest);
Rubin Xu55d22d42014-09-24 19:56:58 +0100208 }
209
210 @Override
211 protected void onListItemClick(ListView l, View v, int position, long id) {
212 super.onListItemClick(l, v, position, id);
213 TestItem test = (TestItem) getListAdapter().getItem(position);
214 test.performTest(this);
215 }
216
217 private void showManualTestDialog(final TestItem test) {
Alexandra Gherghina9c1c5a52014-12-09 12:55:23 +0000218 AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this)
Rubin Xu55d22d42014-09-24 19:56:58 +0100219 .setIcon(android.R.drawable.ic_dialog_info)
Alexandra Gherghina9c1c5a52014-12-09 12:55:23 +0000220 .setTitle(R.string.provisioning_byod)
Rubin Xu55d22d42014-09-24 19:56:58 +0100221 .setNeutralButton(R.string.provisioning_byod_go, null)
Alexandra Gherghina9c1c5a52014-12-09 12:55:23 +0000222 .setPositiveButton(R.string.pass_button_text, new AlertDialog.OnClickListener() {
Rubin Xu55d22d42014-09-24 19:56:58 +0100223 @Override
224 public void onClick(DialogInterface dialog, int which) {
225 setTestResult(test, TestResult.Passed);
226 }
227 })
Alexandra Gherghina9c1c5a52014-12-09 12:55:23 +0000228 .setNegativeButton(R.string.fail_button_text, new AlertDialog.OnClickListener() {
Rubin Xu55d22d42014-09-24 19:56:58 +0100229 @Override
230 public void onClick(DialogInterface dialog, int which) {
231 setTestResult(test, TestResult.Failed);
232 }
Alexandra Gherghina9c1c5a52014-12-09 12:55:23 +0000233 });
234 View customView = test.getCustomView();
235 if (customView != null) {
236 dialogBuilder.setView(customView);
237 } else {
238 dialogBuilder.setMessage(test.getManualTestInstruction());
239 }
240 AlertDialog dialog = dialogBuilder.show();
241 // Note: Setting the OnClickListener on the Dialog rather than the Builder, prevents the
242 // dialog being dismissed on onClick.
Rubin Xu55d22d42014-09-24 19:56:58 +0100243 dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(new OnClickListener() {
244 @Override
245 public void onClick(View v) {
246 ByodFlowTestActivity.this.startActivity(test.getManualTestIntent());
247 }
248 });
249 }
250
251 private void setTestResult(TestItem test, TestResult result) {
252 test.setPassFailState(result);
253
254 boolean testSucceeds = true;
255 for(TestItem aTest : mTests) {
256 testSucceeds &= (aTest.getPassFailState() == TestResult.Passed);
257 }
258 getPassButton().setEnabled(testSucceeds);
259 mAdapter.notifyDataSetChanged();
260 }
261
262 private void startByodProvisioning() {
263 Intent sending = new Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE);
264 sending.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME,
265 mAdminReceiverComponent.getPackageName());
266 sending.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mAdminReceiverComponent);
267
268 if (sending.resolveActivity(getPackageManager()) != null) {
269 // ManagedProvisioning must be started with startActivityForResult, but we don't
270 // care about the result, so passing 0 as a requestCode
271 startActivityForResult(sending, 0);
272 } else {
273 showToast(R.string.provisioning_byod_disabled);
274 }
275 }
276
277 private void queryProfileOwner(boolean showToast) {
278 try {
279 Intent intent = new Intent(ByodHelperActivity.ACTION_QUERY_PROFILE_OWNER);
280 startActivityForResult(intent, REQUEST_STATUS);
281 }
282 catch (ActivityNotFoundException e) {
283 Log.d(TAG, "queryProfileOwner: ActivityNotFoundException", e);
284 setTestResult(mProfileOwnerInstalled, TestResult.Failed);
285 if (showToast) {
286 showToast(R.string.provisioning_byod_no_activity);
287 }
288 }
289 }
290
291 private void requestDeleteProfileOwner() {
292 try {
293 Intent intent = new Intent(ByodHelperActivity.ACTION_REMOVE_PROFILE_OWNER);
294 startActivity(intent);
295 showToast(R.string.provisioning_byod_delete_profile);
296 }
297 catch (ActivityNotFoundException e) {
298 Log.d(TAG, "requestDeleteProfileOwner: ActivityNotFoundException", e);
299 }
300 }
301
302 private void disableComponent() {
303 // Disable app components in the current profile, so only the counterpart in the other profile
304 // can respond (via cross-profile intent filter)
305 getPackageManager().setComponentEnabledSetting(new ComponentName(
306 this, ByodHelperActivity.class),
307 PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
308 PackageManager.DONT_KILL_APP);
309 }
310
Rubin Xu55d22d42014-09-24 19:56:58 +0100311 private void showToast(int messageId) {
312 String message = getString(messageId);
313 Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
314 }
315
316 enum TestResult {
317 Unknown, Failed, Passed
318 }
319
320 static class TestItem {
321
322 private String mDisplayName;
323 private TestResult mPassed;
324 private boolean mManualTest;
325 private String mManualInstruction;
326 private Intent mManualIntent;
327
328 public TestItem(Context context, int nameResId) {
329 mDisplayName = context.getString(nameResId);
330 mPassed = TestResult.Unknown;
331 mManualTest = false;
332 }
333
334 public void performTest(ByodFlowTestActivity activity) {
335 if (isManualTest()) {
336 activity.showManualTestDialog(this);
337 }
338 }
339
340 public TestItem(Context context, int nameResId, int testInstructionResId, Intent testIntent) {
341 mDisplayName = context.getString(nameResId);
342 mPassed = TestResult.Unknown;
343 mManualTest = true;
344 mManualInstruction = context.getString(testInstructionResId);
345 mManualIntent = testIntent;
346 }
347
348 @Override
349 public String toString() {
350 return mDisplayName;
351 }
352
353 TestResult getPassFailState() {
354 return mPassed;
355 }
356
357 void setPassFailState(TestResult state) {
358 mPassed = state;
359 }
360
361 public boolean isManualTest() {
362 return mManualTest;
363 }
364
365 public String getManualTestInstruction() {
366 return mManualInstruction;
367 }
368
369 public Intent getManualTestIntent() {
370 return mManualIntent;
371 }
Alexandra Gherghina9c1c5a52014-12-09 12:55:23 +0000372
373 public View getCustomView() {
374 return null;
375 }
Rubin Xu55d22d42014-09-24 19:56:58 +0100376 }
377
378 static class TestAdapter extends ArrayAdapter<TestItem> {
379
380 public TestAdapter(Context context) {
381 super(context, android.R.layout.simple_list_item_1);
382 }
383
384 @Override
385 public View getView(int position, View convertView, ViewGroup parent) {
386 TextView view = (TextView) super.getView(position, convertView, parent);
387
388 TestItem item = getItem(position);
389 int backgroundResource = 0;
390 int iconResource = 0;
391 if (item.getPassFailState() == TestResult.Passed) {
392 backgroundResource = R.drawable.test_pass_gradient;
393 iconResource = R.drawable.fs_good;
394 } else if (item.getPassFailState() == TestResult.Failed){
395 backgroundResource = R.drawable.test_fail_gradient;
396 iconResource = R.drawable.fs_error;
397 }
398 view.setBackgroundResource(backgroundResource);
399 view.setPadding(10, 0, 10, 0);
400 view.setCompoundDrawablePadding(10);
401 view.setCompoundDrawablesWithIntrinsicBounds(0, 0, iconResource, 0);
402
403 return view;
404 }
405 }
Rubin Xu55d22d42014-09-24 19:56:58 +0100406}