summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelipe Leme <felipeal@google.com>2020-11-17 16:11:03 -0800
committerFelipe Leme <felipeal@google.com>2020-11-23 11:54:26 -0800
commit23356226d971a60ebf8d56ac03abe453a045b5cf (patch)
tree61746623e5a225c4dd8bf862d368fd16688c7fae
parenta2953580122c549e6108fd7e2f911cde1fb2e7d2 (diff)
downloadProvision-23356226d971a60ebf8d56ac03abe453a045b5cf.tar.gz
Changed Provision app so it can trigger device owner provisioning
The Provision app is a setup wizard that by default just set the provisioning Settings, but with this change its behavior can be changed to trigger Device Owner provisioning. For example, to provision Device Owner using the TestDPC app, run the steps below after first boot: adb root adb install PATH_TO_TESTDPC_APK adb shell settings put secure tmp_provision_set_do 1 adb shell settings put secure tmp_provision_package com.afwsamples.testdpc adb shell settings put secure tmp_provision_receiver com.afwsamples.testdpc.DeviceAdminReceiver adb shell settings put secure tmp_provision_trigger 2 adb shell rm /data/system/device_policies.xml adb shell settings put global device_provisioned 0 adb shell settings put secure user_setup_complete 0 adb shell pm enable com.android.provision adb shell pm enable com.android.provision/.DefaultActivity adb shell stop && adb shell start Test: manual verification (see above) Bug: 170333009 Change-Id: I248b4816a3767d9ff742b391ecea1d83a09b7613
-rw-r--r--AndroidManifest.xml6
-rw-r--r--PREUPLOAD.cfg7
-rw-r--r--src/com/android/provision/DefaultActivity.java208
-rw-r--r--src/com/android/provision/DpcInfo.java85
-rw-r--r--src/com/android/provision/Utils.java81
5 files changed, 384 insertions, 3 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 39fcdc3..11f85c0 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -24,6 +24,12 @@
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+ <!-- To start the device owner provisioning workflow -->
+ <uses-permission android:name="android.permission.DISPATCH_PROVISIONING_MESSAGE"/>
+
+ <!-- To factory reset if provisioning failed -->
+ <uses-permission android:name="android.permission.MASTER_CLEAR"/>
+
<application>
<activity android:name="DefaultActivity"
android:excludeFromRecents="true"
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
new file mode 100644
index 0000000..38f9800
--- /dev/null
+++ b/PREUPLOAD.cfg
@@ -0,0 +1,7 @@
+[Hook Scripts]
+checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
+ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES}
+
+[Builtin Hooks]
+commit_msg_changeid_field = true
+commit_msg_test_field = true
diff --git a/src/com/android/provision/DefaultActivity.java b/src/com/android/provision/DefaultActivity.java
index 031f3b1..92720e3 100644
--- a/src/com/android/provision/DefaultActivity.java
+++ b/src/com/android/provision/DefaultActivity.java
@@ -16,33 +16,235 @@
package com.android.provision;
+import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_TRIGGER;
+
+import static com.android.provision.Utils.DEFAULT_SETTINGS_PROVISION_DO_MODE;
+import static com.android.provision.Utils.SETTINGS_PROVISION_DO_MODE;
+import static com.android.provision.Utils.TAG;
+import static com.android.provision.Utils.getSettings;
+
import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.provider.Settings;
+import android.util.Log;
/**
- * Application that sets the provisioned bit, like SetupWizard does.
+ * Application that sets the provisioned bit, like {@code SetupWizard} does.
+ *
+ * <p>By default, it silently provisions the device, but it can also be used to provision
+ * {@code DeviceOwner}. For example, to set the {@code TestDPC} app, run the steps below:
+ * <pre><code>
+ adb root
+ adb install PATH_TO_TESTDPC_APK
+ adb shell settings put secure tmp_provision_set_do 1
+ adb shell settings put secure tmp_provision_package com.afwsamples.testdpc
+ adb shell settings put secure tmp_provision_receiver com.afwsamples.testdpc.DeviceAdminReceiver
+ adb shell settings put secure tmp_provision_trigger 2
+ adb shell rm /data/system/device_policies.xml
+ adb shell settings put global device_provisioned 0
+ adb shell settings put secure user_setup_complete 0
+ adb shell pm enable com.android.provision
+ adb shell pm enable com.android.provision/.DefaultActivity
+ adb shell stop && adb shell start
+
+ // You might also need to run:
+ adb shell am start com.android.provision/.DefaultActivity
+
+ * </code></pre>
*/
public class DefaultActivity extends Activity {
+ // TODO(b/170333009): copied from ManagedProvisioning app, as they're hidden;
+ private static final String PROVISION_FINALIZATION_INSIDE_SUW =
+ "android.app.action.PROVISION_FINALIZATION_INSIDE_SUW";
+ private static final int RESULT_CODE_PROFILE_OWNER_SET = 122;
+ private static final int RESULT_CODE_DEVICE_OWNER_SET = 123;
+
+ private static final int REQUEST_CODE_STEP1 = 42;
+ private static final int REQUEST_CODE_STEP2_PO = 43;
+ private static final int REQUEST_CODE_STEP2_DO = 44;
+
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
+ boolean provisionDeviceOwner = getSettings(getContentResolver(), SETTINGS_PROVISION_DO_MODE,
+ DEFAULT_SETTINGS_PROVISION_DO_MODE) == 1;
+
+ if (provisionDeviceOwner) {
+ provisionDeviceOwner();
+ return;
+ }
+ finishSetup();
+ }
+
+ private void finishSetup() {
+ setProvisioningState();
+ disableSelfAndFinish();
+ }
+
+ private void setProvisioningState() {
+ Log.i(TAG, "Setting provisioning state");
// Add a persistent setting to allow other apps to know the device has been provisioned.
Settings.Global.putInt(getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 1);
Settings.Secure.putInt(getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 1);
+ }
+ private void disableSelfAndFinish() {
// remove this activity from the package manager.
PackageManager pm = getPackageManager();
ComponentName name = new ComponentName(this, DefaultActivity.class);
+ Log.i(TAG, "Disabling itself (" + name + ")");
pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
-
// terminate the activity.
finish();
}
-}
+ private void provisionDeviceOwner() {
+ if (!getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)) {
+ Log.e(TAG, "Cannot set up device owner because device does not have the "
+ + PackageManager.FEATURE_DEVICE_ADMIN + " feature");
+ finishSetup();
+ return;
+ }
+ DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
+ if (!dpm.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE)) {
+ Log.e(TAG, "DeviceOwner provisioning is not allowed, most like device is already "
+ + "provisioned");
+ finishSetup();
+ return;
+ }
+
+ DpcInfo dpcInfo = new DpcInfo(getContentResolver());
+ Intent intent = new Intent(ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE);
+ intent.putExtra(EXTRA_PROVISIONING_TRIGGER, dpcInfo.trigger);
+ intent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
+ dpcInfo.getReceiverComponentName());
+ if (dpcInfo.checkSum != null) {
+ intent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM, dpcInfo.checkSum);
+ }
+ if (dpcInfo.downloadUrl != null) {
+ intent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION,
+ dpcInfo.downloadUrl);
+ }
+
+ Log.i(TAG, "Provisioning device with " + dpcInfo + ". Intent: " + intent);
+ startActivityForResult(intent, REQUEST_CODE_STEP1);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ Log.d(TAG, "onActivityResult(): request=" + requestCode + ", result="
+ + resultCodeToString(resultCode) + ", data=" + data);
+
+ switch (requestCode) {
+ case REQUEST_CODE_STEP1:
+ onProvisioningStep1Result(resultCode);
+ break;
+ case REQUEST_CODE_STEP2_PO:
+ case REQUEST_CODE_STEP2_DO:
+ onProvisioningStep2Result(requestCode, resultCode);
+ break;
+ default:
+ showErrorMessage("onActivityResult(): invalid request code " + requestCode);
+ }
+ }
+
+ private void onProvisioningStep1Result(int resultCode) {
+ int requestCodeStep2;
+ switch (resultCode) {
+ case RESULT_CODE_PROFILE_OWNER_SET:
+ requestCodeStep2 = REQUEST_CODE_STEP2_PO;
+ break;
+ case RESULT_CODE_DEVICE_OWNER_SET:
+ requestCodeStep2 = REQUEST_CODE_STEP2_DO;
+ break;
+ default:
+ factoryReset("invalid response from "
+ + ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE + ": "
+ + resultCodeToString(resultCode));
+ return;
+ }
+ Intent intent = new Intent(PROVISION_FINALIZATION_INSIDE_SUW)
+ .addCategory(Intent.CATEGORY_DEFAULT);
+ Log.i(TAG, "Finalizing DPC with " + intent);
+ startActivityForResult(intent, requestCodeStep2);
+ }
+
+ private void onProvisioningStep2Result(int requestCode, int resultCode) {
+ // Must set state before launching the intent that finalize the DPC, because the DPC
+ // implementation might not remove the back button
+ setProvisioningState();
+
+ boolean doMode = requestCode == REQUEST_CODE_STEP2_DO;
+ if (resultCode != RESULT_OK) {
+ factoryReset("invalid response from " + PROVISION_FINALIZATION_INSIDE_SUW + ": "
+ + resultCodeToString(resultCode));
+ return;
+ }
+
+ Log.i(TAG, (doMode ? "Device owner" : "Profile owner") + " mode provisioned!");
+ disableSelfAndFinish();
+ }
+
+ private static String resultCodeToString(int resultCode) {
+ StringBuilder result = new StringBuilder();
+ switch (resultCode) {
+ case RESULT_OK:
+ result.append("RESULT_OK");
+ break;
+ case RESULT_CANCELED:
+ result.append("RESULT_CANCELED");
+ break;
+ case RESULT_FIRST_USER:
+ result.append("RESULT_FIRST_USER");
+ break;
+ case RESULT_CODE_PROFILE_OWNER_SET:
+ result.append("RESULT_CODE_PROFILE_OWNER_SET");
+ break;
+ case RESULT_CODE_DEVICE_OWNER_SET:
+ result.append("RESULT_CODE_DEVICE_OWNER_SET");
+ break;
+ default:
+ result.append("UNKNOWN_CODE");
+ }
+ return result.append('(').append(resultCode).append(')').toString();
+ }
+
+ private void showErrorMessage(String message) {
+ Log.e(TAG, "Error: " + message);
+ }
+
+ private void factoryReset(String reason) {
+ new AlertDialog.Builder(this)
+ .setMessage("Device owner provisioning failed (" + reason
+ + ") and device must be factory reset")
+ .setPositiveButton("Reset", (d, w) -> sendFactoryResetIntent(reason))
+ .setOnDismissListener((d) -> sendFactoryResetIntent(reason))
+ .show();
+ }
+
+ private void sendFactoryResetIntent(String reason) {
+ Log.e(TAG, "Factory resetting: " + reason);
+ Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
+ intent.setPackage("android");
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.putExtra(Intent.EXTRA_REASON, reason);
+
+ sendBroadcast(intent);
+
+ // Just in case the factory reset request fails...
+ finishSetup();
+ }
+}
diff --git a/src/com/android/provision/DpcInfo.java b/src/com/android/provision/DpcInfo.java
new file mode 100644
index 0000000..af38d4b
--- /dev/null
+++ b/src/com/android/provision/DpcInfo.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2020 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.provision;
+
+import static com.android.provision.Utils.DEFAULT_SETTINGS_PROVISION_DO_CHECKSUM;
+import static com.android.provision.Utils.DEFAULT_SETTINGS_PROVISION_DO_DOWNLOAD_TRIGGER;
+import static com.android.provision.Utils.DEFAULT_SETTINGS_PROVISION_DO_DOWNLOAD_URL;
+import static com.android.provision.Utils.DEFAULT_SETTINGS_PROVISION_DO_PKG;
+import static com.android.provision.Utils.DEFAULT_SETTINGS_PROVISION_DO_RECEIVER;
+import static com.android.provision.Utils.SETTINGS_PROVISION_DO_CHECKSUM;
+import static com.android.provision.Utils.SETTINGS_PROVISION_DO_DOWNLOAD_TRIGGER;
+import static com.android.provision.Utils.SETTINGS_PROVISION_DO_DOWNLOAD_URL;
+import static com.android.provision.Utils.SETTINGS_PROVISION_DO_PKG;
+import static com.android.provision.Utils.SETTINGS_PROVISION_DO_RECEIVER;
+import static com.android.provision.Utils.getSettings;
+
+import android.content.ComponentName;
+import android.content.ContentResolver;
+
+import java.util.Objects;
+
+/**
+ * Info about a Device Policy Controller app.
+ */
+final class DpcInfo {
+
+ public final String packageName;
+ private final String mReceiverName;
+ public final String checkSum;
+ public final String downloadUrl;
+ public final int trigger;
+
+ DpcInfo(ContentResolver resolver) {
+ this(getSettings(resolver, SETTINGS_PROVISION_DO_PKG, DEFAULT_SETTINGS_PROVISION_DO_PKG),
+ getSettings(resolver, SETTINGS_PROVISION_DO_RECEIVER,
+ DEFAULT_SETTINGS_PROVISION_DO_RECEIVER),
+ getSettings(resolver, SETTINGS_PROVISION_DO_CHECKSUM,
+ DEFAULT_SETTINGS_PROVISION_DO_CHECKSUM),
+ getSettings(resolver, SETTINGS_PROVISION_DO_DOWNLOAD_URL,
+ DEFAULT_SETTINGS_PROVISION_DO_DOWNLOAD_URL),
+ getSettings(resolver, SETTINGS_PROVISION_DO_DOWNLOAD_TRIGGER,
+ DEFAULT_SETTINGS_PROVISION_DO_DOWNLOAD_TRIGGER));
+ }
+
+ private DpcInfo(String packageName, String receiverName, String checkSum, String downloadUrl,
+ int trigger) {
+ this.packageName = Objects.requireNonNull(packageName,
+ "packageName (" + SETTINGS_PROVISION_DO_PKG + ") cannot be null");
+ this.mReceiverName = Objects.requireNonNull(receiverName,
+ "receiverName(" + SETTINGS_PROVISION_DO_RECEIVER + ") cannot be null");
+ this.checkSum = checkSum;
+ this.downloadUrl = downloadUrl;
+ this.trigger = trigger;
+ }
+
+ /***
+ * Gets the name of the admin receiver.
+ */
+ public ComponentName getReceiverComponentName() {
+ return new ComponentName(packageName, mReceiverName);
+ }
+
+ @Override
+ public String toString() {
+ return "DpcInfo[package=" + packageName
+ + ", receiver=" + getReceiverComponentName()
+ + ", checkSum=" + checkSum
+ + ", downloadUrl=" + downloadUrl
+ + ", trigger=" + trigger
+ + "]";
+ }
+}
diff --git a/src/com/android/provision/Utils.java b/src/com/android/provision/Utils.java
new file mode 100644
index 0000000..b26a0b4
--- /dev/null
+++ b/src/com/android/provision/Utils.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 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.provision;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+import android.util.Log;
+
+/**
+ * Utility helpers.
+ */
+final class Utils {
+
+ static final String TAG = "Provision";
+
+ private static final String BASE_SETTINGS = "tmp_provision_";
+ static final String SETTINGS_PROVISION_DO_MODE = BASE_SETTINGS + "set_do";
+ static final String SETTINGS_PROVISION_DO_PKG = BASE_SETTINGS + "package";
+ static final String SETTINGS_PROVISION_DO_RECEIVER = BASE_SETTINGS + "receiver";
+ static final String SETTINGS_PROVISION_DO_CHECKSUM = BASE_SETTINGS + "checksum";
+ static final String SETTINGS_PROVISION_DO_DOWNLOAD_URL = BASE_SETTINGS + "download_url";
+ static final String SETTINGS_PROVISION_DO_DOWNLOAD_TRIGGER = BASE_SETTINGS + "trigger";
+
+ // Values below should be merged as null / 0, but can be changed locally to make it easier
+ // to trigger device owner provisioning (see example below).
+ static final int DEFAULT_SETTINGS_PROVISION_DO_MODE = 0;
+ static final String DEFAULT_SETTINGS_PROVISION_DO_PKG = null;
+ static final String DEFAULT_SETTINGS_PROVISION_DO_RECEIVER = null;
+ static final int DEFAULT_SETTINGS_PROVISION_DO_DOWNLOAD_TRIGGER = 0;
+ static final String DEFAULT_SETTINGS_PROVISION_DO_CHECKSUM = null;
+ static final String DEFAULT_SETTINGS_PROVISION_DO_DOWNLOAD_URL = null;
+
+ // Use these constants to trigger device owner provisioning using the TestDPC app (notice that
+ // it must be manually installed in the device).
+// static final int DEFAULT_SETTINGS_PROVISION_DO_MODE = 1;
+// static final String DEFAULT_SETTINGS_PROVISION_DO_PKG = "com.afwsamples.testdpc";
+// static final String DEFAULT_SETTINGS_PROVISION_DO_RECEIVER =
+// "com.afwsamples.testdpc.DeviceAdminReceiver";
+// static final int DEFAULT_SETTINGS_PROVISION_DO_DOWNLOAD_TRIGGER =
+// android.app.admin.DevicePolicyManager.PROVISIONING_TRIGGER_QR_CODE;
+
+
+ static String getSettings(ContentResolver resolver, String property,
+ String overriddenValue) {
+ if (overriddenValue != null) {
+ Log.w(TAG, "Using OVERRIDDEN value " + overriddenValue + " for property " + property);
+ return overriddenValue;
+ }
+ String value = Settings.Secure.getString(resolver, property);
+ Log.w(TAG, "Using value " + overriddenValue + " for property " + property);
+ return value;
+ }
+
+ static int getSettings(ContentResolver resolver, String property,
+ int overriddenValue) {
+ if (overriddenValue != 0) {
+ Log.w(TAG, "Using OVERRIDDEN value " + overriddenValue + " for property " + property);
+ return overriddenValue;
+ }
+ int value = Settings.Secure.getInt(resolver, property, overriddenValue);
+ Log.w(TAG, "Using value " + overriddenValue + " for property " + property);
+ return value;
+ }
+
+ private Utils() {
+ throw new UnsupportedOperationException("contains only static members");
+ }
+}