Allow to specify the admin component for provisioning.

An app may have several device admins.
For this case, allow the caller to specify which one will become
an active device admin, and the profile owner (in the profile owner flow).

We still support the previous way which was to specify only the package.
In this case, we continue to infer the admin from the manifest.

BUG:19348295
Change-Id: I91a74afbad809e838fb6807e182f66f189b7b356
diff --git a/src/com/android/managedprovisioning/BootReminder.java b/src/com/android/managedprovisioning/BootReminder.java
index 85c6511..202cb80 100644
--- a/src/com/android/managedprovisioning/BootReminder.java
+++ b/src/com/android/managedprovisioning/BootReminder.java
@@ -17,7 +17,7 @@
 
 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE;
 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;
-import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME;
 
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -41,9 +41,9 @@
     private static final String PROFILE_OWNER_PREFERENCES_NAME =
             "profile-owner-provisioning-resume";
 
-    private static final String[] PROFILE_OWNER_STRING_EXTRAS = {
-        // Key for the device admin package name
-        EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME
+    private static final String[] PROFILE_OWNER_COMPONENT_NAME_EXTRAS = {
+        // Key for the device admin component name
+        EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME
     };
 
     private static final String[] PROFILE_OWNER_PERSISTABLE_BUNDLE_EXTRAS = {
@@ -104,8 +104,8 @@
      * and {@link EncryptDeviceActivity.TARGET_DEVICE_OWNER}
      *
      * <p> In case of TARGET_PROFILE_OWNER {@code extras} should further contain a value for at
-     * least the key: {@link EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME}, a {@link String} which
-     * specifies the package to set as profile owner.
+     * least the key: {@link EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}, a {@link String}
+     * which, when unflattened to a componentName, specifies the component to set as profile owner.
      *
      * <p>
      * See {@link MessageParser} for the TARGET_DEVICE_OWNER case.
@@ -142,13 +142,14 @@
 
     private static IntentStore getProfileOwnerIntentStore(Context context) {
         return new IntentStore(context,PROFILE_OWNER_INTENT_TARGET, PROFILE_OWNER_PREFERENCES_NAME)
-                .setStringKeys(PROFILE_OWNER_STRING_EXTRAS)
+                .setComponentNameKeys(PROFILE_OWNER_COMPONENT_NAME_EXTRAS)
                 .setPersistableBundleKeys(PROFILE_OWNER_PERSISTABLE_BUNDLE_EXTRAS)
                 .setAccountKeys(PROFILE_OWNER_ACCOUNT_EXTRAS);
     }
 
     private static IntentStore getDeviceOwnerIntentStore(Context context) {
         return new IntentStore(context, DEVICE_OWNER_INTENT_TARGET, DEVICE_OWNER_PREFERENCES_NAME)
+                .setComponentNameKeys(MessageParser.DEVICE_OWNER_COMPONENT_NAME_EXTRAS)
                 .setStringKeys(MessageParser.DEVICE_OWNER_STRING_EXTRAS)
                 .setLongKeys(MessageParser.DEVICE_OWNER_LONG_EXTRAS)
                 .setIntKeys(MessageParser.DEVICE_OWNER_INT_EXTRAS)
diff --git a/src/com/android/managedprovisioning/DeviceOwnerProvisioningActivity.java b/src/com/android/managedprovisioning/DeviceOwnerProvisioningActivity.java
index ba7685b..65cbe6c 100644
--- a/src/com/android/managedprovisioning/DeviceOwnerProvisioningActivity.java
+++ b/src/com/android/managedprovisioning/DeviceOwnerProvisioningActivity.java
@@ -36,6 +36,7 @@
 import android.widget.TextView;
 
 import com.android.managedprovisioning.task.AddWifiNetworkTask;
+import com.android.managedprovisioning.Utils.IllegalProvisioningArgumentException;
 import com.android.setupwizard.navigationbar.SetupWizardNavBar;
 import com.android.setupwizard.navigationbar.SetupWizardNavBar.NavigationBarListener;
 
@@ -147,10 +148,10 @@
         // Parse the incoming intent.
         MessageParser parser = new MessageParser();
         try {
-            mParams = parser.parseIntent(getIntent());
-        } catch (MessageParser.ParseException e) {
+            mParams = parser.parseIntent(getIntent(), this);
+        } catch (Utils.IllegalProvisioningArgumentException e) {
             ProvisionLogger.loge("Could not read data from intent", e);
-            error(e.getErrorMessageId(), false /* no factory reset */);
+            error(R.string.device_owner_error_general, false /* no factory reset */);
             return;
         }
 
diff --git a/src/com/android/managedprovisioning/DeviceOwnerProvisioningService.java b/src/com/android/managedprovisioning/DeviceOwnerProvisioningService.java
index 0f59f71..06f2773 100644
--- a/src/com/android/managedprovisioning/DeviceOwnerProvisioningService.java
+++ b/src/com/android/managedprovisioning/DeviceOwnerProvisioningService.java
@@ -126,7 +126,7 @@
 
                 // Send success if provisioning was succesful.
                 if (mDone) {
-                    onProvisioningSuccess(mParams.mDeviceAdminPackageName);
+                    onProvisioningSuccess();
                 }
             } else {
                 mProvisioningInFlight = true;
@@ -176,7 +176,7 @@
 
             // Send complete intent to mdm.
             Intent result = new Intent(ACTION_PROFILE_PROVISIONING_COMPLETE);
-            result.setPackage(mParams.mDeviceAdminPackageName);
+            result.setPackage(mParams.mDeviceAdminComponentName.getPackageName());
             result.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES |
                     Intent.FLAG_RECEIVER_FOREGROUND);
             if (mParams.mAdminExtrasBundle != null) {
@@ -240,7 +240,7 @@
                     });
 
         mInstallPackageTask = new InstallPackageTask(this,
-                params.mDeviceAdminPackageName, params.mDeviceAdminPackageDownloadLocation,
+                params.getDeviceAdminPackageName(), params.mDeviceAdminPackageDownloadLocation,
                 new InstallPackageTask.Callback() {
                     @Override
                     public void onSuccess() {
@@ -265,7 +265,7 @@
                 });
 
         mSetDevicePolicyTask = new SetDevicePolicyTask(this,
-                params.mDeviceAdminPackageName,
+                params.mDeviceAdminComponentName,
                 getResources().getString(R.string.default_owned_device_username),
                 new SetDevicePolicyTask.Callback() {
                     @Override
@@ -290,13 +290,13 @@
                 });
 
         mDeleteNonRequiredAppsTask = new DeleteNonRequiredAppsTask(
-                this, params.mDeviceAdminPackageName, R.array.required_apps_managed_device,
+                this, params.getDeviceAdminPackageName(), R.array.required_apps_managed_device,
                 R.array.vendor_required_apps_managed_device, true /* creating new profile */,
                 UserHandle.USER_OWNER, params.mLeaveAllSystemAppsEnabled,
                 new DeleteNonRequiredAppsTask.Callback() {
                     public void onSuccess() {
                         // Done with provisioning. Success.
-                        onProvisioningSuccess(params.mDeviceAdminPackageName);
+                        onProvisioningSuccess();
                     }
 
                     @Override
@@ -343,7 +343,7 @@
         LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
     }
 
-    private void onProvisioningSuccess(String deviceAdminPackage) {
+    private void onProvisioningSuccess() {
         if (DEBUG) ProvisionLogger.logd("Reporting success.");
         mDone = true;
 
diff --git a/src/com/android/managedprovisioning/IntentStore.java b/src/com/android/managedprovisioning/IntentStore.java
index ae5839a..8e0ad74 100644
--- a/src/com/android/managedprovisioning/IntentStore.java
+++ b/src/com/android/managedprovisioning/IntentStore.java
@@ -49,6 +49,7 @@
     private String[] mBooleanKeys = new String[0];
     private String[] mPersistableBundleKeys = new String[0];
     private String[] mAccountKeys = new String[0];
+    private String[] mComponentNameKeys = new String[0];
 
     private static final String TAG_PERSISTABLEBUNDLE = "persistable_bundle";
     private static final String TAG_ACCOUNT = "account";
@@ -94,6 +95,11 @@
         return this;
     }
 
+    public IntentStore setComponentNameKeys(String[] keys) {
+        mComponentNameKeys = (keys == null) ? new String[0] : keys;
+        return this;
+    }
+
     public void clear() {
         mPrefs.edit().clear().commit();
     }
@@ -130,6 +136,10 @@
                 editor.putString(key, bundleString);
             }
         }
+        for (String key : mComponentNameKeys) {
+            ComponentName cn = (ComponentName) data.getParcelable(key);
+            editor.putString(key, cn.flattenToString());
+        }
         editor.putBoolean(IS_SET, true);
         editor.commit();
     }
@@ -179,6 +189,12 @@
                 }
             }
         }
