diff options
-rw-r--r-- | AndroidManifest.xml | 6 | ||||
-rw-r--r-- | PREUPLOAD.cfg | 7 | ||||
-rw-r--r-- | src/com/android/provision/DefaultActivity.java | 208 | ||||
-rw-r--r-- | src/com/android/provision/DpcInfo.java | 85 | ||||
-rw-r--r-- | src/com/android/provision/Utils.java | 81 |
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"); + } +} |