Merge "Support launching a device initialization agent during provisioning."
diff --git a/src/com/android/managedprovisioning/DeviceOwnerProvisioningActivity.java b/src/com/android/managedprovisioning/DeviceOwnerProvisioningActivity.java
index 0121780..78c5557 100644
--- a/src/com/android/managedprovisioning/DeviceOwnerProvisioningActivity.java
+++ b/src/com/android/managedprovisioning/DeviceOwnerProvisioningActivity.java
@@ -16,6 +16,9 @@
 
 package com.android.managedprovisioning;
 
+import static android.app.admin.DeviceAdminReceiver.ACTION_READY_FOR_USER_INITIALIZATION;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;
+
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.content.BroadcastReceiver;
@@ -23,6 +26,7 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ResolveInfo;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.SystemProperties;
@@ -36,11 +40,11 @@
 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;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * This activity starts device owner provisioning:
@@ -261,17 +265,43 @@
 
 
     private void onProvisioningSuccess() {
-        // The Setup wizards listens to this flag and finishes itself when it is set.
-        // It then fires a home intent, which we catch in the HomeReceiverActivity before sending
-        // the intent to notify the mdm that provisioning is complete.
-        Global.putInt(getContentResolver(), Global.DEVICE_PROVISIONED, 1);
-        Secure.putInt(getContentResolver(), Secure.USER_SETUP_COMPLETE, 1);
-
+        if (mParams.mDeviceInitializerComponentName != null) {
+            Intent result = new Intent(ACTION_READY_FOR_USER_INITIALIZATION);
+            result.setComponent(mParams.mDeviceInitializerComponentName);
+            result.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES |
+                    Intent.FLAG_RECEIVER_FOREGROUND);
+            if (mParams.mAdminExtrasBundle != null) {
+                result.putExtra(EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE,
+                        mParams.mAdminExtrasBundle);
+            }
+            List<ResolveInfo> matchingReceivers =
+                    getPackageManager().queryBroadcastReceivers(result, 0);
+            if (matchingReceivers.size() > 0) {
+                // Notify the device initializer that it can now perform pre-user-setup tasks.
+                sendBroadcast(result);
+            } else {
+                ProvisionLogger.logi("Initializer component doesn't have a receiver for "
+                        + "android.app.action.READY_FOR_USER_INITIALIZATION. Skipping broadcast "
+                        + "and finishing user initialization.");
+                provisionDevice();
+            }
+        } else {
+            // No initializer, set the device provisioned ourselves.
+            provisionDevice();
+        }
         // Note: the DeviceOwnerProvisioningService will stop itself.
         setResult(Activity.RESULT_OK);
         finish();
     }
 
+    private void provisionDevice() {
+        // The Setup wizards listens to this flag and finishes itself when it is set.
+        // It then fires a home intent, which we catch in the HomeReceiverActivity before
+        // sending the intent to notify the mdm that provisioning is complete.
+        Global.putInt(getContentResolver(), Global.DEVICE_PROVISIONED, 1);
+        Secure.putInt(getContentResolver(), Secure.USER_SETUP_COMPLETE, 1);
+    }
+
     private void requestEncryption(MessageParser messageParser, ProvisioningParams params) {
         Intent encryptIntent = new Intent(DeviceOwnerProvisioningActivity.this,
                 EncryptDeviceActivity.class);
diff --git a/src/com/android/managedprovisioning/DeviceOwnerProvisioningService.java b/src/com/android/managedprovisioning/DeviceOwnerProvisioningService.java
index 3d5dd54..f5913e4 100644
--- a/src/com/android/managedprovisioning/DeviceOwnerProvisioningService.java
+++ b/src/com/android/managedprovisioning/DeviceOwnerProvisioningService.java
@@ -194,7 +194,6 @@
         }
     }
 
