blob: da823e80dc10482d82f4bc319a9e893689ea590c [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;
32import android.view.View;
33import android.view.ViewGroup;
34import android.view.View.OnClickListener;
35import android.widget.ArrayAdapter;
36import 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
65 private TestAdapter mAdapter;
66 private View mStartProvisioningButton;
67 private List<TestItem> mTests = new ArrayList<TestItem>();
68
69 protected DevicePolicyManager mDevicePolicyManager;
70
71 private TestItem mProfileOwnerInstalled;
72 private TestItem mDiskEncryptionTest;
73 private TestItem mProfileVisibleTest;
74 private TestItem mDeviceAdminVisibleTest;
75 private TestItem mWorkAppVisibleTest;
Alexandra Gherghinaaa24ed72014-10-08 01:11:32 +010076 private TestItem mCrossProfileIntentFiltersTest;
Rubin Xu55d22d42014-09-24 19:56:58 +010077
78 @Override
79 protected void onCreate(Bundle savedInstanceState) {
80 super.onCreate(savedInstanceState);
81 mAdminReceiverComponent = new ComponentName(this, DeviceAdminTestReceiver.class.getName());
82 mDevicePolicyManager = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
83 disableComponent();
84
85 setContentView(R.layout.provisioning_byod);
86 setInfoResources(R.string.provisioning_byod, R.string.provisioning_byod_info, -1);
87 setPassFailButtonClickListeners();
88 getPassButton().setEnabled(false);
89 setResult(RESULT_CANCELED);
90
91 setupTests();
92
93 mAdapter = new TestAdapter(this);
94 setListAdapter(mAdapter);
95 mAdapter.addAll(mTests);
96
97 mStartProvisioningButton = findViewById(R.id.byod_start);
98 mStartProvisioningButton.setOnClickListener(new OnClickListener() {
99 @Override
100 public void onClick(View v) {
101 startByodProvisioning();
102 }
103 });
104
105 // If we are started by managed provisioning (fresh managed provisioning after encryption
106 // reboot), redirect the user back to the main test list. This is because the test result
107 // is only saved by the parent TestListActivity, and if we did allow the user to proceed
108 // here, the test result would be lost when this activity finishes.
109 if (ByodHelperActivity.ACTION_PROFILE_OWNER_STATUS.equals(getIntent().getAction())) {
110 startActivity(new Intent(this, TestListActivity.class));
111 // Calling super.finish() because we delete managed profile in our overridden of finish(),
112 // which is not what we want to do here.
113 super.finish();
114 } else {
115 queryProfileOwner(false);
116 }
117 }
118
119 @Override
120 protected void onNewIntent(Intent intent) {
121 // This is called when managed provisioning completes successfully without reboot.
122 super.onNewIntent(intent);
123 if (ByodHelperActivity.ACTION_PROFILE_OWNER_STATUS.equals(intent.getAction())) {
124 handleStatusUpdate(intent);
125 }
126 }
127
128 @Override
129 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
130 // Called after queryProfileOwner()
131 super.onActivityResult(requestCode, resultCode, data);
132 if (requestCode == REQUEST_STATUS && resultCode == RESULT_OK) {
133 handleStatusUpdate(data);
134 }
135 }
136
137 private void handleStatusUpdate(Intent data) {
138 boolean provisioned = data.getBooleanExtra(ByodHelperActivity.EXTRA_PROVISIONED, false);
139 setTestResult(mProfileOwnerInstalled, provisioned ? TestResult.Passed : TestResult.Failed);
140 }
141
142 @Override
143 public void finish() {
144 // Pass and fail buttons are known to call finish() when clicked, and this is when we want to
145 // clean up the provisioned profile.
146 requestDeleteProfileOwner();
147 super.finish();
148 }
149
150 private void setupTests() {
151 mProfileOwnerInstalled = new TestItem(this, R.string.provisioning_byod_profileowner) {
152 @Override
153 public void performTest(ByodFlowTestActivity activity) {
154 queryProfileOwner(true);
155 }
156 };
157
158 mDiskEncryptionTest = new TestItem(this, R.string.provisioning_byod_diskencryption) {
159 @Override
160 public TestResult getPassFailState() {
161 return isDeviceEncrypted() ? TestResult.Passed : TestResult.Failed;
162 }
163 };
164
165 mProfileVisibleTest = new TestItem(this, R.string.provisioning_byod_profile_visible,
166 R.string.provisioning_byod_profile_visible_instruction,
167 new Intent(Settings.ACTION_SETTINGS));
168
169 mDeviceAdminVisibleTest = new TestItem(this, R.string.provisioning_byod_admin_visible,
170 R.string.provisioning_byod_admin_visible_instruction,
171 new Intent(Settings.ACTION_SECURITY_SETTINGS));
172
173 mWorkAppVisibleTest = new TestItem(this, R.string.provisioning_byod_workapps_visible,
174 R.string.provisioning_byod_workapps_visible_instruction,
175 new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME));
176
Alexandra Gherghinaaa24ed72014-10-08 01:11:32 +0100177 Intent intent = new Intent(CrossProfileTestActivity.ACTION_CROSS_PROFILE);
178 Intent chooser = Intent.createChooser(intent, getResources().getString(R.string.provisioning_cross_profile_chooser));
179 mCrossProfileIntentFiltersTest = new TestItem(this,
180 R.string.provisioning_byod_cross_profile,
181 R.string.provisioning_byod_cross_profile_instruction,
182 chooser);
183
Rubin Xu55d22d42014-09-24 19:56:58 +0100184 mTests.add(mDiskEncryptionTest);
185 mTests.add(mProfileOwnerInstalled);
186 mTests.add(mProfileVisibleTest);
187 mTests.add(mDeviceAdminVisibleTest);
188 mTests.add(mWorkAppVisibleTest);
Alexandra Gherghinaaa24ed72014-10-08 01:11:32 +0100189 mTests.add(mCrossProfileIntentFiltersTest);
Rubin Xu55d22d42014-09-24 19:56:58 +0100190 }
191
192 @Override
193 protected void onListItemClick(ListView l, View v, int position, long id) {
194 super.onListItemClick(l, v, position, id);
195 TestItem test = (TestItem) getListAdapter().getItem(position);
196 test.performTest(this);
197 }
198
199 private void showManualTestDialog(final TestItem test) {
200 AlertDialog dialog = new AlertDialog.Builder(this)
201 .setIcon(android.R.drawable.ic_dialog_info)
202 .setMessage(test.getManualTestInstruction())
203 .setNeutralButton(R.string.provisioning_byod_go, null)
204 .setPositiveButton(R.string.pass_button_text, new DialogInterface.OnClickListener() {
205 @Override
206 public void onClick(DialogInterface dialog, int which) {
207 setTestResult(test, TestResult.Passed);
208 }
209 })
210 .setNegativeButton(R.string.fail_button_text, new DialogInterface.OnClickListener() {
211 @Override
212 public void onClick(DialogInterface dialog, int which) {
213 setTestResult(test, TestResult.Failed);
214 }
215 })
216 .create();
217 dialog.show();
218
219 dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(new OnClickListener() {
220 @Override
221 public void onClick(View v) {
222 ByodFlowTestActivity.this.startActivity(test.getManualTestIntent());
223 }
224 });
225 }
226
227 private void setTestResult(TestItem test, TestResult result) {
228 test.setPassFailState(result);
229
230 boolean testSucceeds = true;
231 for(TestItem aTest : mTests) {
232 testSucceeds &= (aTest.getPassFailState() == TestResult.Passed);
233 }
234 getPassButton().setEnabled(testSucceeds);
235 mAdapter.notifyDataSetChanged();
236 }
237
238 private void startByodProvisioning() {
239 Intent sending = new Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE);
240 sending.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME,
241 mAdminReceiverComponent.getPackageName());
242 sending.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mAdminReceiverComponent);
243
244 if (sending.resolveActivity(getPackageManager()) != null) {
245 // ManagedProvisioning must be started with startActivityForResult, but we don't
246 // care about the result, so passing 0 as a requestCode
247 startActivityForResult(sending, 0);
248 } else {
249 showToast(R.string.provisioning_byod_disabled);
250 }
251 }
252
253 private void queryProfileOwner(boolean showToast) {
254 try {
255 Intent intent = new Intent(ByodHelperActivity.ACTION_QUERY_PROFILE_OWNER);
256 startActivityForResult(intent, REQUEST_STATUS);
257 }
258 catch (ActivityNotFoundException e) {
259 Log.d(TAG, "queryProfileOwner: ActivityNotFoundException", e);
260 setTestResult(mProfileOwnerInstalled, TestResult.Failed);
261 if (showToast) {
262 showToast(R.string.provisioning_byod_no_activity);
263 }
264 }
265 }
266
267 private void requestDeleteProfileOwner() {
268 try {
269 Intent intent = new Intent(ByodHelperActivity.ACTION_REMOVE_PROFILE_OWNER);
270 startActivity(intent);
271 showToast(R.string.provisioning_byod_delete_profile);
272 }
273 catch (ActivityNotFoundException e) {
274 Log.d(TAG, "requestDeleteProfileOwner: ActivityNotFoundException", e);
275 }
276 }
277
278 private void disableComponent() {
279 // Disable app components in the current profile, so only the counterpart in the other profile
280 // can respond (via cross-profile intent filter)
281 getPackageManager().setComponentEnabledSetting(new ComponentName(
282 this, ByodHelperActivity.class),
283 PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
284 PackageManager.DONT_KILL_APP);
285 }
286
287 private boolean isDeviceEncrypted() {
288 return mDevicePolicyManager.getStorageEncryptionStatus()
289 == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE;
290 }
291
292 private void showToast(int messageId) {
293 String message = getString(messageId);
294 Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
295 }
296
297 enum TestResult {
298 Unknown, Failed, Passed
299 }
300
301 static class TestItem {
302
303 private String mDisplayName;
304 private TestResult mPassed;
305 private boolean mManualTest;
306 private String mManualInstruction;
307 private Intent mManualIntent;
308
309 public TestItem(Context context, int nameResId) {
310 mDisplayName = context.getString(nameResId);
311 mPassed = TestResult.Unknown;
312 mManualTest = false;
313 }
314
315 public void performTest(ByodFlowTestActivity activity) {
316 if (isManualTest()) {
317 activity.showManualTestDialog(this);
318 }
319 }
320
321 public TestItem(Context context, int nameResId, int testInstructionResId, Intent testIntent) {
322 mDisplayName = context.getString(nameResId);
323 mPassed = TestResult.Unknown;
324 mManualTest = true;
325 mManualInstruction = context.getString(testInstructionResId);
326 mManualIntent = testIntent;
327 }
328
329 @Override
330 public String toString() {
331 return mDisplayName;
332 }
333
334 TestResult getPassFailState() {
335 return mPassed;
336 }
337
338 void setPassFailState(TestResult state) {
339 mPassed = state;
340 }
341
342 public boolean isManualTest() {
343 return mManualTest;
344 }
345
346 public String getManualTestInstruction() {
347 return mManualInstruction;
348 }
349
350 public Intent getManualTestIntent() {
351 return mManualIntent;
352 }
353 }
354
355 static class TestAdapter extends ArrayAdapter<TestItem> {
356
357 public TestAdapter(Context context) {
358 super(context, android.R.layout.simple_list_item_1);
359 }
360
361 @Override
362 public View getView(int position, View convertView, ViewGroup parent) {
363 TextView view = (TextView) super.getView(position, convertView, parent);
364
365 TestItem item = getItem(position);
366 int backgroundResource = 0;
367 int iconResource = 0;
368 if (item.getPassFailState() == TestResult.Passed) {
369 backgroundResource = R.drawable.test_pass_gradient;
370 iconResource = R.drawable.fs_good;
371 } else if (item.getPassFailState() == TestResult.Failed){
372 backgroundResource = R.drawable.test_fail_gradient;
373 iconResource = R.drawable.fs_error;
374 }
375 view.setBackgroundResource(backgroundResource);
376 view.setPadding(10, 0, 10, 0);
377 view.setCompoundDrawablePadding(10);
378 view.setCompoundDrawablesWithIntrinsicBounds(0, 0, iconResource, 0);
379
380 return view;
381 }
382 }
383
384}