Merge "Add proper error handling to the device owner provisioning."
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 77e0602..0c87bba 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -85,7 +85,17 @@
 
   <!-- Title of the error dialog. [CHAR LIMIT=45] -->
   <string name="device_owner_error_title">Provisioning failed</string>
-  <!-- Message of the error dialog. [CHAR LIMIT=NONE] -->
+  <!-- Title of the cancel provisioning dialog. [CHAR LIMIT=45] -->
+  <string name="device_owner_cancel_title">Stop provisioning?</string>
+  <!-- Message of the cancel dialog. [CHAR LIMIT=NONE] -->
+  <string name="device_owner_cancel_message">Are you sure you want to stop provisioning and factory reset the device? The factory reset can take a while.</string>
+  <!-- Cancel button text of the cancel dialog. [CHAR LIMIT=45] -->
+  <string name="device_owner_cancel_cancel">Cancel</string>
+  <!-- OK button text of the error dialog. [CHAR LIMIT=45] -->
+  <string name="device_owner_error_ok">Ok</string>
+  <!-- Reset button text of the error dialog. [CHAR LIMIT=45] -->
+  <string name="device_owner_error_reset">Reset</string>
+  <!-- Message of the error dialog in case of an unspecified error. [CHAR LIMIT=NONE] -->
   <string name="device_owner_error_general">An error occured.</string>
   <!-- Message of the error dialog when already provisioned. [CHAR LIMIT=NONE] -->
   <string name="device_owner_error_already_provisioned">The device is already provisioned.</string>
diff --git a/src/com/android/managedprovisioning/DeviceOwnerProvisioningActivity.java b/src/com/android/managedprovisioning/DeviceOwnerProvisioningActivity.java
index 23f55b9..fce781b 100644
--- a/src/com/android/managedprovisioning/DeviceOwnerProvisioningActivity.java
+++ b/src/com/android/managedprovisioning/DeviceOwnerProvisioningActivity.java
@@ -16,8 +16,11 @@
 
 package com.android.managedprovisioning;
 
+import static android.app.admin.DeviceAdminReceiver.ACTION_PROFILE_PROVISIONING_COMPLETE;
+
 import android.app.Activity;
 import android.app.AlertDialog;
+import android.app.Dialog;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -26,6 +29,7 @@
 import android.content.IntentFilter;
 import android.os.Bundle;
 import android.provider.Settings.Global;
+import android.provider.Settings.Secure;
 import android.support.v4.content.LocalBroadcastManager;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -60,6 +64,9 @@
 
     private BroadcastReceiver mServiceMessageReceiver;
     private TextView mProgressTextView;
+    private Dialog mDialog; // The cancel or error dialog that is currently shown.
+    private boolean mDone; // Indicates whether the service has sent ACTION_PROVISIONING_SUCCESS.
+    private String mDeviceAdminPackageName;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -68,20 +75,20 @@
         ProvisionLogger.logd("Device owner provisioning activity ONCREATE");
 
         // Check whether we can provision.
