Moved functionality of DeviceOwnerProvActivity into service.

Service reports back to activity by broadcasts to a broadcast receiver.
Reporting can consist of either an error code or a success.

Change-Id: Ie8e916bd7e05f3179aca6c42f05103365ebc6b35
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 5072ecf..6a409e0 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -87,5 +87,8 @@
             android:launchMode="singleTop"
             android:screenOrientation="nosensor" >
         </activity>
+        <service
+            android:name="DeviceOwnerProvisioningService" >
+        </service>
   </application>
 </manifest>
diff --git a/res/layout/progress_device_owner.xml b/res/layout/progress_device_owner.xml
index 695c756..d09fff3 100644
--- a/res/layout/progress_device_owner.xml
+++ b/res/layout/progress_device_owner.xml
@@ -44,7 +44,6 @@
         android:layout_width="fill_parent"
         android:layout_height="fill_parent"
         android:gravity="center"
-        android:text="@string/device_owner_subtitle"
         android:textSize="30sp" />
 
 </LinearLayout>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 42412f7..b701c42 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -52,8 +52,16 @@
 
   <!-- Device owner provisioning flow UI. -->
 
-  <!-- Placeholder progress subtitle. [CHAR LIMIT=45] -->
-  <string name="device_owner_subtitle">Placeholder text.</string>
+  <!-- Progress text indicating that Nfc message is being processed. [CHAR LIMIT=45] -->
+  <string name="progress_processing_nfc">Processing Nfc message.</string>
+  <!-- Progress text indicating that wifi is set up. [CHAR LIMIT=45] -->
+  <string name="progress_connect_to_wifi">Connecting to Wifi.</string>
+  <!-- Progress text indicating that the device admin package is being downloaded. [CHAR LIMIT=45] -->
+  <string name="progress_download">Downloading Device Admin package.</string>
+  <!-- Progress text indicating that the device admin package is being installed. [CHAR LIMIT=45] -->
+  <string name="progress_install">Installing Device Admin package.</string>
+  <!-- Progress text indicating that the device admin package is set as ownder. [CHAR LIMIT=45] -->
+  <string name="progress_set_owner">Setting Device Owner.</string>
 
   <!-- Title of the error dialog. [CHAR LIMIT=45] -->
   <string name="device_owner_error_title">Provisioning failed</string>
diff --git a/src/com/android/managedprovisioning/DeviceOwnerProvisioningActivity.java b/src/com/android/managedprovisioning/DeviceOwnerProvisioningActivity.java
index 5f28079..a73cbc6 100644
--- a/src/com/android/managedprovisioning/DeviceOwnerProvisioningActivity.java
+++ b/src/com/android/managedprovisioning/DeviceOwnerProvisioningActivity.java
@@ -20,25 +20,16 @@
 import android.app.AlarmManager;
 import android.app.AlertDialog;
 import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.pm.PackageManager;
+import android.content.IntentFilter;
 import android.os.Bundle;
 import android.provider.Settings.Global;
-import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.View;
-
-import com.android.internal.app.LocalePicker;
-import com.android.managedprovisioning.task.AddWifiNetworkTask;
-import com.android.managedprovisioning.task.DownloadPackageTask;
-import com.android.managedprovisioning.task.InstallPackageTask;
-import com.android.managedprovisioning.task.SetDevicePolicyTask;
-
-import java.lang.Runnable;
-import java.util.Locale;
+import android.widget.TextView;
 
 /**
  * This activity starts device owner provisioning:
@@ -65,6 +56,9 @@
  * </p>
  */
 public class DeviceOwnerProvisioningActivity extends Activity {
+    private BroadcastReceiver mServiceMessageReceiver;
+    private TextView mProgressTextView;
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -86,180 +80,53 @@
             return;
         }
 
-        // Load the ProvisioningParams (from Nfc message in Intent).
-        ProvisioningParams params;
-        NfcMessageParser parser = new NfcMessageParser();
-        Intent bumpData = getIntent();
-        try {
-            params = parser.parseNfcIntent(bumpData);
-        } catch (NfcMessageParser.ParseException e) {
-            ProvisionLogger.loge("Could not read Nfc data from intent", e);
-            error(e.getErrorMessageId());
-            return;
-        }
-
-        initializeProvisioningEnvironment(params);
-
-        // TODO: update UI
         final LayoutInflater inflater = getLayoutInflater();
         final View contentView = inflater.inflate(R.layout.progress_device_owner, null);
         setContentView(contentView);