-
     /**
      * This is the core method of this class. It goes through every provisioning step.
      * Each task checks if it is required and executes if it is.
@@ -203,30 +202,33 @@
         if (DEBUG) ProvisionLogger.logd("Starting device owner provisioning");
 
         // Construct Tasks. Do not start them yet.
-        mAddWifiNetworkTask = new AddWifiNetworkTask(this, params.mWifiSsid,
-                params.mWifiHidden, params.mWifiSecurityType, params.mWifiPassword,
-                params.mWifiProxyHost, params.mWifiProxyPort, params.mWifiProxyBypassHosts,
-                params.mWifiPacUrl, new AddWifiNetworkTask.Callback() {
+       mAddWifiNetworkTask = new AddWifiNetworkTask(this, params.mWifiSsid,
+               params.mWifiHidden, params.mWifiSecurityType, params.mWifiPassword,
+               params.mWifiProxyHost, params.mWifiProxyPort, params.mWifiProxyBypassHosts,
+               params.mWifiPacUrl, new AddWifiNetworkTask.Callback() {
+                       @Override
+                       public void onSuccess() {
+                           progressUpdate(R.string.progress_download);
+                           mDownloadPackageTask.run();
+                       }
+
+                       @Override
+                       public void onError(){
+                           error(R.string.device_owner_error_wifi);
+                           }
+                       });
+
+
+        mDownloadPackageTask = new DownloadPackageTask(
+                this, params, new DownloadPackageTask.Callback() {
                         @Override
                         public void onSuccess() {
-                            mDownloadPackageTask.run();
-                        }
-
-                        @Override
-                        public void onError(){
-                            error(R.string.device_owner_error_wifi);
-                        }
-                    });
-
-        mDownloadPackageTask = new DownloadPackageTask(this,
-                params.mDeviceAdminPackageDownloadLocation, params.mDeviceAdminPackageChecksum,
-                params.mDeviceAdminPackageDownloadCookieHeader, new DownloadPackageTask.Callback() {
-                        @Override
-                        public void onSuccess() {
-                            String downloadLocation =
-                                    mDownloadPackageTask.getDownloadedPackageLocation();
                             progressUpdate(R.string.progress_install);
-                            mInstallPackageTask.run(downloadLocation);
+                            mInstallPackageTask.run(
+                                    mDownloadPackageTask.getDownloadedPackageLocation(
+                                            DownloadPackageTask.DEVICE_OWNER),
+                                    mDownloadPackageTask.getDownloadedPackageLocation(
+                                            DownloadPackageTask.INITIALIZER));
                         }
 
                         @Override
@@ -245,9 +247,8 @@
                         }
                     });
 
-        mInstallPackageTask = new InstallPackageTask(this,
-                params.inferDeviceAdminPackageName(), params.mDeviceAdminPackageDownloadLocation,
-                new InstallPackageTask.Callback() {
+        mInstallPackageTask = new InstallPackageTask(
+                this, params, new InstallPackageTask.Callback() {
                     @Override
                     public void onSuccess() {
                         progressUpdate(R.string.progress_set_owner);
@@ -281,6 +282,8 @@
                 });
         mSetDevicePolicyTask = new SetDevicePolicyTask(this,
                 getResources().getString(R.string.default_owned_device_username),
+                params.mDeviceInitializerComponentName,
+                getResources().getString(R.string.default_owned_device_username),
                 new SetDevicePolicyTask.Callback() {
                     @Override
                     public void onSuccess() {
diff --git a/src/com/android/managedprovisioning/MessageParser.java b/src/com/android/managedprovisioning/MessageParser.java
index 2e8e09e..26fce8c 100644
--- a/src/com/android/managedprovisioning/MessageParser.java
+++ b/src/com/android/managedprovisioning/MessageParser.java
@@ -33,6 +33,11 @@
 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_NAME;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_COOKIE_HEADER;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_CHECKSUM;
 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;
@@ -77,17 +82,25 @@
  * <p>
  * Intent was received directly.
  * 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
- * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER} accompanied by the SHA-1
- * sum of the target file {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}.
- * Additional information to send through to the device admin may be specified in
+ * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} (which is deprecated and supported for
+ * legacy reasons only), and may contain {@link #EXTRA_PROVISIONING_TIME_ZONE},
+ * {@link #EXTRA_PROVISIONING_LOCAL_TIME}, {@link #EXTRA_PROVISIONING_LOCALE}, and
+ * {@link #EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME}. A download
+ * location for the device admin may be specified in
+ * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION} together with an optional
+ * http cookie header {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER}
+ * accompanied by the SHA-1 sum of the target file
+ * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}. A download location for the device
+ * initializer may be specified in
+ * {@link #EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION} together with an
+ * optional http cookie header
+ * {@link #EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_COOKIE_HEADER} accompanied by the
+ * SHA-1 sum of the target file {@link #EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_CHECKSUM}.
+ * Additional information to send through to the device initializer and admin may be specified in
  * {@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}.
- * The boolean {@link #EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED} indicates wheter system
- * apps should not be disabled.
+ * The optional boolean {@link #EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED} indicates whether
+ * system apps should not be disabled. The optional boolean
+ * {@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION} specifies whether the device should be encrypted.
  * Furthermore a wifi network may be specified in {@link #EXTRA_PROVISIONING_WIFI_SSID}, and if
  * applicable {@link #EXTRA_PROVISIONING_WIFI_HIDDEN},
  * {@link #EXTRA_PROVISIONING_WIFI_SECURITY_TYPE}, {@link #EXTRA_PROVISIONING_WIFI_PASSWORD},
@@ -117,7 +130,10 @@
         EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME,
         EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION,
         EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER,
-        EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM
+        EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM,
+        EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION,
+        EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_COOKIE_HEADER,
+        EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_CHECKSUM
     };
 
     protected static final String[] DEVICE_OWNER_LONG_EXTRAS = {
@@ -140,7 +156,8 @@
     };
 
     protected static final String[] DEVICE_OWNER_COMPONENT_NAME_EXTRAS = {
-        EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME
+        EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
+        EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME
     };
 
     public void addProvisioningParamsToBundle(Bundle bundle, ProvisioningParams params) {
@@ -162,6 +179,14 @@
                 params.mDeviceAdminPackageDownloadCookieHeader);
         bundle.putString(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM,
                 params.getDeviceAdminPackageChecksumAsString());
+        bundle.putParcelable(EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME,
+                params.mDeviceInitializerComponentName);
+        bundle.putString(EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION,
+                params.mDeviceInitializerPackageDownloadLocation);
+        bundle.putString(EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_COOKIE_HEADER,
+                params.mDeviceInitializerPackageDownloadCookieHeader);
+        bundle.putString(EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_CHECKSUM,
+                params.getDeviceInitializerPackageChecksumAsString());
 
         bundle.putLong(EXTRA_PROVISIONING_LOCAL_TIME, params.mLocalTime);
 
@@ -246,6 +271,19 @@
             if ((s = props.getProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM)) != null) {
                 params.mDeviceAdminPackageChecksum = stringToByteArray(s);
             }
+            String name = props.getProperty(
+                    EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME);
+            if (name != null) {
+                params.mDeviceInitializerComponentName = ComponentName.unflattenFromString(name);
+            }
+            params.mDeviceInitializerPackageDownloadLocation = props.getProperty(
+                    EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION);
+            params.mDeviceInitializerPackageDownloadCookieHeader = props.getProperty(
+                    EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_COOKIE_HEADER);
+            if ((s = props.getProperty(
+                    EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_CHECKSUM)) != null) {
+                params.mDeviceInitializerPackageChecksum = stringToByteArray(s);
+            }
 
             if ((s = props.getProperty(EXTRA_PROVISIONING_LOCAL_TIME)) != null) {
                 params.mLocalTime = Long.parseLong(s);
@@ -321,6 +359,19 @@
         if (hashString != null) {
             params.mDeviceAdminPackageChecksum = stringToByteArray(hashString);
         }
+        String name = intent.getStringExtra(
+                EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME);
+        if (name != null) {
+            params.mDeviceInitializerComponentName = ComponentName.unflattenFromString(name);
+        }
+        params.mDeviceInitializerPackageDownloadLocation = intent.getStringExtra(
+                EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION);
+        params.mDeviceInitializerPackageDownloadCookieHeader = intent.getStringExtra(
+                EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_COOKIE_HEADER);
+        hashString = intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_CHECKSUM);
+        if (hashString != null) {
+            params.mDeviceInitializerPackageChecksum = stringToByteArray(hashString);
+        }
 
         params.mLocalTime = intent.getLongExtra(EXTRA_PROVISIONING_LOCAL_TIME,
                 ProvisioningParams.DEFAULT_LOCAL_TIME);
diff --git a/src/com/android/managedprovisioning/ProvisioningParams.java b/src/com/android/managedprovisioning/ProvisioningParams.java
index dd7a089..2d833dd 100644
--- a/src/com/android/managedprovisioning/ProvisioningParams.java
+++ b/src/com/android/managedprovisioning/ProvisioningParams.java
@@ -49,10 +49,28 @@
     // At least one one of mDeviceAdminPackageName and mDeviceAdminComponentName should be non-null
     public String mDeviceAdminPackageName; // Package name of the device admin package.
     public ComponentName mDeviceAdminComponentName;
+    public ComponentName mDeviceInitializerComponentName;
 
     private ComponentName mInferedDeviceAdminComponentName;
 
-    String inferDeviceAdminPackageName() {
+    public String mDeviceAdminPackageDownloadLocation; // Url of the device admin .apk
+    public String mDeviceAdminPackageDownloadCookieHeader; // Cookie header for http request
+    public byte[] mDeviceAdminPackageChecksum = new byte[0]; // SHA-1 sum of the .apk file.
+
+    public String mDeviceInitializerPackageDownloadLocation; // Url of the device initializer .apk.
+    // Cookie header for initializer http request.
+    public String mDeviceInitializerPackageDownloadCookieHeader;
+    // SHA-1 sum of the initializer .apk file.
+    public byte[] mDeviceInitializerPackageChecksum = new byte[0];
+
+    public PersistableBundle mAdminExtrasBundle;
+
+    public boolean mStartedByNfc; // True iff provisioning flow was started by Nfc bump.
+
+    public boolean mLeaveAllSystemAppsEnabled;
+    public boolean mSkipEncryption;
+
+    public String inferDeviceAdminPackageName() {
         if (mDeviceAdminComponentName != null) {
             return mDeviceAdminComponentName.getPackageName();
         }
@@ -69,17 +87,6 @@
         return mInferedDeviceAdminComponentName;
     }
 
-    public String mDeviceAdminPackageDownloadLocation; // Url of the device admin .apk
-    public String mDeviceAdminPackageDownloadCookieHeader; // Cookie header for http request
-    public byte[] mDeviceAdminPackageChecksum = new byte[0]; // SHA-1 sum of the .apk file.
-
-    public PersistableBundle mAdminExtrasBundle;
-
-    public boolean mStartedByNfc; // True iff provisioning flow was started by Nfc bump.
-
-    public boolean mLeaveAllSystemAppsEnabled;
-    public boolean mSkipEncryption;
-
     public String getLocaleAsString() {
         if (mLocale != null) {
             return mLocale.getLanguage() + "_" + mLocale.getCountry();
@@ -93,6 +100,11 @@
                 Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
     }
 
+    public String getDeviceInitializerPackageChecksumAsString() {
+        return Base64.encodeToString(mDeviceInitializerPackageChecksum,
+                Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -116,6 +128,11 @@
         out.writeString(mDeviceAdminPackageDownloadCookieHeader);
         out.writeInt(mDeviceAdminPackageChecksum.length);
         out.writeByteArray(mDeviceAdminPackageChecksum);
+        out.writeParcelable(mDeviceInitializerComponentName, 0 /* default */);
+        out.writeString(mDeviceInitializerPackageDownloadLocation);
+        out.writeString(mDeviceInitializerPackageDownloadCookieHeader);
+        out.writeInt(mDeviceInitializerPackageChecksum.length);
+        out.writeByteArray(mDeviceInitializerPackageChecksum);
         out.writeParcelable(mAdminExtrasBundle, 0 /* default */);
         out.writeInt(mStartedByNfc ? 1 : 0);
         out.writeInt(mLeaveAllSystemAppsEnabled ? 1 : 0);
