Merge "Launch managed provisioning for secondary users"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f14901d..07c3f68 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -133,7 +133,9 @@
   <!-- Message of the error dialog in case of an unspecified error. [CHAR LIMIT=NONE] -->
   <string name="device_owner_error_general">Couldn\'t set up your device. Contact your IT department. </string>
   <!-- Message of the error dialog when already provisioned. [CHAR LIMIT=NONE] -->
-  <string name="device_owner_error_already_provisioned">This device is already set up</string>
+  <string name="device_owner_error_already_provisioned">This device is already set up.</string>
+  <!-- Message of the error dialog when a secondary user has already been provisioned. [CHAR LIMIT=NONE] -->
+  <string name="device_owner_error_already_provisioned_user">This device is already set up for this user.</string>
   <!-- Message of the error dialog when setting up wifi failed. [CHAR LIMIT=NONE] -->
   <string name="device_owner_error_wifi">Couldn\'t connect to Wi-Fi</string>
   <!-- Message of the error dialog when data integrity check fails. [CHAR LIMIT=NONE] -->
@@ -142,6 +144,8 @@
   <string name="device_owner_error_download_failed">Couldn\'t download the admin app</string>
   <!-- Message of the error dialog when package to install is invalid. [CHAR LIMIT=NONE] -->
   <string name="device_owner_error_package_invalid">Can\'t use the admin app. It\'s missing components or corrupted. Contact your IT department.</string>
+  <!-- Message of the error dialog when the name of the admin package to be installed is invalid. [CHAR LIMIT=NONE] -->
+  <string name="device_owner_error_package_name_invalid">Can\'t install the admin app. The package name is invalid. Contact your IT department.</string>
   <!-- Message of the error dialog when package could not be installed. [CHAR LIMIT=NONE] -->
   <string name="device_owner_error_installation_failed">Couldn\'t install the admin app</string>
   <!-- Message of the error dialog when device admin package is not installed. [CHAR LIMIT=NONE] -->
diff --git a/src/com/android/managedprovisioning/DeviceOwnerProvisioningActivity.java b/src/com/android/managedprovisioning/DeviceOwnerProvisioningActivity.java
index 646fc83..75945e2 100644
--- a/src/com/android/managedprovisioning/DeviceOwnerProvisioningActivity.java
+++ b/src/com/android/managedprovisioning/DeviceOwnerProvisioningActivity.java
@@ -121,20 +121,28 @@
         if (titleText != null) titleText.setText(getString(R.string.setup_work_device));
         if (mCancelDialogShown) showCancelResetDialog();
 