-        if (Global.getInt(getContentResolver(), Global.DEVICE_PROVISIONED, /* default */ 0) != 0) {
+        if (Global.getInt(getContentResolver(), Global.DEVICE_PROVISIONED, 0 /* default */) != 0) {
             ProvisionLogger.loge("Device already provisioned.");
-            error(R.string.device_owner_error_already_provisioned);
+            error(R.string.device_owner_error_already_provisioned, false /* no factory reset */);
             return;
         }
-
         DevicePolicyManager dpm = (DevicePolicyManager)
                 getSystemService(Context.DEVICE_POLICY_SERVICE);
         if (dpm.getDeviceOwner() != null) {
             ProvisionLogger.loge("Device owner already present.");
-            error(R.string.device_owner_error_already_owned);
+            error(R.string.device_owner_error_already_owned, false /* no factory reset */);
             return;
         }
 
+        // Setup the UI.
         final LayoutInflater inflater = getLayoutInflater();
         final View contentView = inflater.inflate(R.layout.progress, null);
         setContentView(contentView);
@@ -93,15 +100,48 @@
         filter.addAction(DeviceOwnerProvisioningService.ACTION_PROVISIONING_SUCCESS);
         filter.addAction(DeviceOwnerProvisioningService.ACTION_PROVISIONING_ERROR);
         filter.addAction(DeviceOwnerProvisioningService.ACTION_PROGRESS_UPDATE);
-        filter.addAction(DeviceOwnerProvisioningService.ACTION_REQUEST_ENCRYPTION);
         LocalBroadcastManager.getInstance(this).registerReceiver(mServiceMessageReceiver, filter);
 
+        // Parse the incoming intent.
+        MessageParser parser = new MessageParser();
+        ProvisioningParams params;
+        try {
+            params = parser.parseIntent(getIntent());
+        } catch (MessageParser.ParseException e) {
+            ProvisionLogger.loge("Could not read data from intent", e);
+            error(e.getErrorMessageId(), false /* no factory reset */);
+            return;
+        }
+        mDeviceAdminPackageName = params.mDeviceAdminPackageName;
+
+        // Ask to encrypt the device before proceeding
+        if (!EncryptDeviceActivity.isDeviceEncrypted()) {
+            requestEncryption(parser.getCachedProvisioningProperties());
+            finish();
+            return;
+        }
+
         // Start service.
         Intent intent = new Intent(this, DeviceOwnerProvisioningService.class);
+        intent.putExtra(DeviceOwnerProvisioningService.EXTRA_PROVISIONING_PARAMS, params);
         intent.putExtras(getIntent());
         startService(intent);
     }
 
+    private void requestEncryption(String propertiesForResume) {
+        Intent encryptIntent = new Intent(DeviceOwnerProvisioningActivity.this,
+                EncryptDeviceActivity.class);
+
+        Bundle resumeExtras = new Bundle();
+        resumeExtras.putString(EncryptDeviceActivity.EXTRA_RESUME_TARGET,
+                EncryptDeviceActivity.TARGET_DEVICE_OWNER);
+        resumeExtras.putString(MessageParser.EXTRA_PROVISIONING_PROPERTIES,
+                propertiesForResume);
+        encryptIntent.putExtra(EncryptDeviceActivity.EXTRA_RESUME, resumeExtras);
+
+        startActivityForResult(encryptIntent, ENCRYPT_DEVICE_REQUEST_CODE);
+    }
+
     class ServiceMessageReceiver extends BroadcastReceiver
     {
         @Override
@@ -110,19 +150,24 @@
             String action = intent.getAction();
             if (action.equals(DeviceOwnerProvisioningService.ACTION_PROVISIONING_SUCCESS)) {
                 ProvisionLogger.logd("Successfully provisioned");
-                finish();
+                synchronized(this) {
+                    if (mDialog == null) {
+                        onProvisioningSuccess();
+                    } else {
+                        // Postpone finishing this activity till the user has decided whether
+                        // he/she wants to reset or not.
+                        mDone = true;
+                    }
+                }
                 return;
             } else if (action.equals(DeviceOwnerProvisioningService.ACTION_PROVISIONING_ERROR)) {
-                int errorCode = intent.getIntExtra(
-                        DeviceOwnerProvisioningService.EXTRA_USER_VISIBLE_ERROR_ID_KEY, -1);
+                int errorMessageId = intent.getIntExtra(
+                        DeviceOwnerProvisioningService.EXTRA_USER_VISIBLE_ERROR_ID_KEY,
+                        R.string.device_owner_error_general);
+
                 ProvisionLogger.logd("Error reported with code "
-                        + getResources().getString(errorCode));
-                if (errorCode < 0) {
-                    error(R.string.device_owner_error_general);
-                    return;
-                } else {
-                    error(errorCode);
-                }
+                        + getResources().getString(errorMessageId));
+                error(errorMessageId, true /* always factory reset */);
             } else if (action.equals(DeviceOwnerProvisioningService.ACTION_PROGRESS_UPDATE)) {
                 int progressMessage = intent.getIntExtra(
                         DeviceOwnerProvisioningService.EXTRA_PROGRESS_MESSAGE_ID_KEY, -1);
@@ -131,25 +176,80 @@
                 if (progressMessage >= 0) {
                     progressUpdate(progressMessage);
                 }
-            } else if (action.equals(DeviceOwnerProvisioningService.ACTION_REQUEST_ENCRYPTION)) {
-                ProvisionLogger.logd("Received request to encrypt device.");
-                Intent encryptIntent = new Intent(DeviceOwnerProvisioningActivity.this,
-                        EncryptDeviceActivity.class);
-                encryptIntent.putExtras(intent);
-                startActivityForResult(encryptIntent, ENCRYPT_DEVICE_REQUEST_CODE);
             }
         }
     }
 