@@ -145,6 +162,13 @@
             int checksumLength = in.readInt();
             params.mDeviceAdminPackageChecksum = new byte[checksumLength];
             in.readByteArray(params.mDeviceAdminPackageChecksum);
+            params.mDeviceInitializerComponentName = (ComponentName)
+                    in.readParcelable(null /* use default classloader */);
+            params.mDeviceInitializerPackageDownloadLocation = in.readString();
+            params.mDeviceInitializerPackageDownloadCookieHeader = in.readString();
+            checksumLength = in.readInt();
+            params.mDeviceInitializerPackageChecksum = new byte[checksumLength];
+            in.readByteArray(params.mDeviceInitializerPackageChecksum);
             params.mAdminExtrasBundle = in.readParcelable(null /* use default classloader */);
             params.mStartedByNfc = in.readInt() == 1;
             params.mLeaveAllSystemAppsEnabled = in.readInt() == 1;
diff --git a/src/com/android/managedprovisioning/task/DownloadPackageTask.java b/src/com/android/managedprovisioning/task/DownloadPackageTask.java
index 38ef5f1..79185b2 100644
--- a/src/com/android/managedprovisioning/task/DownloadPackageTask.java
+++ b/src/com/android/managedprovisioning/task/DownloadPackageTask.java
@@ -31,6 +31,7 @@
 import android.util.Base64;
 
 import com.android.managedprovisioning.ProvisionLogger;