+        for (String key : mComponentNameKeys) {
+            if (mPrefs.contains(key)) {
+                result.putExtra(key, ComponentName.unflattenFromString(
+                        mPrefs.getString(key, null)));
+            }
+        }
 
         return result;
     }
diff --git a/src/com/android/managedprovisioning/MessageParser.java b/src/com/android/managedprovisioning/MessageParser.java
index c73685c..d379e86 100644
--- a/src/com/android/managedprovisioning/MessageParser.java
+++ b/src/com/android/managedprovisioning/MessageParser.java
@@ -28,15 +28,18 @@
 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_BYPASS;
 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PAC_URL;
 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;
-import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM;
 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION;
 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER;
-import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED;
 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION;
 import static android.app.admin.DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.nfc.NdefMessage;
 import android.nfc.NdefRecord;
@@ -47,6 +50,8 @@
 import android.text.TextUtils;
 import android.util.Base64;
 
+import com.android.managedprovisioning.Utils.IllegalProvisioningArgumentException;
+
 import java.io.IOException;
 import java.io.StringReader;
 import java.util.Enumeration;
@@ -71,8 +76,9 @@
  *
  * <p>
  * Intent was received directly.
- * The intent contains the extra {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME},
- * and may contain {@link #EXTRA_PROVISIONING_TIME_ZONE},
+ * The intent contains the extra {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME} or
+ * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} (which is deprecated but that we still
+ * support), and may contain {@link #EXTRA_PROVISIONING_TIME_ZONE},
  * {@link #EXTRA_PROVISIONING_LOCAL_TIME}, and {@link #EXTRA_PROVISIONING_LOCALE}. A download
  * location may be specified in {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}
  * together with an optional http cookie header
@@ -108,7 +114,7 @@
         EXTRA_PROVISIONING_WIFI_PROXY_HOST,
         EXTRA_PROVISIONING_WIFI_PROXY_BYPASS,
         EXTRA_PROVISIONING_WIFI_PAC_URL,
-        EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME,
+        EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
         EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION,
         EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER,
         EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM
@@ -133,6 +139,10 @@
         EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE
     };
 
+    protected static final String[] DEVICE_OWNER_COMPONENT_NAME_EXTRAS = {
+        EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME
+    };
+
     public void addProvisioningParamsToBundle(Bundle bundle, ProvisioningParams params) {
         bundle.putString(EXTRA_PROVISIONING_TIME_ZONE, params.mTimeZone);
         bundle.putString(EXTRA_PROVISIONING_LOCALE, params.getLocaleAsString());
@@ -142,8 +152,8 @@
         bundle.putString(EXTRA_PROVISIONING_WIFI_PROXY_HOST, params.mWifiProxyHost);
         bundle.putString(EXTRA_PROVISIONING_WIFI_PROXY_BYPASS, params.mWifiProxyBypassHosts);
         bundle.putString(EXTRA_PROVISIONING_WIFI_PAC_URL, params.mWifiPacUrl);
-        bundle.putString(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME,
-                params.mDeviceAdminPackageName);
+        bundle.putParcelable(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
+                params.mDeviceAdminComponentName);
         bundle.putString(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION,
                 params.mDeviceAdminPackageDownloadLocation);
         bundle.putString(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER,
@@ -164,17 +174,18 @@
         bundle.putBoolean(EXTRA_PROVISIONING_SKIP_ENCRYPTION, params.mSkipEncryption);
     }
 
-    public ProvisioningParams parseIntent(Intent intent) throws ParseException {
+    public ProvisioningParams parseIntent(Intent intent, Context c)
+            throws IllegalProvisioningArgumentException {
         ProvisionLogger.logi("Processing intent.");
         if (intent.hasExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)) {
-            return parseNfcIntent(intent);
+            return parseNfcIntent(intent, c);
         } else {
-            return parseNonNfcIntent(intent);
+            return parseNonNfcIntent(intent, c);
         }
     }
 
-    public ProvisioningParams parseNfcIntent(Intent nfcIntent)
-        throws ParseException {
+    public ProvisioningParams parseNfcIntent(Intent nfcIntent, Context c)
+            throws IllegalProvisioningArgumentException {
         ProvisionLogger.logi("Processing Nfc Payload.");
         // Only one first message with NFC_MIME_TYPE is used.
         for (Parcelable rawMsg : nfcIntent
@@ -187,21 +198,20 @@
 
             if (MIME_TYPE_PROVISIONING_NFC.equals(mimeType)) {
                 ProvisioningParams params = parseProperties(new String(firstRecord.getPayload()
-                                , UTF_8));
+                                , UTF_8), c);
                 params.mStartedByNfc = true;
                 return params;
             }
         }
-        throw new ParseException(
-                "Intent does not contain NfcRecord with the correct MIME type.",
-                R.string.device_owner_error_general);
+        throw new IllegalProvisioningArgumentException(
+                "Intent does not contain NfcRecord with the correct MIME type.");
     }
 
     // Note: EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE property contains a Properties object
     // serialized into String. See Properties.store() and Properties.load() for more details.
     // The property value is optional.
-    private ProvisioningParams parseProperties(String data)
-            throws ParseException {
+    private ProvisioningParams parseProperties(String data, Context c)
+            throws IllegalProvisioningArgumentException {
         ProvisioningParams params = new ProvisioningParams();
         try {
             Properties props = new Properties();
@@ -219,8 +229,7 @@
             params.mWifiProxyHost = props.getProperty(EXTRA_PROVISIONING_WIFI_PROXY_HOST);
             params.mWifiProxyBypassHosts = props.getProperty(EXTRA_PROVISIONING_WIFI_PROXY_BYPASS);
             params.mWifiPacUrl = props.getProperty(EXTRA_PROVISIONING_WIFI_PAC_URL);
-            params.mDeviceAdminPackageName
-                    = props.getProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
+            params.mDeviceAdminComponentName = findDeviceAdminFromProperties(props, c);
             params.mDeviceAdminPackageDownloadLocation
                     = props.getProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION);
             params.mDeviceAdminPackageDownloadCookieHeader = props.getProperty(
@@ -253,14 +262,11 @@
             checkValidityOfProvisioningParams(params);
             return params;
         } catch (IOException e) {
-            throw new ParseException("Couldn't load payload",
-                    R.string.device_owner_error_general, e);
+            throw new Utils.IllegalProvisioningArgumentException("Couldn't load payload", e);
         } catch (NumberFormatException e) {
-            throw new ParseException("Incorrect numberformat.",
-                    R.string.device_owner_error_general, e);
+            throw new Utils.IllegalProvisioningArgumentException("Incorrect numberformat.", e);
         } catch (IllformedLocaleException e) {
-            throw new ParseException("Invalid locale.",
-                    R.string.device_owner_error_general, e);
+            throw new Utils.IllegalProvisioningArgumentException("Invalid locale.", e);
         }
     }
 
@@ -278,8 +284,8 @@
         }
     }
 