+        mProgressTextView = (TextView) findViewById(R.id.dev_owner_prog_text);
 
-        startDeviceOwnerProvisioning(params);
+        // Setup broadcast receiver for feedback from service.
+        mServiceMessageReceiver = new ServiceMessageReceiver();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(DeviceOwnerProvisioningService.ACTION_PROVISIONING_SUCCESS);
+        filter.addAction(DeviceOwnerProvisioningService.ACTION_PROVISIONING_ERROR);
+        filter.addAction(DeviceOwnerProvisioningService.ACTION_PROGRESS_UPDATE);
+        registerReceiver(mServiceMessageReceiver, filter);
+
+        // Start service.
+        Intent intent = new Intent(this, DeviceOwnerProvisioningService.class);
+        intent.putExtras(getIntent());
+        startService(intent);
     }
 
-    /**
-     * This is the core method of this class. It goes through every provisioning step.
-     */
-    private void startDeviceOwnerProvisioning(ProvisioningParams params) {
-        ProvisionLogger.logd("Starting device owner provisioning");
-
-        // Construct Tasks. Do not start them yet.
-        final AddWifiNetworkTask addWifiNetworkTask = new AddWifiNetworkTask(this, params.mWifiSsid,
-                params.mWifiHidden, params.mWifiSecurityType, params.mWifiPassword,
-                params.mWifiProxyHost, params.mWifiProxyPort, params.mWifiProxyBypassHosts);
-        final DownloadPackageTask downloadPackageTask = new DownloadPackageTask(this,
-                params.mDownloadLocation, params.mHash);
-        final InstallPackageTask installPackageTask = new InstallPackageTask(this,
-                params.mDeviceAdminPackageName, params.mAdminReceiver);
-        final SetDevicePolicyTask setDevicePolicyTask = new SetDevicePolicyTask(this,
-                params.mDeviceAdminPackageName, params.mAdminReceiver, params.mOwner);
-
-        // Set callbacks.
-        addWifiNetworkTask.setCallback(new AddWifiNetworkTask.Callback() {
-            @Override
-            public void onSuccess() {
-                if (downloadPackageTask.downloadLocationWasProvided()) {
-                    downloadPackageTask.run();
+    private class ServiceMessageReceiver extends BroadcastReceiver
+    {
+        @Override
+        public void onReceive(Context context, Intent intent)
+        {
+            String action = intent.getAction();
+            if (action.equals(DeviceOwnerProvisioningService.ACTION_PROVISIONING_SUCCESS)) {
+                ProvisionLogger.logd("Successfully provisioned");
+                finish();
+                return;
+            } else if (action.equals(DeviceOwnerProvisioningService.ACTION_PROVISIONING_ERROR)) {
+                int errorCode = intent.getIntExtra(
+                        DeviceOwnerProvisioningService.EXTRA_PROVISIONING_ERROR_ID_KEY, -1);
+                ProvisionLogger.logd("Error reported with code " + errorCode);
+                if (errorCode < 0) {
+                    error(R.string.device_owner_error_general);
+                    return;
                 } else {
-                    // TODO: replace success by starting the next task.
-                    onProvisioningSuccess();
+                    error(errorCode);
+                }
+            } else if (action.equals(DeviceOwnerProvisioningService.ACTION_PROGRESS_UPDATE)) {
+                int progressMessage = intent.getIntExtra(
+                        DeviceOwnerProvisioningService.EXTRA_PROGRESS_MESSAGE_ID_KEY, -1);
+                ProvisionLogger.logd("Progress update reported with code " + progressMessage);
+                if (progressMessage > 0) {
+                    progressUpdate(progressMessage);
                 }
             }
-
-            @Override
-            public void onError(){
-                error(R.string.device_owner_error_wifi);
-            }
-        });
-
-        downloadPackageTask.setCallback(new DownloadPackageTask.Callback() {
-            @Override
-            public void onSuccess() {
-                String downloadLocation = downloadPackageTask.getDownloadedPackageLocation();
-                Runnable cleanupRunnable = downloadPackageTask.getCleanUpDownloadRunnable();
-                installPackageTask.run(downloadLocation, cleanupRunnable);
-            }
-
-            @Override
-            public void onError(int errorCode) {
-                switch(errorCode) {
-                    case DownloadPackageTask.ERROR_HASH_MISMATCH:
-                        error(R.string.device_owner_error_hash_mismatch);
-                        break;
-                    case DownloadPackageTask.ERROR_DOWNLOAD_FAILED:
-                        error(R.string.device_owner_error_download_failed);
-                        break;
-                    default:
-                        error(R.string.device_owner_error_general);
-                        break;
-                }
-            }
-        });
-
-        installPackageTask.setCallback(new InstallPackageTask.Callback() {
-            @Override
-            public void onSuccess() {
-                setDevicePolicyTask.run();
-            }
-
-            @Override
-            public void onError(int errorCode) {
-                switch(errorCode) {
-                    case InstallPackageTask.ERROR_PACKAGE_INVALID:
-                        error(R.string.device_owner_error_package_invalid);
-                        break;
-                    case InstallPackageTask.ERROR_INSTALLATION_FAILED:
-                        error(R.string.device_owner_error_installation_failed);
-                        break;
-                    default:
-                        error(R.string.device_owner_error_general);
-                        break;
-                }
-            }
-        });
-
-        setDevicePolicyTask.setCallback(new SetDevicePolicyTask.Callback() {
-            public void onSuccess() {
-                // Done with provisioning. Success.
-                onProvisioningSuccess();
-            }
-            public void onError(int errorCode) {
-                switch(errorCode) {
-                    case SetDevicePolicyTask.ERROR_PACKAGE_NOT_INSTALLED:
-                        error(R.string.device_owner_error_package_not_installed);
-                        break;
-                    default:
-                        error(R.string.device_owner_error_general);
-                        break;
-                }
-            }
-        });
-
-
-        // Start first task, which starts next task in its callback, etc.
-        if (addWifiNetworkTask.wifiCredentialsWereProvided()) {
-            addWifiNetworkTask.run();
-        } else {
-            setDevicePolicyTask.run();
-        }
-    }
-
-    public void onProvisioningSuccess() {
-
-        // This package is no longer needed by the system, so disable it.
-        PackageManager pkgMgr = getPackageManager();
-        pkgMgr.setComponentEnabledSetting(
-                new ComponentName(getPackageName(), getClass().getName()),
-                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
-                PackageManager.DONT_KILL_APP);
-        finish();
-    }
-
-    private void initializeProvisioningEnvironment(ProvisioningParams params) {
-        setTimeAndTimezone(params.mTimeZone, params.mLocalTime);
-        setLocale(params.mLocale);
-    }
-
-    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) {
-                ProvisionLogger.logd("Setting time to " + localTime);
-                am.setTime(localTime);
-            }
-        } catch (Exception e) {
-            ProvisionLogger.loge("Alarm manager failed to set the system time/timezone.");
-            // Do not stop provisioning process, but ignore this error.
-        }
-    }
-
-    private void setLocale(Locale locale) {
-        if (locale == null || locale.equals(Locale.getDefault())) {
-            return;
-        }
-        try {
-            ProvisionLogger.logd("Setting locale to " + locale);
-            // If locale is different from current locale this results in a configuration change,
-            // which will trigger the restarting of the activity.
-            LocalePicker.updateLocale(locale);
-        } catch (Exception e) {
-            ProvisionLogger.loge("Failed to set the system locale.");
-            // Do not stop provisioning process, but ignore this error.
         }
     }
 
@@ -268,7 +135,17 @@
         // TODO: Handle this graciously by stopping the provisioning flow and cleaning up.
     }
 