+import com.android.managedprovisioning.ProvisioningParams;
 
 import java.io.InputStream;
 import java.io.IOException;
@@ -38,45 +39,55 @@
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
- * Downloads a given file and checks whether its hash matches a given hash to verify that the
- * intended file was downloaded.
+ * Downloads the device admin and the device initializer if download locations were provided for
+ * them in the provisioning parameters. Also checks that each file's hash matches a given hash to
+ * verify that the downloaded files are the ones that are expected.
  */
 public class DownloadPackageTask {
     public static final int ERROR_HASH_MISMATCH = 0;
     public static final int ERROR_DOWNLOAD_FAILED = 1;
     public static final int ERROR_OTHER = 2;
 
+    public static final String DEVICE_OWNER = "deviceOwner";
+    public static final String INITIALIZER = "initializer";
+
     private static final String HASH_TYPE = "SHA-1";
 
     private final Context mContext;
-    private final String mDownloadLocationFrom;
     private final Callback mCallback;
-    private final byte[] mHash;
-    private final String mHttpCookieHeader;
-
-    private boolean mDoneDownloading;
-    private String mDownloadLocationTo;
-    private long mDownloadId;
     private BroadcastReceiver mReceiver;
+    private final DownloadManager mDlm;
 
-    public DownloadPackageTask (Context context, String downloadLocation, byte[] hash,
-            String httpCookieHeader, Callback callback) {
+    private Set<DownloadInfo> mDownloads;
+
+    public DownloadPackageTask (Context context, ProvisioningParams params, Callback callback) {
         mCallback = callback;
         mContext = context;
-        mDownloadLocationFrom = downloadLocation;
-        mHash = hash;
-        mHttpCookieHeader = httpCookieHeader;
-        mDoneDownloading = false;
-    }
+        mDlm = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
 
-    public boolean downloadLocationWasProvided() {
-        return !TextUtils.isEmpty(mDownloadLocationFrom);
+        mDownloads = new HashSet<DownloadInfo>();
+        if (!TextUtils.isEmpty(params.mDeviceAdminPackageDownloadLocation)) {
+            mDownloads.add(new DownloadInfo(
+                    params.mDeviceAdminPackageDownloadLocation,
+                    params.mDeviceAdminPackageChecksum,
+                    params.mDeviceAdminPackageDownloadCookieHeader,
+                    DEVICE_OWNER));
+        }
+        if (!TextUtils.isEmpty(params.mDeviceInitializerPackageDownloadLocation)) {
+            mDownloads.add(new DownloadInfo(
+                    params.mDeviceInitializerPackageDownloadLocation,
+                    params.mDeviceInitializerPackageChecksum,
+                    params.mDeviceInitializerPackageDownloadCookieHeader,
+                    INITIALIZER));
+        }
     }
 
     public void run() {
-        if (!downloadLocationWasProvided()) {
+        if (mDownloads.size() == 0) {
             mCallback.onSuccess();
             return;
         }
@@ -84,38 +95,49 @@
         mContext.registerReceiver(mReceiver,
                 new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
 
-        ProvisionLogger.logd("Starting download from " + mDownloadLocationFrom);
         DownloadManager dm = (DownloadManager) mContext
                 .getSystemService(Context.DOWNLOAD_SERVICE);
-        Request request = new Request(Uri.parse(mDownloadLocationFrom));
-        if (mHttpCookieHeader != null) {
-            request.addRequestHeader("Cookie", mHttpCookieHeader);
-            ProvisionLogger.logd("Downloading with http cookie header: " + mHttpCookieHeader);
+        for (DownloadInfo downloadInfo : mDownloads) {
+            ProvisionLogger.logd("Starting download from " + downloadInfo.mDownloadLocationFrom);
+
+            Request request = new Request(Uri.parse(downloadInfo.mDownloadLocationFrom));
+            if (downloadInfo.mHttpCookieHeader != null) {
+                request.addRequestHeader("Cookie", downloadInfo.mHttpCookieHeader);
+                ProvisionLogger.logd(
+                        "Downloading with http cookie header: " + downloadInfo.mHttpCookieHeader);
+            }
+            downloadInfo.mDownloadId = dm.enqueue(request);
         }
-        mDownloadId = dm.enqueue(request);
     }
 
     private BroadcastReceiver createDownloadReceiver() {
         return new BroadcastReceiver() {
+            /**
+             * Whenever the download manager finishes a download, record the successful download for
+             * the corresponding DownloadInfo.
+             */
             @Override
             public void onReceive(Context context, Intent intent) {
                 if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
                     Query q = new Query();
-                    q.setFilterById(mDownloadId);
-                    DownloadManager dm = (DownloadManager) mContext
-                            .getSystemService(Context.DOWNLOAD_SERVICE);
-                    Cursor c = dm.query(q);
-                    if (c.moveToFirst()) {
-                        int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
-                        if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
-                            String location = c.getString(
-                                    c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
-                            c.close();
-                            onDownloadSuccess(location);
-                        } else if (DownloadManager.STATUS_FAILED == c.getInt(columnIndex)){
-                            int reason = c.getColumnIndex(DownloadManager.COLUMN_REASON);
-                            c.close();
-                            onDownloadFail(reason);
+                    for (DownloadInfo downloadInfo : mDownloads) {
+                        q.setFilterById(downloadInfo.mDownloadId);
+                        Cursor c = mDlm.query(q);
+                        if (c.moveToFirst()) {
+                            long downloadId =
+                                    c.getLong(c.getColumnIndex(DownloadManager.COLUMN_ID));
+                            int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
+                            if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
+                                String location = c.getString(
+                                        c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
+                                c.close();
+                                onDownloadSuccess(downloadId, location);
+                            } else if (DownloadManager.STATUS_FAILED == c.getInt(columnIndex)){
+                                int reason = c.getInt(
+                                        c.getColumnIndex(DownloadManager.COLUMN_REASON));
+                                c.close();
+                                onDownloadFail(reason);
+                            }
                         }
                     }
                 }
@@ -123,40 +145,61 @@
         };
     }
 
-    private void onDownloadSuccess(String location) {
-        if (mDoneDownloading) {
+    /**
+     * For a given successful download, check that the downloaded file is the expected file. Check
+     * if this was the last file the task had to download and finish the DownloadPackageTask if that
+     * is the case.
+     * @param downloadId the unique download id for the completed download.
+     * @param location the file location of the downloaded file.
+     */
+    private void onDownloadSuccess(long downloadId, String location) {
+        DownloadInfo downloadInfo = null;
+        for (DownloadInfo info : mDownloads) {
+            if (downloadId == info.mDownloadId) {
+                downloadInfo = info;
+            }
+        }
+        if (downloadInfo == null || downloadInfo.mDoneDownloading) {
             // DownloadManager can send success more than once. Only act first time.
             return;
         } else {
-            mDoneDownloading = true;
+            downloadInfo.mDoneDownloading = true;
         }
-
         ProvisionLogger.logd("Downloaded succesfully to: " + location);
 
         // Check whether hash of downloaded file matches hash given in constructor.
         byte[] hash = computeHash(location);
         if (hash == null) {
-
             // Error should have been reported in computeHash().
             return;
         }
 
-        if (Arrays.equals(mHash, hash)) {
+        if (Arrays.equals(downloadInfo.mHash, hash)) {
             ProvisionLogger.logd(HASH_TYPE + "-hashes matched, both are "
                     + byteArrayToString(hash));
-            mDownloadLocationTo = location;
-            mCallback.onSuccess();
+            downloadInfo.mLocation = location;
+            downloadInfo.mSuccess = true;
+            checkSuccess();
         } else {
             ProvisionLogger.loge(HASH_TYPE + "-hash of downloaded file does not match given hash.");
             ProvisionLogger.loge(HASH_TYPE + "-hash of downloaded file: "
                     + byteArrayToString(hash));
             ProvisionLogger.loge(HASH_TYPE + "-hash provided by programmer: "
-                    + byteArrayToString(mHash));
+                    + byteArrayToString(downloadInfo.mHash));
 
             mCallback.onError(ERROR_HASH_MISMATCH);
         }
     }
 
+    private void checkSuccess() {
+        for (DownloadInfo info : mDownloads) {
+            if (!info.mSuccess) {
+                return;
+            }
+        }
+        mCallback.onSuccess();
+    }
+
     private void onDownloadFail(int errorCode) {
         ProvisionLogger.loge("Downloading package failed.");
         ProvisionLogger.loge("COLUMN_REASON in DownloadManager response has value: "
@@ -203,8 +246,13 @@
         return hash;
     }
 
-    public String getDownloadedPackageLocation() {
-        return mDownloadLocationTo;
+    public String getDownloadedPackageLocation(String packageType) {
+        for (DownloadInfo info : mDownloads) {
+            if (packageType.equals(info.mPackageType)) {
+                return info.mLocation;
+            }
+        }
+        return "";
     }
 
     public void cleanUp() {
@@ -217,12 +265,14 @@
         //Remove download.
         DownloadManager dm = (DownloadManager) mContext
                 .getSystemService(Context.DOWNLOAD_SERVICE);
-        boolean removeSuccess = dm.remove(mDownloadId) == 1;
-        if (removeSuccess) {
-            ProvisionLogger.logd("Successfully removed the device owner installer file.");
-        } else {
-            ProvisionLogger.loge("Could not remove the device owner installer file.");
-            // Ignore this error. Failing cleanup should not stop provisioning flow.
+        for (DownloadInfo info : mDownloads) {
+            boolean removeSuccess = dm.remove(info.mDownloadId) == 1;
+            if (removeSuccess) {
+                ProvisionLogger.logd("Successfully removed installer file.");
+            } else {
+                ProvisionLogger.loge("Could not remove installer file.");
+                // Ignore this error. Failing cleanup should not stop provisioning flow.
+            }
         }
     }
 
@@ -235,4 +285,24 @@
         public abstract void onSuccess();
         public abstract void onError(int errorCode);
     }
+
+    private static class DownloadInfo {
+        public final String mDownloadLocationFrom;
+        public final byte[] mHash;
+        public final String mHttpCookieHeader;
+        public final String mPackageType;
+        public long mDownloadId;
+        public String mLocation;
+        public boolean mDoneDownloading;
+        public boolean mSuccess;
+
+        public DownloadInfo(String downloadLocation, byte[] hash, String httpCookieHeader,
+                String packageType) {
+            mDownloadLocationFrom = downloadLocation;
+            mHash = hash;
+            mHttpCookieHeader = httpCookieHeader;
+            mPackageType = packageType;
+            mDoneDownloading = false;
+        }
+    }
 }
diff --git a/src/com/android/managedprovisioning/task/InstallPackageTask.java b/src/com/android/managedprovisioning/task/InstallPackageTask.java
index b0cc438..5eaa43a 100644
--- a/src/com/android/managedprovisioning/task/InstallPackageTask.java
+++ b/src/com/android/managedprovisioning/task/InstallPackageTask.java
@@ -27,14 +27,17 @@
 import android.Manifest.permission;
 
 import com.android.managedprovisioning.ProvisionLogger;
+import com.android.managedprovisioning.ProvisioningParams;
 
 import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
- * Installs a device owner package from a given path.
+ * Optionally installs device owner and device initializer packages.
  * <p>
- * Before installing it is checked whether the file at the specified path contains the given package
- * and the given admin receiver.
+ * Before installing it is checked whether each file at the specified paths contains the correct
+ * package and admin receiver.
  * </p>
  */
 public class InstallPackageTask {
@@ -43,73 +46,89 @@
 
     private final Context mContext;
     private final Callback mCallback;
-    private final String mPackageName;
+    private final String mDeviceAdminPackageName;
+    private final String mDeviceInitializerPackageName;
+    private final boolean mDownloadedAdmin;
+    private final boolean mDownloadedInitializer;
 
-    private String mPackageLocation;
-    private final String mDownloadLocationFrom;
     private PackageManager mPm;
     private int mPackageVerifierEnable;
+    private Set<InstallInfo> mPackagesToInstall;
 
-    public InstallPackageTask (Context context, String packageName, String downloadLocation,
-            Callback callback) {
+    public InstallPackageTask (Context context, ProvisioningParams params, Callback callback) {
         mCallback = callback;
         mContext = context;
-        mPackageLocation = null; // Initialized in run().
-        mPackageName = packageName;
-        mDownloadLocationFrom = downloadLocation;
+        mDeviceAdminPackageName = params.inferDeviceAdminPackageName();
+        mDeviceInitializerPackageName = params.mDeviceInitializerComponentName.getPackageName();
+        mDownloadedAdmin = !TextUtils.isEmpty(params.mDeviceAdminPackageDownloadLocation);
+        mDownloadedInitializer =
+                !TextUtils.isEmpty(params.mDeviceInitializerPackageDownloadLocation);
+        mPackagesToInstall = new HashSet<InstallInfo>();
     }
 
-    public boolean downloadLocationWasProvided() {
-        return !TextUtils.isEmpty(mDownloadLocationFrom);
-    }
-
-    public void run(String packageLocation) {
-        if (TextUtils.isEmpty(packageLocation)) {
-            if (!downloadLocationWasProvided()) {
-                ProvisionLogger.loge("Package Location is empty, nothing to install.");
-                mCallback.onSuccess();
-                return;
+    public void run(String deviceAdminPackageLocation, String deviceInitializerPackageLocation) {
+        if (mDownloadedAdmin) {
+            if (!TextUtils.isEmpty(deviceAdminPackageLocation)) {
+                mPackagesToInstall.add(
+                        new InstallInfo(mDeviceAdminPackageName, deviceAdminPackageLocation));
             } else {
                 ProvisionLogger.loge("Package Location is empty.");
                 mCallback.onError(ERROR_PACKAGE_INVALID);
                 return;
             }
         }
-        mPackageLocation = packageLocation;
+        if (mDownloadedInitializer) {
+            if (!TextUtils.isEmpty(deviceInitializerPackageLocation)) {
+                mPackagesToInstall.add(new InstallInfo(
+                        mDeviceInitializerPackageName, deviceInitializerPackageLocation));
+            } else {
+                ProvisionLogger.loge("Package Location is empty.");
+                mCallback.onError(ERROR_PACKAGE_INVALID);
+                return;
+            }
+        }
+
+        if (mPackagesToInstall.size() == 0) {
+            ProvisionLogger.loge("Nothing to install");
+            mCallback.onSuccess();
+            return;
+        }
+        ProvisionLogger.logi("Installing package(s)");
 
         PackageInstallObserver observer = new PackageInstallObserver();
         mPm = mContext.getPackageManager();
 
-        if (packageContentIsCorrect()) {
-            // Temporarily turn off package verification.
-            mPackageVerifierEnable = Global.getInt(mContext.getContentResolver(),
-                    Global.PACKAGE_VERIFIER_ENABLE, 1);
-            Global.putInt(mContext.getContentResolver(), Global.PACKAGE_VERIFIER_ENABLE, 0);
+        for (InstallInfo info : mPackagesToInstall) {
+            if (packageContentIsCorrect(info.mPackageName, info.mLocation)) {
+                // Temporarily turn off package verification.
+                mPackageVerifierEnable = Global.getInt(mContext.getContentResolver(),
+                        Global.PACKAGE_VERIFIER_ENABLE, 1);
+                Global.putInt(mContext.getContentResolver(), Global.PACKAGE_VERIFIER_ENABLE, 0);
 
-            Uri packageUri = Uri.parse("file://" + mPackageLocation);
-
-            // Allow for replacing an existing package.
-            // Needed in case this task is performed multiple times.
-            mPm.installPackage(packageUri, observer,
-                    /* flags */ PackageManager.INSTALL_REPLACE_EXISTING, mContext.getPackageName());
-        } else {
-            // Error should have been reported in packageContentIsCorrect().
-            return;
+                // Allow for replacing an existing package.
+                // Needed in case this task is performed multiple times.
+                mPm.installPackage(Uri.parse("file://" + info.mLocation),
+                        observer,
+                        /* flags */ PackageManager.INSTALL_REPLACE_EXISTING,
+                        mContext.getPackageName());
+            } else {
+                // Error should have been reported in packageContentIsCorrect().
+                return;
+            }
         }
     }
 
-    private boolean packageContentIsCorrect() {
-        PackageInfo pi = mPm.getPackageArchiveInfo(mPackageLocation,
-                PackageManager.GET_RECEIVERS);
+    private boolean packageContentIsCorrect(String packageName, String packageLocation) {
+        PackageInfo pi = mPm.getPackageArchiveInfo(packageLocation, PackageManager.GET_RECEIVERS);
         if (pi == null) {
             ProvisionLogger.loge("Package could not be parsed successfully.");
             mCallback.onError(ERROR_PACKAGE_INVALID);
             return false;
         }
-        if (!pi.packageName.equals(mPackageName)) {
+        if (!pi.packageName.equals(packageName)) {
             ProvisionLogger.loge("Package name in apk (" + pi.packageName
                     + ") does not match package name specified by programmer ("
-                    + mPackageName + ").");
+                    + packageName + ").");
             mCallback.onError(ERROR_PACKAGE_INVALID);
             return false;
         }
@@ -127,34 +146,68 @@
     private class PackageInstallObserver extends IPackageInstallObserver.Stub {
         @Override
         public void packageInstalled(String packageName, int returnCode) {
-            // Set package verification flag to its original value.
-            Global.putInt(mContext.getContentResolver(), Global.PACKAGE_VERIFIER_ENABLE,
-                    mPackageVerifierEnable);
-
-            if (returnCode == PackageManager.INSTALL_SUCCEEDED
-                    && mPackageName.equals(packageName)) {
-                ProvisionLogger.logd("Package " + mPackageName + " is succesfully installed.");
-
-                mCallback.onSuccess();
+            InstallInfo installInfo = null;
+            for (InstallInfo info : mPackagesToInstall) {
+                if (packageName.equals(info.mPackageName)) {
+                    installInfo = info;
+                }
+            }
+            if (installInfo == null)  {
+                ProvisionLogger.loge("Package doesn't have expected package name.");
+                mCallback.onError(ERROR_PACKAGE_INVALID);
+                return;
+            }
+            if (returnCode == PackageManager.INSTALL_SUCCEEDED) {
+                installInfo.mDoneInstalling = true;
+                ProvisionLogger.logd(
+                        "Package " + installInfo.mPackageName + " is succesfully installed.");
+                checkSuccess();
             } else {
                 if (returnCode == PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE) {
-                    ProvisionLogger.logd("Current version of " + mPackageName
-                            + " higher than the version to be installed.");
-                    ProvisionLogger.logd("Package " + mPackageName + " was not reinstalled.");
-                    mCallback.onSuccess();
-                    return;
+                    installInfo.mDoneInstalling = true;
+                    ProvisionLogger.logd("Current version of " + installInfo.mPackageName
+                            + " higher than the version to be installed. It was not reinstalled.");
+                    checkSuccess();
+                } else {
+                    ProvisionLogger.logd(
+                            "Installing package " + installInfo.mPackageName + " failed.");
+                    ProvisionLogger.logd(
+                            "Errorcode returned by IPackageInstallObserver = " + returnCode);
+                    mCallback.onError(ERROR_INSTALLATION_FAILED);
                 }
-
-                ProvisionLogger.logd("Installing package " + mPackageName + " failed.");
-                ProvisionLogger.logd("Errorcode returned by IPackageInstallObserver = "
-                        + returnCode);
-                mCallback.onError(ERROR_INSTALLATION_FAILED);
             }
         }
     }
 
+    /**
+     * Calls the success callback once all of the packages that needed to be installed are
+     * successfully installed.
+     */
+    private void checkSuccess() {
+        for (InstallInfo info : mPackagesToInstall) {
+            if (!info.mDoneInstalling) {
+                return;
+            }
+        }
+        // Set package verification flag to its original value.
+        Global.putInt(mContext.getContentResolver(), Global.PACKAGE_VERIFIER_ENABLE,
+                mPackageVerifierEnable);
+        mCallback.onSuccess();
+    }
+
     public abstract static class Callback {
         public abstract void onSuccess();
         public abstract void onError(int errorCode);
     }
+
+    private static class InstallInfo {
+        public String mPackageName;
+        public String mLocation;
+        public boolean mDoneInstalling;
+
+        public InstallInfo(String packageName, String location) {
+            mPackageName = packageName;
+            mLocation = location;
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/managedprovisioning/task/SetDevicePolicyTask.java b/src/com/android/managedprovisioning/task/SetDevicePolicyTask.java
index 2a7b7f5..82a60a8 100644
--- a/src/com/android/managedprovisioning/task/SetDevicePolicyTask.java
+++ b/src/com/android/managedprovisioning/task/SetDevicePolicyTask.java
@@ -19,15 +19,15 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.text.TextUtils;
 
 import com.android.managedprovisioning.ProvisionLogger;
 
+/**
+ * This tasks sets a given component as the owner of the device. If provided it also sets a given
+ * component as the device initializer, which can perform additional setup steps at the end of
+ * provisioning before setting the device as provisioned.
+ */
 public class SetDevicePolicyTask {
     public static final int ERROR_PACKAGE_NOT_INSTALLED = 0;
     public static final int ERROR_NO_RECEIVER = 1;
@@ -37,47 +37,77 @@
     private final Context mContext;
     private String mAdminPackage;
     private ComponentName mAdminComponent;
-    private final String mOwner;
+    private final String mOwnerName;
+    private ComponentName mInitializerComponent;
+    private String mInitializerPackageName;
+    private String mInitializerName;
 
     private PackageManager mPackageManager;
     private DevicePolicyManager mDevicePolicyManager;
 
-    public SetDevicePolicyTask(Context context, String owner, Callback callback) {
+    public SetDevicePolicyTask(Context context, String ownerName,
+            ComponentName initializerComponent, String initializerName, Callback callback) {
         mCallback = callback;
         mContext = context;
-        mOwner = owner;
+        mOwnerName = ownerName;
+        mInitializerComponent = initializerComponent;
+        if (mInitializerComponent != null) {
+            mInitializerPackageName = initializerComponent.getPackageName();
+            mInitializerName = initializerName;
+        }
+
         mPackageManager = mContext.getPackageManager();
         mDevicePolicyManager = (DevicePolicyManager) mContext.
                 getSystemService(Context.DEVICE_POLICY_SERVICE);
     }
 
     public void run(ComponentName adminComponent) {
-        mAdminComponent = adminComponent;
-        mAdminPackage = mAdminComponent.getPackageName();
-        enableDevicePolicyApp();
-        setActiveAdmin();
-        setDeviceOwner();
+        try {
+            mAdminComponent = adminComponent;
+            mAdminPackage = mAdminComponent.getPackageName();
+
+            enableDevicePolicyApp(mAdminPackage);
+            setActiveAdmin(mAdminComponent);
+            setDeviceOwner(mAdminPackage, mOwnerName);
+
+            if (mInitializerComponent != null) {
+                enableDevicePolicyApp(mInitializerPackageName);
+                setActiveAdmin(mInitializerComponent);
+                setDeviceInitializer(mInitializerComponent, mInitializerName);
+            }
+        } catch (Exception e) {
+            ProvisionLogger.loge("Failure setting device owner or initializer", e);
+            mCallback.onError(ERROR_OTHER);
+            return;
+        }
+
         mCallback.onSuccess();
     }
 
-    private void enableDevicePolicyApp() {
-        int enabledSetting = mPackageManager
-                .getApplicationEnabledSetting(mAdminPackage);
+    private void enableDevicePolicyApp(String packageName) {
+        int enabledSetting = mPackageManager.getApplicationEnabledSetting(packageName);
         if (enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
-            mPackageManager.setApplicationEnabledSetting(mAdminPackage,
+            mPackageManager.setApplicationEnabledSetting(packageName,
                     PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 0);
         }
     }
 
-    public void setActiveAdmin() {
-        ProvisionLogger.logd("Setting " + mAdminComponent + " as active admin.");
-        mDevicePolicyManager.setActiveAdmin(mAdminComponent, true);
+    public void setActiveAdmin(ComponentName component) {
+        ProvisionLogger.logd("Setting " + component + " as active admin.");
+        mDevicePolicyManager.setActiveAdmin(component, true);
     }
 
-    public void setDeviceOwner() {
-        ProvisionLogger.logd("Setting " + mAdminPackage + " as device owner " + mOwner + ".");
-        if (!mDevicePolicyManager.isDeviceOwner(mAdminPackage)) {
-            mDevicePolicyManager.setDeviceOwner(mAdminPackage, mOwner);
+    public void setDeviceOwner(String packageName, String owner) {
+        ProvisionLogger.logd("Setting " + packageName + " as device owner " + owner + ".");
+        if (!mDevicePolicyManager.isDeviceOwner(packageName)) {
+            mDevicePolicyManager.setDeviceOwner(packageName, owner);
+        }
+    }
+
+    public void setDeviceInitializer(ComponentName component, String owner) {
+        ProvisionLogger.logd("Setting " + component + " as device initializer " + owner + ".");
+        if (!mDevicePolicyManager.isDeviceInitializerApp(component.getPackageName())) {
+            mDevicePolicyManager.setDeviceInitializer(null, component, owner);
         }
     }