Merge "Allow to specify the admin component for provisioning."
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 0c538f9..ecd32f3 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);
         }
     }