-    public ProvisioningParams parseNonNfcIntent(Intent intent)
-        throws ParseException {
+    public ProvisioningParams parseNonNfcIntent(Intent intent, Context c)
+            throws IllegalProvisioningArgumentException {
         ProvisionLogger.logi("Processing intent.");
         ProvisioningParams params = new ProvisioningParams();
 
@@ -294,8 +300,7 @@
         params.mWifiProxyHost = intent.getStringExtra(EXTRA_PROVISIONING_WIFI_PROXY_HOST);
         params.mWifiProxyBypassHosts = intent.getStringExtra(EXTRA_PROVISIONING_WIFI_PROXY_BYPASS);
         params.mWifiPacUrl = intent.getStringExtra(EXTRA_PROVISIONING_WIFI_PAC_URL);
-        params.mDeviceAdminPackageName
-                = intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
+        params.mDeviceAdminComponentName = Utils.findDeviceAdminFromIntent(intent, c);
         params.mDeviceAdminPackageDownloadLocation
                 = intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION);
         params.mDeviceAdminPackageDownloadCookieHeader = intent.getStringExtra(
@@ -326,58 +331,42 @@
             params.mAdminExtrasBundle = (PersistableBundle) intent.getParcelableExtra(
                     EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE);
         } catch (ClassCastException e) {
-            throw new ParseException("Extra " + EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE
-                    + " must be of type PersistableBundle.",
-                    R.string.device_owner_error_general, e);
+            throw new IllegalProvisioningArgumentException("Extra "
+                    + EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE
+                    + " must be of type PersistableBundle.", e);
         }
 
         checkValidityOfProvisioningParams(params);
         return params;
     }
 
