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 {