-        // Check whether we can provision.
-        if (Global.getInt(getContentResolver(), Global.DEVICE_PROVISIONED, 0 /* default */) != 0) {
-            ProvisionLogger.loge("Device already provisioned.");
-            error(R.string.device_owner_error_already_provisioned, false /* no factory reset */);
-            return;
+        // Check whether we have already provisioned this user.
+        if (Utils.isCurrentUserOwner()) {
+            int provisioned =
+                    Global.getInt(getContentResolver(), Global.DEVICE_PROVISIONED, 0 /*default*/);
+            if (provisioned != 0) {
+                ProvisionLogger.loge("Device already provisioned.");
+                error(R.string.device_owner_error_already_provisioned,
+                        false /* no factory reset */);
+                return;
+            }
+        } else {
+            int provisioned =
+                    Secure.getInt(getContentResolver(), Secure.USER_SETUP_COMPLETE, 0 /*default*/);
+            if (provisioned != 0) {
+                ProvisionLogger.loge("User already provisioned.");
+                error(R.string.device_owner_error_already_provisioned_user,
+                        false /* no factory reset */);
+                return;
+            }
         }
 
-        if (!Utils.isCurrentUserOwner()) {
-            ProvisionLogger.loge("Device owner can only be set up for USER_OWNER.");
-            error(R.string.device_owner_error_general, false /* no factory reset */);
-            return;
-        }
-
-        if (factoryResetProtected()) {
+        if (Utils.isCurrentUserOwner() && factoryResetProtected()) {
             ProvisionLogger.loge("Factory reset protection blocks provisioning.");
             error(R.string.device_owner_error_already_provisioned, false /* no factory reset */);
             return;
@@ -191,7 +199,7 @@
     }
 
     private void showInterstitialAndProvision(final ProvisioningParams params) {
-        if (mUserConsented || params.mStartedByNfc) {
+        if (mUserConsented || params.mStartedByNfc || !Utils.isCurrentUserOwner()) {
             startDeviceOwnerProvisioningService(params);
         } else {
             // Notify the user that the admin will have full control over the device,
@@ -294,10 +302,19 @@
     }
 
     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);
+        if (Utils.isCurrentUserOwner()) {
+            // This only needs to be set once per device
+            Global.putInt(getContentResolver(), Global.DEVICE_PROVISIONED, 1);
+        }
+
+        // Setting this flag will either cause Setup Wizard to finish immediately when it starts (if
+        // it is not already running), or when its next activity starts (if it is already running,
+        // e.g. the non-NFC flow).
+        // When either of these things happen, a home intent is fired. We catch that in
+        // HomeReceiverActivity before sending the intent to notify the mdm that provisioning is
+        // complete.
+        // Note that, in the NFC flow or for secondary users, setting this flag will prevent the
+        // user from seeing SUW, even if no other device initialization app was specified.
         Secure.putInt(getContentResolver(), Secure.USER_SETUP_COMPLETE, 1);
     }
 
diff --git a/src/com/android/managedprovisioning/DeviceOwnerProvisioningService.java b/src/com/android/managedprovisioning/DeviceOwnerProvisioningService.java
index 06808a7..d6492cc 100644
--- a/src/com/android/managedprovisioning/DeviceOwnerProvisioningService.java
+++ b/src/com/android/managedprovisioning/DeviceOwnerProvisioningService.java
@@ -222,6 +222,9 @@
                             case InstallPackageTask.ERROR_INSTALLATION_FAILED:
                                 error(R.string.device_owner_error_installation_failed);
                                 break;
+                            case InstallPackageTask.ERROR_PACKAGE_NAME_INVALID:
+                                error(R.string.device_owner_error_package_name_invalid);
+                                break;
                             default:
                                 error(R.string.device_owner_error_general);
                                 break;
diff --git a/src/com/android/managedprovisioning/InstallCertRequestReceiver.java b/src/com/android/managedprovisioning/InstallCertRequestReceiver.java
index aff0e6d..246726d 100644
--- a/src/com/android/managedprovisioning/InstallCertRequestReceiver.java
+++ b/src/com/android/managedprovisioning/InstallCertRequestReceiver.java
@@ -33,10 +33,8 @@
     @Override
     public void onReceive(Context context, Intent intent) {
         ProvisionLogger.logi("Got ca cert request.");
-        DevicePolicyManager dpm =
-                (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
 
-        if (Utils.isCurrentUserOwner() && !TextUtils.isEmpty(dpm.getDeviceOwner()) &&
+        if (Utils.isCurrentUserOwner() && Utils.hasDeviceOwner(context) &&
                 REQUEST_CERT_ACTION.equals(intent.getAction())) {
             CertService.startService(context, intent);
         }
diff --git a/src/com/android/managedprovisioning/UserInitializedReceiver.java b/src/com/android/managedprovisioning/UserInitializedReceiver.java
index 30696d6..4d75a9f 100644
--- a/src/com/android/managedprovisioning/UserInitializedReceiver.java
+++ b/src/com/android/managedprovisioning/UserInitializedReceiver.java
@@ -16,25 +16,115 @@
 
 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 static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION;
+import static android.Manifest.permission.BIND_DEVICE_ADMIN;
+
+import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.UserHandle;
+import android.text.TextUtils;
+
+import java.util.List;
 
 /**
- * Sends a broadcast to the primary user to request CA certs. Runs on secondary users on user
- * initialization.
- */
+ * On secondary user initialization, send a broadcast to the primary user to request CA certs.
+ * Also, if this device has a Device Owner, send an intent to start managed provisioning.
+  */
 public class UserInitializedReceiver extends BroadcastReceiver {
+
+    private static final String MP_PACKAGE_NAME = "com.android.managedprovisioning";
+    private static final String MP_ACTIVITY_NAME =
+            "com.android.managedprovisioning.DeviceOwnerProvisioningActivity";
+
     @Override
     public void onReceive(Context context, Intent receivedIntent) {
         ProvisionLogger.logi("User is initialized");
-        if (!Utils.isCurrentUserOwner()) {
-            Intent intent = new Intent(InstallCertRequestReceiver.REQUEST_CERT_ACTION);
-            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-            intent.putExtra(CertService.EXTRA_REQUESTING_USER, Process.myUserHandle());
-            context.sendBroadcastAsUser(intent, UserHandle.OWNER);
+        if (!Utils.isCurrentUserOwner() && !Utils.isManagedProfile(context)) {
+            requestCACerts(context);
+
+            if (Utils.hasDeviceOwner(context)) {
+                ProvisionLogger.logi("Initializing secondary user with a device owner. " +
+                        "Starting managed provisioning.");
+                launchManagedProvisioning(context);
+            }
         }
     }
+
+    private void requestCACerts(Context context) {
+        Intent intent = new Intent(InstallCertRequestReceiver.REQUEST_CERT_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        intent.putExtra(CertService.EXTRA_REQUESTING_USER, Process.myUserHandle());
+        context.sendBroadcastAsUser(intent, UserHandle.OWNER);
+    }
+
+    /**
+     * Construct an appropriate intent and launch managed provisioning for a secondary user.
+     */
+    private void launchManagedProvisioning(Context context) {
+        DevicePolicyManager dpm = (DevicePolicyManager)
+                context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+
+        Intent startMpIntent = new Intent(context, DeviceOwnerProvisioningActivity.class);
+        startMpIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        startMpIntent.putExtra(
+                EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME, dpm.getDeviceOwner());
+
+        ComponentName diComponentName = getDeviceInitializerComponentName(
+                dpm.getDeviceInitializerApp(), context);
+        if (diComponentName != null) {
+            startMpIntent.putExtra(
+                    EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME,
+                    diComponentName);
+        }
+
+        // Rely on DPC to disable any system apps that need to be turned off
+        startMpIntent.putExtra(EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED, true);
+
+        // For secondary users, if the device needs to be encrypted, it has already happened
+        startMpIntent.putExtra(EXTRA_PROVISIONING_SKIP_ENCRYPTION, true);
+        ProvisionLogger.logd("Sending intent to start managed provisioning");
+        context.startActivity(startMpIntent);
+    }
+
+    /**
+     * Find the name of the device initializer component within the given package. It must be a
+     * broadcast receiver with ACTION_READY_FOR_USER_INITIALIZATION and the BIND_DEVICE_OWNER
+     * permission.
+     * @param deviceInitializerPackageName The package to check
+     * @return The ComponentName for the DI, or null if an appropriate component couldn't be found
+     */
+    private ComponentName getDeviceInitializerComponentName(String deviceInitializerPackageName,
+            Context context) {
+
+        if (!TextUtils.isEmpty(deviceInitializerPackageName)) {
+            Intent findDeviceInitIntent = new Intent(ACTION_READY_FOR_USER_INITIALIZATION);
+            findDeviceInitIntent.setPackage(deviceInitializerPackageName);
+
+            PackageManager pm = context.getPackageManager();
+            List<ResolveInfo> results;
+            results = pm.queryBroadcastReceivers(findDeviceInitIntent,
+                    PackageManager.GET_DISABLED_COMPONENTS, UserHandle.USER_OWNER);
+
+            for (ResolveInfo result : results) {
+                if (result.activityInfo.permission != null &&
+                        result.activityInfo.permission.equals(BIND_DEVICE_ADMIN)) {
+                    return new ComponentName(
+                            result.activityInfo.packageName, result.activityInfo.name);
+                }
+            }
+        }
+        return null;
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/managedprovisioning/Utils.java b/src/com/android/managedprovisioning/Utils.java
index 65c590b..8ed866a 100644
--- a/src/com/android/managedprovisioning/Utils.java
+++ b/src/com/android/managedprovisioning/Utils.java
@@ -16,6 +16,7 @@
 
 package com.android.managedprovisioning;
 
+import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -25,10 +26,13 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
 import android.graphics.drawable.Drawable;
+import android.os.Binder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.text.TextUtils;
 
 import java.util.HashSet;
@@ -220,4 +224,17 @@
     public static boolean isCurrentUserOwner() {
         return UserHandle.myUserId() == UserHandle.USER_OWNER;
     }
+
+    public static boolean hasDeviceOwner(Context context) {
+        DevicePolicyManager dpm =
+                (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        return !TextUtils.isEmpty(dpm.getDeviceOwner());
+    }
+
+    public static boolean isManagedProfile(Context context) {
+        UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        UserInfo user = um.getUserInfo(UserHandle.myUserId());
+        return user != null ? user.isManagedProfile() : false;
+    }
+
 }
diff --git a/src/com/android/managedprovisioning/task/InstallPackageTask.java b/src/com/android/managedprovisioning/task/InstallPackageTask.java
index 7f10dc0..fb375eb 100644
--- a/src/com/android/managedprovisioning/task/InstallPackageTask.java
+++ b/src/com/android/managedprovisioning/task/InstallPackageTask.java
@@ -16,6 +16,7 @@
 package com.android.managedprovisioning.task;
 
 import android.app.DownloadManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.IPackageInstallObserver;
 import android.content.pm.ActivityInfo;
@@ -34,15 +35,17 @@
 import java.util.Set;
 
 /**
- * Optionally installs device owner and device initializer packages.
+ * Optionally installs device owner and device initializer packages. Can install a downloaded apk,
+ * or install an existing package which is already installed for a different user.
  * <p>
- * Before installing it is checked whether each file at the specified paths contains the correct
+ * Before installing from a downloaded file, each file is checked to ensure it contains the correct
  * package and admin receiver.
  * </p>
  */
 public class InstallPackageTask {
     public static final int ERROR_PACKAGE_INVALID = 0;
     public static final int ERROR_INSTALLATION_FAILED = 1;
+    public static final int ERROR_PACKAGE_NAME_INVALID = 2;
 
     private final Context mContext;
     private final Callback mCallback;
@@ -53,6 +56,12 @@
     private int mPackageVerifierEnable;
     private Set<InstallInfo> mPackagesToInstall;
 
+    /**
+     * Create an InstallPackageTask. When run, this will attempt to install the device initializer
+     * and device admin packages if they are specified in {@code params}.
+     *
+     * {@see #run(String, String)} for more detail on package installation.
+     */
     public InstallPackageTask (Context context, ProvisioningParams params, Callback callback) {
         mCallback = callback;
         mContext = context;
@@ -65,30 +74,49 @@
         }
 
         mPackagesToInstall = new HashSet<InstallInfo>();
+        mPm = mContext.getPackageManager();
     }
 
+    /**
+     * Install the device admin and device initializer packages. Each package will be installed from
+     * the given location if one is provided. If a null or empty location is provided, and the
+     * package is installed for a different user, it will be enabled for the calling user. If the
+     * package location is not provided and the package is not installed for any other users, this
+     * task will produce an error.
+     *
+     * Errors will be indicated if a downloaded package is invalid, or installation fails.
+     *
+     * @param deviceAdminPackageLocation The file system location of a downloaded device admin
+     *                                   package. If null, the package will be installed from
+     *                                   another user if possible.
+     * @param deviceInitializerPackageLocation The file system location of a downloaded device
+     *                                         initializer package. If null, the package will be
+     *                                         installed from another user if possible.
+     */
     public void run(String deviceAdminPackageLocation, String deviceInitializerPackageLocation) {
-        if (!TextUtils.isEmpty(deviceAdminPackageLocation)) {
-            mPackagesToInstall.add(
-                    new InstallInfo(mDeviceAdminPackageName, deviceAdminPackageLocation));
+        if (!TextUtils.isEmpty(mDeviceAdminPackageName)) {
+            mPackagesToInstall.add(new InstallInfo(
+                    mDeviceAdminPackageName, deviceAdminPackageLocation));
         }
-        if (!TextUtils.isEmpty(deviceInitializerPackageLocation)) {
+        if (!TextUtils.isEmpty(mDeviceInitializerPackageName)) {
             mPackagesToInstall.add(new InstallInfo(
                     mDeviceInitializerPackageName, deviceInitializerPackageLocation));
         }
 
         if (mPackagesToInstall.size() == 0) {
-            ProvisionLogger.loge("Nothing to install");
+            ProvisionLogger.loge("No downloaded packages to install");
             mCallback.onSuccess();
             return;
         }
         ProvisionLogger.logi("Installing package(s)");
 
         PackageInstallObserver observer = new PackageInstallObserver();
-        mPm = mContext.getPackageManager();
 
         for (InstallInfo info : mPackagesToInstall) {
-            if (packageContentIsCorrect(info.mPackageName, info.mLocation)) {
+            if (TextUtils.isEmpty(info.mLocation)) {
+                installExistingPackage(info);
+
+            } else if (packageContentIsCorrect(info.mPackageName, info.mLocation)) {
                 // Temporarily turn off package verification.
                 mPackageVerifierEnable = Global.getInt(mContext.getContentResolver(),
                         Global.PACKAGE_VERIFIER_ENABLE, 1);
@@ -184,6 +212,24 @@
         mCallback.onSuccess();
     }
 
+    /**
+     * Attempt to install this package from an existing package installed under a different user.
+     * If this package is already installed for this user, this is a no-op. If it is not installed
+     * for another user, this will produce an error.
+     * @param info The package to install
+     */
+    private void installExistingPackage(InstallInfo info) {
+        try {
+            ProvisionLogger.logi("Installing existing package " + info.mPackageName);
+            mPm.installExistingPackage(info.mPackageName);
+            info.mDoneInstalling = true;
+        } catch (PackageManager.NameNotFoundException e) {
+            mCallback.onError(ERROR_PACKAGE_INVALID);
+            return;
+        }
+        checkSuccess();
+    }
+
     public abstract static class Callback {
         public abstract void onSuccess();
         public abstract void onError(int errorCode);
diff --git a/src/com/android/managedprovisioning/task/SetDevicePolicyTask.java b/src/com/android/managedprovisioning/task/SetDevicePolicyTask.java
index 82a60a8..e071a01 100644
--- a/src/com/android/managedprovisioning/task/SetDevicePolicyTask.java
+++ b/src/com/android/managedprovisioning/task/SetDevicePolicyTask.java
@@ -16,10 +16,14 @@
 
 package com.android.managedprovisioning.task;
 
+import android.app.AppGlobals;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
 
 import com.android.managedprovisioning.ProvisionLogger;
 
@@ -73,7 +77,10 @@
             if (mInitializerComponent != null) {
                 enableDevicePolicyApp(mInitializerPackageName);
                 setActiveAdmin(mInitializerComponent);
-                setDeviceInitializer(mInitializerComponent, mInitializerName);
+                if (!setDeviceInitializer(mInitializerComponent, mInitializerName)) {
+                    // error reported in setDeviceInitializer
+                    return;
+                }
             }
         } catch (Exception e) {
             ProvisionLogger.loge("Failure setting device owner or initializer", e);
@@ -104,11 +111,21 @@
         }
     }
 
-    public void setDeviceInitializer(ComponentName component, String owner) {
+    public boolean setDeviceInitializer(ComponentName component, String owner) {
         ProvisionLogger.logd("Setting " + component + " as device initializer " + owner + ".");
         if (!mDevicePolicyManager.isDeviceInitializerApp(component.getPackageName())) {
             mDevicePolicyManager.setDeviceInitializer(null, component, owner);
         }
+        IPackageManager pm = AppGlobals.getPackageManager();
+        try {
+            pm.setBlockUninstallForUser(component.getPackageName(), true,
+                    UserHandle.getCallingUserId());
+        } catch (RemoteException e) {
+            ProvisionLogger.loge("Failed to block uninstall of device initializer app", e);
+            mCallback.onError(ERROR_OTHER);
+            return false;
+        }
+        return true;
     }
 
     public abstract static class Callback {