+    private void onProvisioningSuccess() {
+        stopService(new Intent(DeviceOwnerProvisioningActivity.this,
+                        DeviceOwnerProvisioningService.class));
+
+        // Skip the setup wizard.
+        Global.putInt(getContentResolver(), Global.DEVICE_PROVISIONED, 1);
+        Secure.putInt(getContentResolver(), Secure.USER_SETUP_COMPLETE, 1);
+
+        Intent completeIntent = new Intent(ACTION_PROFILE_PROVISIONING_COMPLETE);
+        completeIntent.setPackage(mDeviceAdminPackageName);
+        completeIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES |
+                Intent.FLAG_RECEIVER_FOREGROUND);
+        sendBroadcast(completeIntent);
+        finish();
+    }
+
     @Override
     public void onBackPressed() {
-        // TODO: Handle this graciously by stopping the provisioning flow and cleaning up.
+        showCancelResetDialog();
+    }
+
+    private void showCancelResetDialog() {
+        AlertDialog.Builder alertBuilder =
+                new AlertDialog.Builder(DeviceOwnerProvisioningActivity.this)
+                .setTitle(R.string.device_owner_cancel_title)
+                .setMessage(R.string.device_owner_cancel_message)
+                .setNegativeButton(R.string.device_owner_cancel_cancel,
+                        new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog,int id) {
+                                dialog.dismiss();
+                                synchronized(this) {
+                                    mDialog = null;
+                                    if (mDone) {
+                                        onProvisioningSuccess();
+                                    }
+                                }
+                            }
+                        })
+                .setPositiveButton(R.string.device_owner_error_reset,
+                        new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog,int id) {
+                                // Factory reset the device.
+                                sendBroadcast(
+                                        new Intent("android.intent.action.MASTER_CLEAR"));
+                                stopService(new Intent(DeviceOwnerProvisioningActivity.this,
+                                                DeviceOwnerProvisioningService.class));
+                                finish();
+                            }
+                        });
+
+        if (mDialog != null) {
+            mDialog.dismiss();
+        }
+        mDialog = alertBuilder.create();
+        mDialog.show();
     }
 
     @Override
     public void onDestroy() {
         ProvisionLogger.logd("Device owner provisioning activity ONDESTROY");
-        LocalBroadcastManager.getInstance(this).unregisterReceiver(mServiceMessageReceiver);
+        if (mServiceMessageReceiver != null) {
+            LocalBroadcastManager.getInstance(this).unregisterReceiver(mServiceMessageReceiver);
+            mServiceMessageReceiver = null;
+        }
+        if (mDialog != null) {
+            mDialog.dismiss();
+            mDialog = null;
+        }
         super.onDestroy();
     }
 
@@ -167,20 +267,37 @@
         }
     }
 
-    private void error(int dialogMessage) {
-        AlertDialog dlg = new AlertDialog.Builder(this)
+    private void error(int dialogMessage, boolean resetRequired) {
+        AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this)
             .setTitle(R.string.device_owner_error_title)
             .setMessage(dialogMessage)
-            .setCancelable(false)
-            .setPositiveButton("OK", new DialogInterface.OnClickListener() {
-                @Override
-                public void onClick(DialogInterface dialog,int id) {
-                    // Close activity
-                    finish();
-                }
-            })
-            .create();
-        dlg.show();
+            .setCancelable(false);
+        if (resetRequired) {
+            alertBuilder.setPositiveButton(R.string.device_owner_error_reset,
+                    new DialogInterface.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface dialog,int id) {
+                            // Factory reset the device.
+                            sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
+                            stopService(new Intent(DeviceOwnerProvisioningActivity.this,
+                                            DeviceOwnerProvisioningService.class));
+                            finish();
+                        }
+                    });
+        } else {
+            alertBuilder.setPositiveButton(R.string.device_owner_error_ok,
+                    new DialogInterface.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface dialog,int id) {
+                            // Close activity.
+                            stopService(new Intent(DeviceOwnerProvisioningActivity.this,
+                                            DeviceOwnerProvisioningService.class));
+                            finish();
+                        }
+                    });
+        }
+        mDialog = alertBuilder.create();
+        mDialog.show();
     }
 
     @Override
