BYOD provisioning flow in CtsVerifier
Bug: 17384646
Change-Id: Ifa98244e29fa549a65884d63f5efc8912f0ed4ac
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
new file mode 100644
index 0000000..69071f6
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.managedprovisioning;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.admin.DevicePolicyManager;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.TestListActivity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * CTS verifier test for BYOD managed provisioning flow.
+ * This activity is responsible for starting the managed provisioning flow and verify the outcome of provisioning.
+ * It performs the following verifications:
+ * Full disk encryption is enabled.
+ * Profile owner is correctly installed.
+ * Profile owner shows up in the Settings app.
+ * Badged work apps show up in launcher.
+ * The first two verifications are performed automatically, by interacting with profile owner using
+ * cross-profile intents, while the last two are carried out manually by the user.
+ */
+public class ByodFlowTestActivity extends PassFailButtons.ListActivity {
+
+ private final String TAG = "ByodFlowTestActivity";
+ private static final int REQUEST_STATUS = 1;
+
+ private ComponentName mAdminReceiverComponent;
+
+ private TestAdapter mAdapter;
+ private View mStartProvisioningButton;
+ private List<TestItem> mTests = new ArrayList<TestItem>();
+
+ protected DevicePolicyManager mDevicePolicyManager;
+
+ private TestItem mProfileOwnerInstalled;
+ private TestItem mDiskEncryptionTest;
+ private TestItem mProfileVisibleTest;
+ private TestItem mDeviceAdminVisibleTest;
+ private TestItem mWorkAppVisibleTest;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mAdminReceiverComponent = new ComponentName(this, DeviceAdminTestReceiver.class.getName());
+ mDevicePolicyManager = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
+ disableComponent();
+
+ setContentView(R.layout.provisioning_byod);
+ setInfoResources(R.string.provisioning_byod, R.string.provisioning_byod_info, -1);
+ setPassFailButtonClickListeners();
+ getPassButton().setEnabled(false);
+ setResult(RESULT_CANCELED);
+
+ setupTests();
+
+ mAdapter = new TestAdapter(this);
+ setListAdapter(mAdapter);
+ mAdapter.addAll(mTests);
+
+ mStartProvisioningButton = findViewById(R.id.byod_start);
+ mStartProvisioningButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startByodProvisioning();
+ }
+ });
+
+ // If we are started by managed provisioning (fresh managed provisioning after encryption
+ // reboot), redirect the user back to the main test list. This is because the test result
+ // is only saved by the parent TestListActivity, and if we did allow the user to proceed
+ // here, the test result would be lost when this activity finishes.
+ if (ByodHelperActivity.ACTION_PROFILE_OWNER_STATUS.equals(getIntent().getAction())) {
+ startActivity(new Intent(this, TestListActivity.class));
+ // Calling super.finish() because we delete managed profile in our overridden of finish(),
+ // which is not what we want to do here.
+ super.finish();
+ } else {
+ queryProfileOwner(false);
+ }
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ // This is called when managed provisioning completes successfully without reboot.
+ super.onNewIntent(intent);
+ if (ByodHelperActivity.ACTION_PROFILE_OWNER_STATUS.equals(intent.getAction())) {
+ handleStatusUpdate(intent);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ // Called after queryProfileOwner()
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == REQUEST_STATUS && resultCode == RESULT_OK) {
+ handleStatusUpdate(data);
+ }
+ }
+
+ private void handleStatusUpdate(Intent data) {
+ boolean provisioned = data.getBooleanExtra(ByodHelperActivity.EXTRA_PROVISIONED, false);
+ setTestResult(mProfileOwnerInstalled, provisioned ? TestResult.Passed : TestResult.Failed);
+ }
+
+ @Override
+ public void finish() {
+ // Pass and fail buttons are known to call finish() when clicked, and this is when we want to
+ // clean up the provisioned profile.
+ requestDeleteProfileOwner();
+ super.finish();
+ }
+
+ private void setupTests() {
+ mProfileOwnerInstalled = new TestItem(this, R.string.provisioning_byod_profileowner) {
+ @Override
+ public void performTest(ByodFlowTestActivity activity) {
+ queryProfileOwner(true);
+ }
+ };
+
+ mDiskEncryptionTest = new TestItem(this, R.string.provisioning_byod_diskencryption) {
+ @Override
+ public TestResult getPassFailState() {
+ return isDeviceEncrypted() ? TestResult.Passed : TestResult.Failed;
+ }
+ };
+
+ mProfileVisibleTest = new TestItem(this, R.string.provisioning_byod_profile_visible,
+ R.string.provisioning_byod_profile_visible_instruction,
+ new Intent(Settings.ACTION_SETTINGS));
+
+ mDeviceAdminVisibleTest = new TestItem(this, R.string.provisioning_byod_admin_visible,
+ R.string.provisioning_byod_admin_visible_instruction,
+ new Intent(Settings.ACTION_SECURITY_SETTINGS));
+
+ mWorkAppVisibleTest = new TestItem(this, R.string.provisioning_byod_workapps_visible,
+ R.string.provisioning_byod_workapps_visible_instruction,
+ new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME));
+
+ mTests.add(mDiskEncryptionTest);
+ mTests.add(mProfileOwnerInstalled);
+ mTests.add(mProfileVisibleTest);
+ mTests.add(mDeviceAdminVisibleTest);
+ mTests.add(mWorkAppVisibleTest);
+ }
+
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ super.onListItemClick(l, v, position, id);
+ TestItem test = (TestItem) getListAdapter().getItem(position);
+ test.performTest(this);
+ }
+
+ private void showManualTestDialog(final TestItem test) {
+ AlertDialog dialog = new AlertDialog.Builder(this)
+ .setIcon(android.R.drawable.ic_dialog_info)
+ .setMessage(test.getManualTestInstruction())
+ .setNeutralButton(R.string.provisioning_byod_go, null)
+ .setPositiveButton(R.string.pass_button_text, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ setTestResult(test, TestResult.Passed);
+ }
+ })
+ .setNegativeButton(R.string.fail_button_text, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ setTestResult(test, TestResult.Failed);
+ }
+ })
+ .create();
+ dialog.show();
+
+ dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ByodFlowTestActivity.this.startActivity(test.getManualTestIntent());
+ }
+ });
+ }
+
+ private void setTestResult(TestItem test, TestResult result) {
+ test.setPassFailState(result);
+
+ boolean testSucceeds = true;
+ for(TestItem aTest : mTests) {
+ testSucceeds &= (aTest.getPassFailState() == TestResult.Passed);
+ }
+ getPassButton().setEnabled(testSucceeds);
+ mAdapter.notifyDataSetChanged();
+ }
+
+ private void startByodProvisioning() {
+ Intent sending = new Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE);
+ sending.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME,
+ mAdminReceiverComponent.getPackageName());
+ sending.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mAdminReceiverComponent);
+
+ if (sending.resolveActivity(getPackageManager()) != null) {
+ // ManagedProvisioning must be started with startActivityForResult, but we don't
+ // care about the result, so passing 0 as a requestCode
+ startActivityForResult(sending, 0);
+ } else {
+ showToast(R.string.provisioning_byod_disabled);
+ }
+ }
+
+ private void queryProfileOwner(boolean showToast) {
+ try {
+ Intent intent = new Intent(ByodHelperActivity.ACTION_QUERY_PROFILE_OWNER);
+ startActivityForResult(intent, REQUEST_STATUS);
+ }
+ catch (ActivityNotFoundException e) {
+ Log.d(TAG, "queryProfileOwner: ActivityNotFoundException", e);
+ setTestResult(mProfileOwnerInstalled, TestResult.Failed);
+ if (showToast) {
+ showToast(R.string.provisioning_byod_no_activity);
+ }
+ }
+ }
+
+ private void requestDeleteProfileOwner() {
+ try {
+ Intent intent = new Intent(ByodHelperActivity.ACTION_REMOVE_PROFILE_OWNER);
+ startActivity(intent);
+ showToast(R.string.provisioning_byod_delete_profile);
+ }
+ catch (ActivityNotFoundException e) {
+ Log.d(TAG, "requestDeleteProfileOwner: ActivityNotFoundException", e);
+ }
+ }
+
+ private void disableComponent() {
+ // Disable app components in the current profile, so only the counterpart in the other profile
+ // can respond (via cross-profile intent filter)
+ getPackageManager().setComponentEnabledSetting(new ComponentName(
+ this, ByodHelperActivity.class),
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ }
+
+ private boolean isDeviceEncrypted() {
+ return mDevicePolicyManager.getStorageEncryptionStatus()
+ == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE;
+ }
+
+ private void showToast(int messageId) {
+ String message = getString(messageId);
+ Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
+ }
+
+ enum TestResult {
+ Unknown, Failed, Passed
+ }
+
+ static class TestItem {
+
+ private String mDisplayName;
+ private TestResult mPassed;
+ private boolean mManualTest;
+ private String mManualInstruction;
+ private Intent mManualIntent;
+
+ public TestItem(Context context, int nameResId) {
+ mDisplayName = context.getString(nameResId);
+ mPassed = TestResult.Unknown;
+ mManualTest = false;
+ }
+
+ public void performTest(ByodFlowTestActivity activity) {
+ if (isManualTest()) {
+ activity.showManualTestDialog(this);
+ }
+ }
+
+ public TestItem(Context context, int nameResId, int testInstructionResId, Intent testIntent) {
+ mDisplayName = context.getString(nameResId);
+ mPassed = TestResult.Unknown;
+ mManualTest = true;
+ mManualInstruction = context.getString(testInstructionResId);
+ mManualIntent = testIntent;
+ }
+
+ @Override
+ public String toString() {
+ return mDisplayName;
+ }
+
+ TestResult getPassFailState() {
+ return mPassed;
+ }
+
+ void setPassFailState(TestResult state) {
+ mPassed = state;
+ }
+
+ public boolean isManualTest() {
+ return mManualTest;
+ }
+
+ public String getManualTestInstruction() {
+ return mManualInstruction;
+ }
+
+ public Intent getManualTestIntent() {
+ return mManualIntent;
+ }
+ }
+
+ static class TestAdapter extends ArrayAdapter<TestItem> {
+
+ public TestAdapter(Context context) {
+ super(context, android.R.layout.simple_list_item_1);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextView view = (TextView) super.getView(position, convertView, parent);
+
+ TestItem item = getItem(position);
+ int backgroundResource = 0;
+ int iconResource = 0;
+ if (item.getPassFailState() == TestResult.Passed) {
+ backgroundResource = R.drawable.test_pass_gradient;
+ iconResource = R.drawable.fs_good;
+ } else if (item.getPassFailState() == TestResult.Failed){
+ backgroundResource = R.drawable.test_fail_gradient;
+ iconResource = R.drawable.fs_error;
+ }
+ view.setBackgroundResource(backgroundResource);
+ view.setPadding(10, 0, 10, 0);
+ view.setCompoundDrawablePadding(10);
+ view.setCompoundDrawablesWithIntrinsicBounds(0, 0, iconResource, 0);
+
+ return view;
+ }
+ }
+
+}