+    private ComponentName findDeviceAdminFromProperties(Properties props, Context c)
+            throws IllegalProvisioningArgumentException {
+        String packageName = props.getProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
+        ComponentName component = null;
+        String s;
+        if ((s = props.getProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME)) != null) {
+            component = ComponentName.unflattenFromString(s);
+        }
+        return Utils.findDeviceAdmin(packageName, component, c);
+    }
+
     /**
      * Check whether necessary fields are set.
      */
     private void checkValidityOfProvisioningParams(ProvisioningParams params)
-        throws ParseException  {
-        if (TextUtils.isEmpty(params.mDeviceAdminPackageName)) {
-            throw new ParseException("Must provide the name of the device admin package.",
-                    R.string.device_owner_error_general);
-        }
+            throws IllegalProvisioningArgumentException  {
+        // The presence of the device admin component name was already checked when calling
+        // Utils.findDeviceAdmin()
         if (!TextUtils.isEmpty(params.mDeviceAdminPackageDownloadLocation)) {
             if (params.mDeviceAdminPackageChecksum == null ||
                     params.mDeviceAdminPackageChecksum.length == 0) {
-                throw new ParseException("Checksum of installer file is required for downloading " +
-                        "device admin file, but not provided.",
-                        R.string.device_owner_error_general);
+                throw new IllegalProvisioningArgumentException("Checksum of installer file is"
+                        + " required for downloading device admin file, but not provided.");
             }
         }
     }
 
-    /**
-     * Exception thrown when the ProvisioningParams initialization failed completely.
-     *
-     * Note: We're using a custom exception to avoid catching subsequent exceptions that might be
-     * significant.
-     */
-    public static class ParseException extends Exception {
-        private int mErrorMessageId;
-
-        public ParseException(String message, int errorMessageId) {
-            super(message);
-            mErrorMessageId = errorMessageId;
-        }
-
-        public ParseException(String message, int errorMessageId, Throwable t) {
-            super(message, t);
-            mErrorMessageId = errorMessageId;
-        }
-
-        public int getErrorMessageId() {
-            return mErrorMessageId;
-        }
-    }
-
     public static byte[] stringToByteArray(String s)
         throws NumberFormatException {
         try {
diff --git a/src/com/android/managedprovisioning/ProfileOwnerPreProvisioningActivity.java b/src/com/android/managedprovisioning/ProfileOwnerPreProvisioningActivity.java
index 5ad071e..efac6e0 100644
--- a/src/com/android/managedprovisioning/ProfileOwnerPreProvisioningActivity.java
+++ b/src/com/android/managedprovisioning/ProfileOwnerPreProvisioningActivity.java
@@ -17,7 +17,7 @@
 package com.android.managedprovisioning;
 
 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;
-import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME;
 import static com.android.managedprovisioning.EncryptDeviceActivity.EXTRA_RESUME;
 import static com.android.managedprovisioning.EncryptDeviceActivity.EXTRA_RESUME_TARGET;
 import static com.android.managedprovisioning.EncryptDeviceActivity.TARGET_PROFILE_OWNER;
@@ -29,7 +29,9 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -49,6 +51,7 @@
 import android.widget.TextView;
 import android.widget.Button;
 
+import com.android.managedprovisioning.Utils.IllegalProvisioningArgumentException;
 import com.android.setupwizard.navigationbar.SetupWizardNavBar;
 import com.android.setupwizard.navigationbar.SetupWizardNavBar.NavigationBarListener;
 
@@ -86,6 +89,8 @@
 
     private String mMdmPackageName;
 
+    private ComponentName mMdmComponentName;
+
     private Button mSetupButton;
 
     @Override
@@ -115,7 +120,7 @@
         // Initialize member variables from the intent, stop if the intent wasn't valid.
         try {
             initialize(getIntent());
-        } catch (ProvisioningFailedException e) {
+        } catch (IllegalProvisioningArgumentException e) {
             showErrorAndClose(R.string.managed_provisioning_error_text, e.getMessage());
             return;
         }
@@ -239,30 +244,20 @@
      *
      * @param intent The intent that started provisioning
      */
-    private void initialize(Intent intent) throws ProvisioningFailedException {
+    private void initialize(Intent intent) throws IllegalProvisioningArgumentException {
         // Check if the admin extras bundle is of the right type.
         try {
             PersistableBundle bundle = (PersistableBundle) getIntent().getParcelableExtra(
                     EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE);
         } catch (ClassCastException e) {
-            throw new ProvisioningFailedException("Extra "
+            throw new IllegalProvisioningArgumentException("Extra "
                     + EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE
                     + " must be of type PersistableBundle.", e);
         }
 
-        // Validate package name and check if the package is installed
-        mMdmPackageName = intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
-        if (TextUtils.isEmpty(mMdmPackageName)) {
-            throw new ProvisioningFailedException("Missing intent extra: "
-                    + EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
-        } else {
-            try {
-                this.getPackageManager().getPackageInfo(mMdmPackageName, 0);
-            } catch (NameNotFoundException e) {
-                throw new ProvisioningFailedException("Mdm "+ mMdmPackageName
-                        + " is not installed. ", e);
-            }
-        }
+        mMdmComponentName = Utils.findDeviceAdminFromIntent(intent, this);
+        mMdmPackageName = mMdmComponentName.getPackageName();
+
     }
 
     /**
@@ -278,7 +273,7 @@
             UserConsentDialog.newInstance(UserConsentDialog.PROFILE_OWNER)
                     .show(getFragmentManager(), "UserConsentDialogFragment");
         } else {
-            Bundle resumeExtras = getIntent().getExtras();
+            Bundle resumeExtras = getNewExtras();
             resumeExtras.putString(EXTRA_RESUME_TARGET, TARGET_PROFILE_OWNER);
             Intent encryptIntent = new Intent(this, EncryptDeviceActivity.class)
                     .putExtra(EXTRA_RESUME, resumeExtras);
@@ -318,12 +313,21 @@
 
     private void startProfileOwnerProvisioning() {
         Intent intent = new Intent(this, ProfileOwnerProvisioningActivity.class);
-        intent.putExtras(getIntent());
+        intent.putExtras(getNewExtras());
         startActivityForResult(intent, PROVISIONING_REQUEST_CODE);
         // Set cross-fade transition animation into the interstitial progress activity.
         overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
     }
 
+    private Bundle getNewExtras() {
+        Bundle bundle = getIntent().getExtras();
+        // The original intent may have contained the package name but not the component name.
+        // But now, we know what the component name is. So let's pass it.
+        bundle.putParcelable(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
+                mMdmComponentName);
+        return bundle;
+    }
+
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         if (requestCode == ENCRYPT_DEVICE_REQUEST_CODE) {
@@ -437,22 +441,6 @@
                 .getWindow().getDecorView().setSystemUiVisibility(IMMERSIVE_FLAGS);
     }
 
-    /**
-     * Exception thrown when the provisioning has failed completely.
-     *
-     * We're using a custom exception to avoid catching subsequent exceptions that might be
-     * significant.
-     */
-    private class ProvisioningFailedException extends Exception {
-        public ProvisioningFailedException(String message) {
-            super(message);
-        }
-
-        public ProvisioningFailedException(String message, Throwable t) {
-            super(message, t);
-        }
-    }
-
     @Override
     public void onNavigationBarCreated(SetupWizardNavBar bar) {
         mSetupButton = bar.getNextButton();
diff --git a/src/com/android/managedprovisioning/ProfileOwnerProvisioningService.java b/src/com/android/managedprovisioning/ProfileOwnerProvisioningService.java
index e7d22f2..921c699 100644
--- a/src/com/android/managedprovisioning/ProfileOwnerProvisioningService.java
+++ b/src/com/android/managedprovisioning/ProfileOwnerProvisioningService.java
@@ -19,7 +19,7 @@
 import static android.app.admin.DeviceAdminReceiver.ACTION_PROFILE_PROVISIONING_COMPLETE;
 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE;
 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;
-import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME;
 import static android.Manifest.permission.BIND_DEVICE_ADMIN;
 
 import android.accounts.Account;
@@ -231,8 +231,10 @@
         }
     }
 
-    private void initialize(Intent intent) throws ProvisioningException {
-        mMdmPackageName = intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
+    private void initialize(Intent intent) {
+        mActiveAdminComponentName = (ComponentName) intent.getParcelableExtra(
+                EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME);
+        mMdmPackageName = mActiveAdminComponentName.getPackageName();
         mAccountToMigrate = (Account) intent.getParcelableExtra(
                 EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE);
         if (mAccountToMigrate != null) {
@@ -242,31 +244,6 @@
         // Cast is guaranteed by check in Activity.
         mAdminExtrasBundle  = (PersistableBundle) intent.getParcelableExtra(
                 EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE);
-
-        mActiveAdminComponentName = getAdminReceiverComponent(mMdmPackageName);
-    }
-
-    /**
-     * Find the Device admin receiver component from the manifest.
-     */
-    private ComponentName getAdminReceiverComponent(String packageName)
-            throws ProvisioningException {
-        try {
-            PackageInfo pi = getPackageManager().getPackageInfo(packageName,
-                    PackageManager.GET_RECEIVERS);
-            for (ActivityInfo ai : pi.receivers) {
-                if (!TextUtils.isEmpty(ai.permission) &&
-                        ai.permission.equals(BIND_DEVICE_ADMIN)) {
-                    return new ComponentName(packageName, ai.name);
-                }
-            }
-
-            throw raiseError("Didn't find admin receiver component with BIND_DEVICE_ADMIN "
-                    + "permissions");
-        } catch (NameNotFoundException e) {
-            throw raiseError("Error: The provided mobile device management package does not define "
-                    + "a device admin receiver component in its manifest.");
-        }
     }
 
     /**
diff --git a/src/com/android/managedprovisioning/ProvisioningParams.java b/src/com/android/managedprovisioning/ProvisioningParams.java
index cd6374a..16238a6 100644
--- a/src/com/android/managedprovisioning/ProvisioningParams.java
+++ b/src/com/android/managedprovisioning/ProvisioningParams.java
@@ -16,13 +16,13 @@
 
 package com.android.managedprovisioning;
 
+import android.content.ComponentName;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
 import android.util.Base64;
 import java.util.Locale;
 
-
 /**
  * Provisioning Parameters for DeviceOwner Provisioning
  */
@@ -46,7 +46,7 @@
     public String mWifiProxyBypassHosts;
     public String mWifiPacUrl;
 
-    public String mDeviceAdminPackageName; // Package name of the device admin package.
+    public ComponentName mDeviceAdminComponentName;
 
     public String mDeviceAdminPackageDownloadLocation; // Url of the device admin .apk
     public String mDeviceAdminPackageDownloadCookieHeader; // Cookie header for http request
@@ -59,6 +59,10 @@
     public boolean mLeaveAllSystemAppsEnabled;
     public boolean mSkipEncryption;
 
+    public String getDeviceAdminPackageName() {
+        return mDeviceAdminComponentName.getPackageName();
+    }
+
     public String getLocaleAsString() {
         if (mLocale != null) {
             return mLocale.getLanguage() + "_" + mLocale.getCountry();
@@ -89,7 +93,7 @@
         out.writeString(mWifiProxyHost);
         out.writeInt(mWifiProxyPort);
         out.writeString(mWifiProxyBypassHosts);
-        out.writeString(mDeviceAdminPackageName);
+        out.writeParcelable(mDeviceAdminComponentName, 0 /* default */);
         out.writeString(mDeviceAdminPackageDownloadLocation);
         out.writeString(mDeviceAdminPackageDownloadCookieHeader);
         out.writeInt(mDeviceAdminPackageChecksum.length);
@@ -115,7 +119,8 @@
             params.mWifiProxyHost = in.readString();
             params.mWifiProxyPort = in.readInt();
             params.mWifiProxyBypassHosts = in.readString();
-            params.mDeviceAdminPackageName = in.readString();
+            params.mDeviceAdminComponentName = (ComponentName)
+                    in.readParcelable(null /* use default classloader */);
             params.mDeviceAdminPackageDownloadLocation = in.readString();
             params.mDeviceAdminPackageDownloadCookieHeader = in.readString();
             int checksumLength = in.readInt();
diff --git a/src/com/android/managedprovisioning/Utils.java b/src/com/android/managedprovisioning/Utils.java
index a74fdf3..bd05bc5 100644
--- a/src/com/android/managedprovisioning/Utils.java
+++ b/src/com/android/managedprovisioning/Utils.java
@@ -16,12 +16,21 @@
 
 package com.android.managedprovisioning;
 
-import android.content.ComponentName;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME;
+import static  android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.ComponentName;
+import android.content.Intent;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.text.TextUtils;
 
 import java.util.List;
 import java.util.HashSet;
@@ -65,4 +74,104 @@
                 + toDisable.toShortString());
         }
     }
+
+    public static ComponentName findDeviceAdminFromIntent(Intent intent, Context c)
+            throws IllegalProvisioningArgumentException {
+        ComponentName mdmComponentName = intent.getParcelableExtra(
+                EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME);
+        String mdmPackageName = intent.getStringExtra(
+                EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
+        return findDeviceAdmin(mdmPackageName, mdmComponentName, c);
+    }
+
+    /**
+     * Exception thrown when the provisioning has failed completely.
+     *
+     * We're using a custom exception to avoid catching subsequent exceptions that might be
+     * significant.
+     */
+    public static class IllegalProvisioningArgumentException extends Exception {
+        public IllegalProvisioningArgumentException(String message) {
+            super(message);
+        }
+
+        public IllegalProvisioningArgumentException(String message, Throwable t) {
+            super(message, t);
+        }
+    }
+
+    /**
+     * Check the validity of the admin component name supplied, or try to infer this componentName
+     * from the package.
+     *
+     * We are supporting lookup by package name for legacy reasons.
+     *
+     * If mdmComponentName is supplied (not null):
+     * mdmPackageName is ignored.
+     * Check that the package of mdmComponentName is installed, that mdmComponentName is a
+     * receiver in this package, and return it.
+     *
+     * Otherwise:
+     * mdmPackageName must be supplied (not null).
+     * Check that this package is installed, try to infer a potential device admin in this package,
+     * and return it.
+     */
+    public static ComponentName findDeviceAdmin(String mdmPackageName,
+            ComponentName mdmComponentName, Context c) throws IllegalProvisioningArgumentException {
+        if (mdmComponentName != null) {
+            mdmPackageName = mdmComponentName.getPackageName();
+        }
+        if (mdmPackageName == null) {
+            throw new IllegalProvisioningArgumentException("Neither the package name nor the"
+                    + " component name of the admin are supplied");
+        }
+        PackageInfo pi;
+        try {
+            pi = c.getPackageManager().getPackageInfo(mdmPackageName,
+                    PackageManager.GET_RECEIVERS);
+        } catch (NameNotFoundException e) {
+            throw new IllegalProvisioningArgumentException("Mdm "+ mdmPackageName
+                    + " is not installed. ", e);
+        }
+        if (mdmComponentName != null) {
+            // If the component was specified in the intent: check that it is in the manifest.
+            checkAdminComponent(mdmComponentName, pi);
+            return mdmComponentName;
+        } else {
+            // Otherwise: try to find a potential device admin in the manifest.
+            return findDeviceAdminInPackage(mdmPackageName, pi);
+        }
+    }
+
+    private static void checkAdminComponent(ComponentName mdmComponentName, PackageInfo pi)
+            throws IllegalProvisioningArgumentException{
+        for (ActivityInfo ai : pi.receivers) {
+            if (mdmComponentName.getClassName().equals(ai.name)) {
+                return;
+            }
+        }
+        throw new IllegalProvisioningArgumentException("The component " + mdmComponentName
+                + " cannot be found");
+    }
+
+    private static ComponentName findDeviceAdminInPackage(String mdmPackageName, PackageInfo pi)
+            throws IllegalProvisioningArgumentException {
+        ComponentName mdmComponentName = null;
+        for (ActivityInfo ai : pi.receivers) {
+            if (!TextUtils.isEmpty(ai.permission) &&
+                    ai.permission.equals(android.Manifest.permission.BIND_DEVICE_ADMIN)) {
+                if (mdmComponentName != null) {
+                    throw new IllegalProvisioningArgumentException("There are several "
+                            + "device admins in " + mdmPackageName + " but no one in specified");
+                } else {
+                    mdmComponentName = new ComponentName(mdmPackageName, ai.name);
+                }
+            }
+        }
+        if (mdmComponentName == null) {
+            throw new IllegalProvisioningArgumentException("There are no device admins in"
+                    + mdmPackageName);
+        }
+        return mdmComponentName;
+    }
 }
diff --git a/src/com/android/managedprovisioning/task/SetDevicePolicyTask.java b/src/com/android/managedprovisioning/task/SetDevicePolicyTask.java
index 7fe13bd..29b8a96 100644
--- a/src/com/android/managedprovisioning/task/SetDevicePolicyTask.java
+++ b/src/com/android/managedprovisioning/task/SetDevicePolicyTask.java
@@ -35,18 +35,19 @@
 
     private final Callback mCallback;
     private final Context mContext;
-    private final String mPackageName;
+    private final String mAdminPackage;
+    private final ComponentName mAdminComponent;
     private final String mOwner;
 
-    private String mAdminReceiver;
     private PackageManager mPackageManager;
     private DevicePolicyManager mDevicePolicyManager;
 
-    public SetDevicePolicyTask(Context context, String packageName, String owner,
+    public SetDevicePolicyTask(Context context, ComponentName adminComponent, String owner,
             Callback callback) {
         mCallback = callback;
         mContext = context;
-        mPackageName = packageName;
+        mAdminComponent = adminComponent;
+        mAdminPackage = adminComponent.getPackageName();
         mOwner = owner;
         mPackageManager = mContext.getPackageManager();
         mDevicePolicyManager = (DevicePolicyManager) mContext.
@@ -54,53 +55,30 @@
     }
 
     public void run() {
-        // Check whether package is installed and find the admin receiver.
-        if (isPackageInstalled()) {
-            enableDevicePolicyApp();
-            setActiveAdmin();
-            setDeviceOwner();
-            mCallback.onSuccess();
-        }
-    }
-
-    private boolean isPackageInstalled() {
-        try {
-            PackageInfo pi = mPackageManager.getPackageInfo(mPackageName,
-                    PackageManager.GET_RECEIVERS);
-            for (ActivityInfo ai : pi.receivers) {
-                if (!TextUtils.isEmpty(ai.permission) &&
-                        ai.permission.equals(android.Manifest.permission.BIND_DEVICE_ADMIN)) {
-                    mAdminReceiver = ai.name;
-                    return true;
-                }
-            }
-            mCallback.onError(ERROR_NO_RECEIVER);
-            return false;
-        } catch (NameNotFoundException e) {
-            mCallback.onError(ERROR_PACKAGE_NOT_INSTALLED);
-            return false;
-        }
+        enableDevicePolicyApp();
+        setActiveAdmin();
+        setDeviceOwner();
+        mCallback.onSuccess();
     }
 
     private void enableDevicePolicyApp() {
         int enabledSetting = mPackageManager
-                .getApplicationEnabledSetting(mPackageName);
+                .getApplicationEnabledSetting(mAdminPackage);
         if (enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
-            mPackageManager.setApplicationEnabledSetting(mPackageName,
+            mPackageManager.setApplicationEnabledSetting(mAdminPackage,
                     PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 0);
         }
     }
 
     public void setActiveAdmin() {
-        ProvisionLogger.logd("Setting " + mPackageName + " as active admin.");
-        ComponentName component = new ComponentName(mPackageName, mAdminReceiver);
-        mDevicePolicyManager.setActiveAdmin(component, true);
+        ProvisionLogger.logd("Setting " + mAdminComponent + " as active admin.");
+        mDevicePolicyManager.setActiveAdmin(mAdminComponent, true);
     }
 
     public void setDeviceOwner() {
-        ProvisionLogger.logd("Setting " + mPackageName + " as device owner " + mOwner + ".");
-        if (!mDevicePolicyManager.isDeviceOwner(mPackageName)) {
-            mDevicePolicyManager.setDeviceOwner(mPackageName, mOwner);
+        ProvisionLogger.logd("Setting " + mAdminPackage + " as device owner " + mOwner + ".");
+        if (!mDevicePolicyManager.isDeviceOwner(mAdminPackage)) {
+            mDevicePolicyManager.setDeviceOwner(mAdminPackage, mOwner);
         }
     }