diff --git a/src/com/android/managedprovisioning/DeviceOwnerProvisioningService.java b/src/com/android/managedprovisioning/DeviceOwnerProvisioningService.java
index e002a92..dfdd312 100644
--- a/src/com/android/managedprovisioning/DeviceOwnerProvisioningService.java
+++ b/src/com/android/managedprovisioning/DeviceOwnerProvisioningService.java
@@ -16,8 +16,6 @@
 
 package com.android.managedprovisioning;
 
-import static android.app.admin.DeviceAdminReceiver.ACTION_PROFILE_PROVISIONING_COMPLETE;
-
 import android.app.AlarmManager;
 import android.app.Service;
 import android.content.ComponentName;
@@ -25,8 +23,6 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.IBinder;
-import android.provider.Settings.Global;
-import android.provider.Settings.Secure;
 import android.support.v4.content.LocalBroadcastManager;
 
 import com.android.internal.app.LocalePicker;
@@ -38,7 +34,6 @@
 
 import java.lang.Runnable;
 import java.util.Locale;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * This service does the work for the DeviceOwnerProvisioningActivity.
@@ -60,20 +55,23 @@
             "com.android.phone.PERFORM_CDMA_PROVISIONING";
 
     // Intent actions for communication with DeviceOwnerProvisioningService.
-    public static final String ACTION_PROVISIONING_SUCCESS =
+    protected static final String ACTION_PROVISIONING_SUCCESS =
             "com.android.managedprovisioning.provisioning_success";
-    public static final String ACTION_PROVISIONING_ERROR =
+    protected static final String ACTION_PROVISIONING_ERROR =
             "com.android.managedprovisioning.error";
-    public static final String EXTRA_USER_VISIBLE_ERROR_ID_KEY = "UserVisibleErrorMessage-Id";
-    public static final String ACTION_PROGRESS_UPDATE =
+    protected static final String EXTRA_USER_VISIBLE_ERROR_ID_KEY =
+            "UserVisibleErrorMessage-Id";
+    protected static final String ACTION_PROGRESS_UPDATE =
             "com.android.managedprovisioning.progress_update";
-    public static final String EXTRA_PROGRESS_MESSAGE_ID_KEY = "ProgressMessageId";
-    public static final String ACTION_REQUEST_ENCRYPTION =
-            "com.android.managedprovisioning.request_encryption";
+    protected static final String EXTRA_PROGRESS_MESSAGE_ID_KEY =
+            "ProgressMessageId";
+    protected static final String EXTRA_PROVISIONING_PARAMS =
+            "ProvisioningParams";
 
-    private AtomicBoolean mProvisioningInFlight = new AtomicBoolean(false);
-    private int mLastProgressMessage;
-    private int mStartIdProvisioning;
+    private boolean mProvisioningInFlight = false;
+    private int mLastProgressMessage = -1;
+    private int mLastErrorMessage = -1;
+    private boolean mDone = false;
 
     // Provisioning tasks.
     private AddWifiNetworkTask mAddWifiNetworkTask;
@@ -82,43 +80,42 @@
     private SetDevicePolicyTask mSetDevicePolicyTask;
     private DeleteNonRequiredAppsTask mDeleteNonRequiredAppsTask;
 