-    public void error(int dialogMessage) {
+    @Override
+    public void onDestroy() {
+        unregisterReceiver(mServiceMessageReceiver);
+        super.onDestroy();
+    }
+
+    private void progressUpdate(int progressMessage) {
+        mProgressTextView.setText(progressMessage);
+    }
+
+    private void error(int dialogMessage) {
         AlertDialog dlg = new AlertDialog.Builder(this)
             .setTitle(R.string.device_owner_error_title)
             .setMessage(dialogMessage)
diff --git a/src/com/android/managedprovisioning/DeviceOwnerProvisioningService.java b/src/com/android/managedprovisioning/DeviceOwnerProvisioningService.java
new file mode 100644
index 0000000..127825c
--- /dev/null
+++ b/src/com/android/managedprovisioning/DeviceOwnerProvisioningService.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.managedprovisioning;
+
+import android.app.AlarmManager;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.text.TextUtils;
+
+import com.android.internal.app.LocalePicker;
+import com.android.managedprovisioning.task.AddWifiNetworkTask;
+import com.android.managedprovisioning.task.DownloadPackageTask;
+import com.android.managedprovisioning.task.InstallPackageTask;
+import com.android.managedprovisioning.task.SetDevicePolicyTask;
+
+import java.lang.Runnable;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This service does the work for the DeviceOwnerProvisioningActivity.
+ * Feedback is sent back to the activity via intents.
+ *
+ * <p>
+ * If the corresponding activity is killed and restarted, the service is
+ * called twice. The service will not start the provisioning flow a second time, but instead
+ * send a status update to the activity.
+ * </p>
+ */
+public class DeviceOwnerProvisioningService extends Service {
+    public static final String ACTION_PROVISIONING_SUCCESS =
+            "com.android.managedprovisioning.provisioning_success";
+    public static final String ACTION_PROVISIONING_ERROR =
+            "com.android.managedprovisioning.error";
+    public static final String EXTRA_PROVISIONING_ERROR_ID_KEY = "ErrorMessageId";
+    public static final String ACTION_PROGRESS_UPDATE =
+            "com.android.managedprovisioning.progress_update";
+    public static final String EXTRA_PROGRESS_MESSAGE_ID_KEY = "ProgressMessageId";
+
+    private AtomicBoolean mProvisioningInFlight = new AtomicBoolean(false);
+    private int mLastProgressMessage;
+    private int mStartIdProvisioning;
+
+    @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 {
+            mStartIdProvisioning = startId;
+
+            // Do the work on a separate thread.
+            new Thread(new Runnable() {
+                    public void run() {
+                        // Load the ProvisioningParams (from Nfc message in Intent).
+                        progressUpdate(R.string.progress_processing_nfc);
+                        ProvisioningParams params;
+                        NfcMessageParser parser = new NfcMessageParser();
+                        try {
+                            params = parser.parseNfcIntent(intent);
+                            initializeProvisioningEnvironment(params);
+                            startDeviceOwnerProvisioning(params);
+                        } catch (NfcMessageParser.ParseException e) {
+                            ProvisionLogger.loge("Could not read Nfc data from intent", e);
+                            error(e.getErrorMessageId());
+                            return;
+                        }
+                    }
+                }).start();
+        }
+        return START_NOT_STICKY;
+    }
+
+    /**
+     * This is the core method of this class. It goes through every provisioning step.
+     */
+    private void startDeviceOwnerProvisioning(ProvisioningParams params) {
+        ProvisionLogger.logd("Starting device owner provisioning");
+
+        // Construct Tasks. Do not start them yet.
+        final AddWifiNetworkTask addWifiNetworkTask = new AddWifiNetworkTask(this, params.mWifiSsid,
+                params.mWifiHidden, params.mWifiSecurityType, params.mWifiPassword,
+                params.mWifiProxyHost, params.mWifiProxyPort, params.mWifiProxyBypassHosts);
+        final DownloadPackageTask downloadPackageTask = new DownloadPackageTask(this,
+                params.mDownloadLocation, params.mHash);
+        final InstallPackageTask installPackageTask = new InstallPackageTask(this,
+                params.mDeviceAdminPackageName, params.mAdminReceiver);
+        final SetDevicePolicyTask setDevicePolicyTask = new SetDevicePolicyTask(this,
+                params.mDeviceAdminPackageName, params.mAdminReceiver, params.mOwner);
+
+        // Set callbacks.
+        addWifiNetworkTask.setCallback(new AddWifiNetworkTask.Callback() {
+            @Override
+            public void onSuccess() {
+                if (downloadPackageTask.downloadLocationWasProvided()) {
+                    progressUpdate(R.string.progress_download);
+                    downloadPackageTask.run();
+                } else {
+                    progressUpdate(R.string.progress_set_owner);
+                    setDevicePolicyTask.run();
+                }
+            }
+
+            @Override
+            public void onError(){
+                error(R.string.device_owner_error_wifi);
+            }
+        });
+
+        downloadPackageTask.setCallback(new DownloadPackageTask.Callback() {
+            @Override
+            public void onSuccess() {
+                String downloadLocation = downloadPackageTask.getDownloadedPackageLocation();
+                Runnable cleanupRunnable = downloadPackageTask.getCleanUpDownloadRunnable();
+                progressUpdate(R.string.progress_install);
+                installPackageTask.run(downloadLocation, cleanupRunnable);
+            }
+
+            @Override
+            public void onError(int errorCode) {
+                switch(errorCode) {
+                    case DownloadPackageTask.ERROR_HASH_MISMATCH:
+                        error(R.string.device_owner_error_hash_mismatch);
+                        break;
+                    case DownloadPackageTask.ERROR_DOWNLOAD_FAILED:
+                        error(R.string.device_owner_error_download_failed);
+                        break;
+                    default:
+                        error(R.string.device_owner_error_general);
+                        break;
+                }
+            }
+        });
+
+        installPackageTask.setCallback(new InstallPackageTask.Callback() {
+            @Override
+            public void onSuccess() {
+                progressUpdate(R.string.progress_set_owner);
+                setDevicePolicyTask.run();
+            }
+
+            @Override
+            public void onError(int errorCode) {
+                switch(errorCode) {
+                    case InstallPackageTask.ERROR_PACKAGE_INVALID:
+                        error(R.string.device_owner_error_package_invalid);
+                        break;
+                    case InstallPackageTask.ERROR_INSTALLATION_FAILED:
+                        error(R.string.device_owner_error_installation_failed);
+                        break;
+                    default:
+                        error(R.string.device_owner_error_general);
+                        break;
+                }
+            }
+        });
+
+        setDevicePolicyTask.setCallback(new SetDevicePolicyTask.Callback() {
+            public void onSuccess() {
+                // Done with provisioning. Success.
+                onProvisioningSuccess();
+            }
+            public void onError(int errorCode) {
+                switch(errorCode) {
+                    case SetDevicePolicyTask.ERROR_PACKAGE_NOT_INSTALLED:
+                        error(R.string.device_owner_error_package_not_installed);
+                        break;
+                    default:
+                        error(R.string.device_owner_error_general);
+                        break;
+                }
+            }
+        });
+
+
+        // Start first task, which starts next task in its callback, etc.
+        if (addWifiNetworkTask.wifiCredentialsWereProvided()) {
+            progressUpdate(R.string.progress_connect_to_wifi);
+            addWifiNetworkTask.run();
+        } else {
+            progressUpdate(R.string.progress_set_owner);
+            setDevicePolicyTask.run();
+        }
+    }
+
+    private void error(int dialogMessage) {
+        ProvisionLogger.logd("Reporting Error with code " + dialogMessage);
+        Intent intent = new Intent(ACTION_PROVISIONING_ERROR);
+        intent.putExtra(EXTRA_PROVISIONING_ERROR_ID_KEY, dialogMessage);
+        sendBroadcast(intent);
+        stopSelf(mStartIdProvisioning);
+    }
+
+    private void progressUpdate(int progressMessage) {
+        ProvisionLogger.logd("Reporting progress update with code " + progressMessage);
+        mLastProgressMessage = progressMessage;
+        sendProgressUpdateToActivity();
+    }
+
+    private void sendProgressUpdateToActivity() {
+        Intent intent = new Intent(ACTION_PROGRESS_UPDATE);
+        intent.putExtra(EXTRA_PROGRESS_MESSAGE_ID_KEY, mLastProgressMessage);
+        sendBroadcast(intent);
+    }
+
+    private void onProvisioningSuccess() {
+        sendBroadcast(new Intent(ACTION_PROVISIONING_SUCCESS));
+        stopSelf(mStartIdProvisioning);
+    }
+
+    private void initializeProvisioningEnvironment(ProvisioningParams params) {
+        setTimeAndTimezone(params.mTimeZone, params.mLocalTime);
+        setLocale(params.mLocale);
+    }
+
+    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) {
+                ProvisionLogger.logd("Setting time to " + localTime);
+                am.setTime(localTime);
+            }
+        } catch (Exception e) {
+            ProvisionLogger.loge("Alarm manager failed to set the system time/timezone.");
+            // Do not stop provisioning process, but ignore this error.
+        }
+    }
+
+    private void setLocale(Locale locale) {
+        if (locale == null || locale.equals(Locale.getDefault())) {
+            return;
+        }
+        try {
+            ProvisionLogger.logd("Setting locale to " + locale);
+            // If locale is different from current locale this results in a configuration change,
+            // which will trigger the restarting of the activity.
+            LocalePicker.updateLocale(locale);
+        } catch (Exception e) {
+            ProvisionLogger.loge("Failed to set the system locale.");
+            // Do not stop provisioning process, but ignore this error.
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+}
+