+    private ProvisioningParams mParams;
+
     @Override
     public int onStartCommand(final Intent intent, int flags, int startId) {
-        if (mProvisioningInFlight.getAndSet(true)) {
-            ProvisionLogger.logd("Provisioning already in flight. Ignoring intent.");
-            sendProgressUpdateToActivity();
-            stopSelf(startId);
-        } else {
-            progressUpdate(R.string.progress_data_process);
+        ProvisionLogger.logd("Device owner provisioning service ONSTARTCOMMAND.");
 
-            mStartIdProvisioning = startId;
+        synchronized (this) { // Make operations on mProvisioningInFlight atomic.
+            if (mProvisioningInFlight) {
+                ProvisionLogger.logd("Provisioning already in flight.");
 
-            // Load the ProvisioningParams (from message in Intent).
-            MessageParser parser = new MessageParser();
-            final ProvisioningParams params;
-            try {
-                params = parser.parseIntent(intent);
-            } catch (MessageParser.ParseException e) {
-                ProvisionLogger.loge("Could not read data from intent", e);
-                error(e.getErrorMessageId());
-                return START_NOT_STICKY;
+                sendProgressUpdateToActivity();
+
+                // Send error message if currently in error state.
+                if (mLastErrorMessage >= 0) {
+                    sendError();
+                }
+
+                if (mDone) {
+                    onProvisioningSuccess(mParams.mDeviceAdminPackageName);
+                }
+            } else {
+                mProvisioningInFlight = true;
+                ProvisionLogger.logd("First start of the service.");
+                progressUpdate(R.string.progress_data_process);
+
+                // Load the ProvisioningParams (from message in Intent).
+                mParams = (ProvisioningParams) intent.getParcelableExtra(EXTRA_PROVISIONING_PARAMS);
+
+                // Do the work on a separate thread.
+                new Thread(new Runnable() {
+                        public void run() {
+                            initializeProvisioningEnvironment(mParams);
+                            startDeviceOwnerProvisioning(mParams);
+                        }
+                    }).start();
             }
-
-            // Ask to encrypt the device before proceeding
-            if (!EncryptDeviceActivity.isDeviceEncrypted()) {
-                requestEncryption(parser.getCachedProvisioningProperties());
-                stopSelf(startId);
-                return START_NOT_STICKY;
-            }
-
-            // Do the work on a separate thread.
-            new Thread(new Runnable() {
-                    @Override
-                    public void run() {
-                        initializeProvisioningEnvironment(params);
-                        startDeviceOwnerProvisioning(params);
-                    }
-                }).start();
         }
         return START_NOT_STICKY;
     }
@@ -158,10 +155,8 @@
                         public void onSuccess() {
                             String downloadLocation =
                                     mDownloadPackageTask.getDownloadedPackageLocation();
-                            Runnable cleanupRunnable =
-                                    mDownloadPackageTask.getCleanUpDownloadRunnable();
                             progressUpdate(R.string.progress_install);
-                            mInstallPackageTask.run(downloadLocation, cleanupRunnable);
+                            mInstallPackageTask.run(downloadLocation);
                         }
 
                         @Override
@@ -255,15 +250,17 @@
     }
 
     private void error(int dialogMessage) {
+        mLastErrorMessage = dialogMessage;
+        sendError();
+        // Wait for stopService() call from the activity.
+    }
 
-        ProvisionLogger.logd("Reporting Error: " + getResources().getString(dialogMessage));
-
+    private void sendError() {
+        ProvisionLogger.logd("Reporting Error: " + getResources().getString(mLastErrorMessage));
         Intent intent = new Intent(ACTION_PROVISIONING_ERROR);
         intent.setClass(this, DeviceOwnerProvisioningActivity.ServiceMessageReceiver.class);
-        intent.putExtra(EXTRA_USER_VISIBLE_ERROR_ID_KEY, dialogMessage);
+        intent.putExtra(EXTRA_USER_VISIBLE_ERROR_ID_KEY, mLastErrorMessage);
         LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
-
-        stopSelf(mStartIdProvisioning);
     }
 
     private void progressUpdate(int progressMessage) {
@@ -280,33 +277,14 @@
         LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
     }
 
-    private void requestEncryption(String propertiesForResume) {
-        Bundle resumeExtras = new Bundle();
-        resumeExtras.putString(EncryptDeviceActivity.EXTRA_RESUME_TARGET,
-                EncryptDeviceActivity.TARGET_DEVICE_OWNER);
-        resumeExtras.putString(MessageParser.EXTRA_PROVISIONING_PROPERTIES,
-                propertiesForResume);
-        Intent intent = new Intent(ACTION_REQUEST_ENCRYPTION);
-        intent.putExtra(EncryptDeviceActivity.EXTRA_RESUME, resumeExtras);
-        sendBroadcast(intent);
-    }
 
     private void onProvisioningSuccess(String deviceAdminPackage) {
+        ProvisionLogger.logv("Reporting success.");
+        mDone = true;
         Intent successIntent = new Intent(ACTION_PROVISIONING_SUCCESS);
         successIntent.setClass(this, DeviceOwnerProvisioningActivity.ServiceMessageReceiver.class);
         LocalBroadcastManager.getInstance(this).sendBroadcast(successIntent);
-
-        // Skip the setup wizard.
-        Global.putInt(getContentResolver(), Global.DEVICE_PROVISIONED, 1);
-        Secure.putInt(getContentResolver(), Secure.USER_SETUP_COMPLETE, 1);
-
-        Intent completeIntent = new Intent(ACTION_PROFILE_PROVISIONING_COMPLETE);
-        completeIntent.setPackage(deviceAdminPackage);
-        completeIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES |
-            Intent.FLAG_RECEIVER_FOREGROUND);
-        sendBroadcast(completeIntent);
-
-        stopSelf(mStartIdProvisioning);
+        // Wait for stopService() call from the activity.
     }
 
     private void initializeProvisioningEnvironment(ProvisioningParams params) {
@@ -320,14 +298,14 @@
         startActivity(intent); // Activity will be a Nop if not a CDMA device.
     }
 
-    private void setTimeAndTimezone(String timeZone, Long localTime) {
+    private void setTimeAndTimezone(String timeZone, long localTime) {
         try {
             final AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
             if (timeZone != null) {
                 ProvisionLogger.logd("Setting time zone to " + timeZone);
                 am.setTimeZone(timeZone);
             }
-            if (localTime != null) {
+            if (localTime > 0) {
                 ProvisionLogger.logd("Setting time to " + localTime);
                 am.setTime(localTime);
             }
@@ -353,9 +331,18 @@
     }
 
     @Override
+    public void onCreate () {
+        ProvisionLogger.logd("Device owner provisioning service ONCREATE.");
+    }
+
+    @Override
     public void onDestroy () {
+        ProvisionLogger.logd("Device owner provisioning service ONDESTROY");
         if (mAddWifiNetworkTask != null) {
-            mAddWifiNetworkTask.unRegister();
+            mAddWifiNetworkTask.cleanUp();
+        }
+        if (mDownloadPackageTask != null) {
+            mDownloadPackageTask.cleanUp();
         }
     }
 
diff --git a/src/com/android/managedprovisioning/MessageParser.java b/src/com/android/managedprovisioning/MessageParser.java
index 98dc0ec..5e1383f 100644
--- a/src/com/android/managedprovisioning/MessageParser.java
+++ b/src/com/android/managedprovisioning/MessageParser.java
@@ -23,6 +23,7 @@
 import android.nfc.NdefRecord;
 import android.nfc.NfcAdapter;
 import android.os.Parcelable;
+import android.text.TextUtils;
 
 import java.io.IOException;
 import java.io.StringReader;
@@ -262,17 +263,17 @@
      */
     private void checkValidityOfProvisioningParams(ProvisioningParams params)
         throws ParseException  {
-        if (params.mDeviceAdminPackageName == null) {
+        if (TextUtils.isEmpty(params.mDeviceAdminPackageName)) {
             throw new ParseException("Must provide the name of the device admin package.",
                     R.string.device_owner_error_no_package_name);
         }
-        if (params.mDownloadLocation != null) {
-            if (params.mHash == null) {
+        if (!TextUtils.isEmpty(params.mDownloadLocation)) {
+            if (params.mHash == null || params.mHash.length == 0) {
                 throw new ParseException("Hash of installer file is required for downloading " +
                         "device admin file, but not provided.",
                         R.string.device_owner_error_no_hash);
             }
-            if (params.mWifiSsid == null) {
+            if (TextUtils.isEmpty(params.mWifiSsid)) {
                 throw new ParseException("Wifi ssid is required for downloading device admin " +
                         "file, but not provided.",
                         R.string.device_owner_error_no_wifi_ssid);
@@ -307,4 +308,4 @@
             return mErrorMessageId;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/managedprovisioning/ProvisioningParams.java b/src/com/android/managedprovisioning/ProvisioningParams.java
index 072a4ba..f4562d1 100644
--- a/src/com/android/managedprovisioning/ProvisioningParams.java
+++ b/src/com/android/managedprovisioning/ProvisioningParams.java
@@ -16,14 +16,16 @@
 
 package com.android.managedprovisioning;
 
+import android.os.Parcel;
+import android.os.Parcelable;
 import java.util.Locale;
 
 /**
  * Provisioning Parameters for DeviceOwner Provisioning
  */
-public class ProvisioningParams {
+public class ProvisioningParams implements Parcelable {
     public static String mTimeZone;
-    public static Long mLocalTime;
+    public static long mLocalTime = -1;
     public static Locale mLocale;
 
     public static String mWifiSsid;
@@ -39,5 +41,56 @@
     public static String mOwner; // Human readable name of the institution that owns this device.
 
     public static String mDownloadLocation; // Url where the device admin .apk is downloaded from.
-    public static byte[] mHash; // Hash of the .apk file (see {@link DownloadPackageTask).
+    public static byte[] mHash = new byte[0]; // Hash of the .apk file.
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(mTimeZone);
+        out.writeLong(mLocalTime);
+        out.writeSerializable(mLocale);
+        out.writeString(mWifiSsid);
+        out.writeInt(mWifiHidden ? 1 : 0);
+        out.writeString(mWifiSecurityType);
+        out.writeString(mWifiPassword);
+        out.writeString(mWifiProxyHost);
+        out.writeInt(mWifiProxyPort);
+        out.writeString(mWifiProxyBypassHosts);
+        out.writeString(mDeviceAdminPackageName);
+        out.writeString(mOwner);
+        out.writeString(mDownloadLocation);
+        out.writeByteArray(mHash);
+    }
+
+    public static final Parcelable.Creator<ProvisioningParams> CREATOR
+        = new Parcelable.Creator<ProvisioningParams>() {
+        @Override
+        public ProvisioningParams createFromParcel(Parcel in) {
+            ProvisioningParams params = new ProvisioningParams();
+            params.mTimeZone = in.readString();
+            params.mLocalTime = in.readLong();
+            params.mLocale = (Locale) in.readSerializable();
+            params.mWifiSsid = in.readString();
+            params.mWifiHidden = in.readInt()==1;
+            params.mWifiSecurityType = in.readString();
+            params.mWifiPassword = in.readString();
+            params.mWifiProxyHost = in.readString();
+            params.mWifiProxyPort = in.readInt();
+            params.mWifiProxyBypassHosts = in.readString();
+            params.mDeviceAdminPackageName = in.readString();
+            params.mOwner = in.readString();
+            params.mDownloadLocation = in.readString();
+            in.readByteArray(params.mHash);
+            return params;
+        }
+
+        @Override
+        public ProvisioningParams[] newArray(int size) {
+            return new ProvisioningParams[size];
+        }
+    };
 }
diff --git a/src/com/android/managedprovisioning/task/AddWifiNetworkTask.java b/src/com/android/managedprovisioning/task/AddWifiNetworkTask.java
index 6756bbc..20cf6d4 100644
--- a/src/com/android/managedprovisioning/task/AddWifiNetworkTask.java
+++ b/src/com/android/managedprovisioning/task/AddWifiNetworkTask.java
@@ -119,7 +119,7 @@
         }
     }
 
-    public void unRegister() {
+    public void cleanUp() {
         if (mNetworkMonitor != null) {
             mNetworkMonitor.close();
             mNetworkMonitor = null;
diff --git a/src/com/android/managedprovisioning/task/DownloadPackageTask.java b/src/com/android/managedprovisioning/task/DownloadPackageTask.java
index 387c8dc..7e79e52 100644
--- a/src/com/android/managedprovisioning/task/DownloadPackageTask.java
+++ b/src/com/android/managedprovisioning/task/DownloadPackageTask.java
@@ -34,7 +34,6 @@
 import java.io.InputStream;
 import java.io.IOException;
 import java.io.FileInputStream;
-import java.lang.Runnable;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
@@ -58,6 +57,7 @@
     private boolean mDoneDownloading;
     private String mDownloadLocationTo;
     private long mDownloadId;
+    private BroadcastReceiver mReceiver;
 
     public DownloadPackageTask (Context context, String downloadLocation, byte[] hash,
             Callback callback) {
@@ -73,7 +73,8 @@
     }
 
     public void run() {
-        mContext.registerReceiver(createDownloadReceiver(),
+        mReceiver = createDownloadReceiver();
+        mContext.registerReceiver(mReceiver,
                 new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
 
         ProvisionLogger.logd("Starting download from " + mDownloadLocationFrom);
@@ -96,13 +97,11 @@
                     if (c.moveToFirst()) {
                         int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
                         if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
-                            mContext.unregisterReceiver(this);
                             String location = c.getString(
                                     c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
                             c.close();
                             onDownloadSuccess(location);
                         } else if (DownloadManager.STATUS_FAILED == c.getInt(columnIndex)){
-                            mContext.unregisterReceiver(this);
                             int reason = c.getColumnIndex(DownloadManager.COLUMN_REASON);
                             c.close();
                             onDownloadFail(reason);
@@ -197,21 +196,23 @@
         return mDownloadLocationTo;
     }
 
-    public Runnable getCleanUpDownloadRunnable() {
-        return new Runnable() {
-            @Override
-            public void run() {
-                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.
-                }
+    public void cleanUp() {
+        if (mReceiver != null) {
+            //Unregister receiver.
+            mContext.unregisterReceiver(mReceiver);
+            mReceiver = null;
+
+            //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 logging purposes only.
diff --git a/src/com/android/managedprovisioning/task/InstallPackageTask.java b/src/com/android/managedprovisioning/task/InstallPackageTask.java
index 45b9ce9..6d38b2a 100644
--- a/src/com/android/managedprovisioning/task/InstallPackageTask.java
+++ b/src/com/android/managedprovisioning/task/InstallPackageTask.java
@@ -29,7 +29,6 @@
 import com.android.managedprovisioning.ProvisionLogger;
 
 import java.io.File;
-import java.lang.Runnable;
 
 /**
  * Installs a device owner package from a given path.
@@ -41,7 +40,6 @@
 public class InstallPackageTask {
     public static final int ERROR_PACKAGE_INVALID = 0;
     public static final int ERROR_INSTALLATION_FAILED = 1;
-    public static final int ERROR_OTHER = 2;
 
     private final Context mContext;
     private final Callback mCallback;
@@ -49,7 +47,6 @@
 
     private String mPackageLocation;
     private PackageManager mPm;
-    private Runnable mCleanUpDownloadRunnable;
     private int mPackageVerifierEnable;
 
     public InstallPackageTask (Context context, String packageName,
@@ -60,14 +57,13 @@
         mPackageName = packageName;
     }
 
-    public void run(String packageLocation, Runnable cleanUpDownloadRunnable) {
+    public void run(String packageLocation) {
         if (TextUtils.isEmpty(packageLocation)) {
             ProvisionLogger.loge("Package Location is empty.");
             mCallback.onError(ERROR_PACKAGE_INVALID);
             return;
         }
         mPackageLocation = packageLocation;
-        mCleanUpDownloadRunnable = cleanUpDownloadRunnable;
 
         PackageInstallObserver observer = new PackageInstallObserver();
         mPm = mContext.getPackageManager();
@@ -117,25 +113,22 @@
     private class PackageInstallObserver extends IPackageInstallObserver.Stub {
         @Override
         public void packageInstalled(String packageName, int returnCode) {
+            if (!packageName.equals(mPackageName)) {
+                return;
+            }
+
             // Set package verification flag to its original value.
             Global.putInt(mContext.getContentResolver(), Global.PACKAGE_VERIFIER_ENABLE,
                     mPackageVerifierEnable);
 
-            if (!packageName.equals(mPackageName)) {
-                ProvisionLogger.loge("Something went wrong: Installed package " + packageName
-                        + " and not " + mPackageName + ".");
-                mCallback.onError(ERROR_OTHER);
-                return;
-            }
             if (returnCode == PackageManager.INSTALL_SUCCEEDED) {
                 ProvisionLogger.logd("Package " + packageName + " is succesfully installed.");
-                mCleanUpDownloadRunnable.run();
+
                 mCallback.onSuccess();
             } else {
                 ProvisionLogger.logd("Installing package " + packageName + " failed.");
                 ProvisionLogger.logd("Errorcode returned by IPackageInstallObserver = "
                         + returnCode);
-                mCleanUpDownloadRunnable.run();
                 mCallback.onError(ERROR_INSTALLATION_FAILED);
             }
         }