Add explicit and persistent user provisioning state.

Add explicit modelling of provisioning state so that integration
of management provisioning flows with packages such as setup-wizard
are cleaner, and can be more direct. Previously we relied upon
USER_SETUP_COMPLETE secure setting and HOME intents to signal intent,
but this is not very clear and can be fragile.

Bug: 25858670
Change-Id: Idc56a040f710c3aee281db420f21717da3960722
This commit is contained in:
Alan Treadway
2016-01-19 15:15:08 +00:00
parent fe434a15d6
commit afad878369
5 changed files with 370 additions and 7 deletions

View File

@@ -18,6 +18,7 @@ package com.android.commands.dpm;
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
import android.app.admin.DevicePolicyManager;
import android.app.admin.IDevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
@@ -143,6 +144,10 @@ public final class Dpm extends BaseCommand {
mDevicePolicyManager.removeActiveAdmin(mComponent, UserHandle.USER_SYSTEM);
throw e;
}
mDevicePolicyManager.setUserProvisioningState(
DevicePolicyManager.STATE_USER_SETUP_FINALIZED, mUserId);
System.out.println("Success: Device owner set to package " + mComponent);
System.out.println("Active admin set to component " + mComponent.toShortString());
}
@@ -161,6 +166,10 @@ public final class Dpm extends BaseCommand {
mDevicePolicyManager.removeActiveAdmin(mComponent, mUserId);
throw e;
}
mDevicePolicyManager.setUserProvisioningState(
DevicePolicyManager.STATE_USER_SETUP_FINALIZED, mUserId);
System.out.println("Success: Active admin and profile owner set to "
+ mComponent.toShortString() + " for user " + mUserId);
}
@@ -180,4 +189,4 @@ public final class Dpm extends BaseCommand {
throw new IllegalArgumentException ("Invalid integer argument '" + argument + "'", e);
}
}
}
}

View File

@@ -16,6 +16,7 @@
package android.app.admin;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
@@ -53,6 +54,8 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.security.KeyFactory;
@@ -279,6 +282,21 @@ public class DevicePolicyManager {
public static final String ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE
= "android.app.action.PROVISION_MANAGED_SHAREABLE_DEVICE";
/**
* Activity action: Finalizes management provisioning, should be used after user-setup
* has been completed and {@link #getUserProvisioningState()} returns one of:
* <ul>
* <li>{@link #STATE_USER_SETUP_INCOMPLETE}</li>
* <li>{@link #STATE_USER_SETUP_COMPLETE}</li>
* <li>{@link #STATE_USER_PROFILE_COMPLETE}</li>
* </ul>
*
* @hide
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_PROVISION_FINALIZATION
= "android.app.action.PROVISION_FINALIZATION";
/**
* A {@link android.os.Parcelable} extra of type {@link android.os.PersistableBundle} that
* allows a mobile device management application or NFC programmer application which starts
@@ -860,6 +878,44 @@ public class DevicePolicyManager {
*/
public static final int PERMISSION_GRANT_STATE_DENIED = 2;
/**
* No management for current user in-effect. This is the default.
* @hide
*/
public static final int STATE_USER_UNMANAGED = 0;
/**
* Management partially setup, user setup needs to be completed.
* @hide
*/
public static final int STATE_USER_SETUP_INCOMPLETE = 1;
/**
* Management partially setup, user setup completed.
* @hide
*/
public static final int STATE_USER_SETUP_COMPLETE = 2;
/**
* Management setup and active on current user.
* @hide
*/
public static final int STATE_USER_SETUP_FINALIZED = 3;
/**
* Management partially setup on a managed profile.
* @hide
*/
public static final int STATE_USER_PROFILE_COMPLETE = 4;
/**
* @hide
*/
@IntDef({STATE_USER_UNMANAGED, STATE_USER_SETUP_INCOMPLETE, STATE_USER_SETUP_COMPLETE,
STATE_USER_SETUP_FINALIZED, STATE_USER_PROFILE_COMPLETE})
@Retention(RetentionPolicy.SOURCE)
public @interface UserProvisioningState {}
/**
* Return true if the given administrator component is currently
* active (enabled) in the system.
@@ -5202,6 +5258,40 @@ public class DevicePolicyManager {
}
}
/**
* @return the {@link UserProvisioningState} for the current user - for unmanaged users will
* return {@link #STATE_USER_UNMANAGED}
* @hide
*/
@UserProvisioningState
public int getUserProvisioningState() {
if (mService != null) {
try {
return mService.getUserProvisioningState();
} catch (RemoteException e) {
Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e);
}
}
return STATE_USER_UNMANAGED;
}
/**
* Set the {@link UserProvisioningState} for the supplied user, if they are managed.
*
* @param state to store
* @param userHandle for user
* @hide
*/
public void setUserProvisioningState(@UserProvisioningState int state, int userHandle) {
if (mService != null) {
try {
mService.setUserProvisioningState(state, userHandle);
} catch (RemoteException e) {
Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e);
}
}
}
/**
* @hide
* Indicates the entity that controls the device or profile owner. A user/profile is considered

View File

@@ -268,6 +268,9 @@ interface IDevicePolicyManager {
int getOrganizationColor(in ComponentName admin);
int getOrganizationColorForUser(int userHandle);
int getUserProvisioningState();
void setUserProvisioningState(int state, int userHandle);
void setAffiliationIds(in ComponentName admin, in List<String> ids);
boolean isAffiliatedUser();
}

View File

@@ -196,6 +196,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private static final String ATTR_PERMISSION_PROVIDER = "permission-provider";
private static final String ATTR_SETUP_COMPLETE = "setup-complete";
private static final String ATTR_PROVISIONING_STATE = "provisioning-state";
private static final String ATTR_PERMISSION_POLICY = "permission-policy";
private static final String ATTR_DELEGATED_CERT_INSTALLER = "delegated-cert-installer";
@@ -358,6 +359,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
int mPasswordOwner = -1;
long mLastMaximumTimeToLock = -1;
boolean mUserSetupComplete = false;
int mUserProvisioningState;
int mPermissionPolicy;
final ArrayMap<ComponentName, ActiveAdmin> mAdminMap = new ArrayMap<>();
@@ -2008,6 +2010,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
out.attribute(null, ATTR_SETUP_COMPLETE,
Boolean.toString(true));
}
if (policy.mUserProvisioningState != DevicePolicyManager.STATE_USER_UNMANAGED) {
out.attribute(null, ATTR_PROVISIONING_STATE,
Integer.toString(policy.mUserProvisioningState));
}
if (policy.mPermissionPolicy != DevicePolicyManager.PERMISSION_POLICY_PROMPT) {
out.attribute(null, ATTR_PERMISSION_POLICY,
Integer.toString(policy.mPermissionPolicy));
@@ -2145,6 +2151,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (userSetupComplete != null && Boolean.toString(true).equals(userSetupComplete)) {
policy.mUserSetupComplete = true;
}
String provisioningState = parser.getAttributeValue(null, ATTR_PROVISIONING_STATE);
if (!TextUtils.isEmpty(provisioningState)) {
policy.mUserProvisioningState = Integer.parseInt(provisioningState);
}
String permissionPolicy = parser.getAttributeValue(null, ATTR_PERMISSION_POLICY);
if (!TextUtils.isEmpty(permissionPolicy)) {
policy.mPermissionPolicy = Integer.parseInt(permissionPolicy);
@@ -5352,6 +5362,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
policy.mDelegatedCertInstallerPackage = null;
policy.mApplicationRestrictionsManagingPackage = null;
policy.mStatusBarDisabled = false;
policy.mUserProvisioningState = DevicePolicyManager.STATE_USER_UNMANAGED;
saveSettingsLocked(userId);
final long ident = mInjector.binderClearCallingIdentity();
@@ -5378,6 +5389,98 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return getUserData(userHandle).mUserSetupComplete;
}
@Override
public int getUserProvisioningState() {
if (!mHasFeature) {
return DevicePolicyManager.STATE_USER_UNMANAGED;
}
int userHandle = mInjector.userHandleGetCallingUserId();
return getUserProvisioningState(userHandle);
}
private int getUserProvisioningState(int userHandle) {
return getUserData(userHandle).mUserProvisioningState;
}
@Override
public void setUserProvisioningState(int newState, int userHandle) {
if (!mHasFeature) {
return;
}
if (userHandle != mOwners.getDeviceOwnerUserId() && !mOwners.hasProfileOwner(userHandle)
&& getManagedUserId(userHandle) == -1) {
// No managed device, user or profile, so setting provisioning state makes no sense.
throw new IllegalStateException("Not allowed to change provisioning state unless a "
+ "device or profile owner is set.");
}
synchronized (this) {
boolean transitionCheckNeeded = true;
// Calling identity/permission checks.
final int callingUid = mInjector.binderGetCallingUid();
if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
// ADB shell can only move directly from un-managed to finalized as part of directly
// setting profile-owner or device-owner.
if (getUserProvisioningState(userHandle) !=
DevicePolicyManager.STATE_USER_UNMANAGED
|| newState != DevicePolicyManager.STATE_USER_SETUP_FINALIZED) {
throw new IllegalStateException("Not allowed to change provisioning state "
+ "unless current provisioning state is unmanaged, and new state is "
+ "finalized.");
}
transitionCheckNeeded = false;
} else {
// For all other cases, caller must have MANAGE_PROFILE_AND_DEVICE_OWNERS.
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, null);
}
final DevicePolicyData policyData = getUserData(userHandle);
if (transitionCheckNeeded) {
// Optional state transition check for non-ADB case.
checkUserProvisioningStateTransition(policyData.mUserProvisioningState, newState);
}
policyData.mUserProvisioningState = newState;
saveSettingsLocked(userHandle);
}
}
private void checkUserProvisioningStateTransition(int currentState, int newState) {
// Valid transitions for normal use-cases.
switch (currentState) {
case DevicePolicyManager.STATE_USER_UNMANAGED:
// Can move to any state from unmanaged (except itself as an edge case)..
if (newState != DevicePolicyManager.STATE_USER_UNMANAGED) {
return;
}
break;
case DevicePolicyManager.STATE_USER_SETUP_INCOMPLETE:
case DevicePolicyManager.STATE_USER_SETUP_COMPLETE:
// Can only move to finalized from these states.
if (newState == DevicePolicyManager.STATE_USER_SETUP_FINALIZED) {
return;
}
break;
case DevicePolicyManager.STATE_USER_PROFILE_COMPLETE:
// Current user has a managed-profile, but current user is not managed, so
// rather than moving to finalized state, go back to unmanaged once
// profile provisioning is complete.
if (newState == DevicePolicyManager.STATE_USER_UNMANAGED) {
return;
}
break;
case DevicePolicyManager.STATE_USER_SETUP_FINALIZED:
// Cannot transition out of finalized.
break;
}
// Didn't meet any of the accepted state transition checks above, throw appropriate error.
throw new IllegalStateException("Cannot move to user provisioning state [" + newState + "] "
+ "from state [" + currentState + "]");
}
@Override
public void setProfileEnabled(ComponentName who) {
if (!mHasFeature) {
@@ -5697,7 +5800,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
for (int u = 0; u < userCount; u++) {
DevicePolicyData policy = getUserData(mUserData.keyAt(u));
pw.println();
pw.println(" Enabled Device Admins (User " + policy.mUserHandle + "):");
pw.println(" Enabled Device Admins (User " + policy.mUserHandle
+ ", provisioningState: " + policy.mUserProvisioningState + "):");
final int N = policy.mAdminList.size();
for (int i=0; i<N; i++) {
ActiveAdmin ap = policy.mAdminList.get(i);

View File

@@ -15,9 +15,6 @@
*/
package com.android.server.devicepolicy;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import android.Manifest.permission;
import android.app.Activity;
import android.app.admin.DeviceAdminReceiver;
@@ -27,7 +24,6 @@ import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.net.wifi.WifiInfo;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Process;
@@ -37,11 +33,16 @@ import android.test.MoreAsserts;
import android.util.ArraySet;
import android.util.Pair;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -64,13 +65,17 @@ import static org.mockito.Mockito.when;
*
m FrameworksServicesTests &&
adb install \
-r out/target/product/hammerhead/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
-r ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
adb shell am instrument -e class com.android.server.devicepolicy.DevicePolicyManagerTest \
-w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
(mmma frameworks/base/services/tests/servicestests/ for non-ninja build)
*/
public class DevicePolicyManagerTest extends DpmTestBase {
private static final List<String> OWNER_SETUP_PERMISSIONS = Arrays.asList(
permission.MANAGE_DEVICE_ADMINS, permission.MANAGE_PROFILE_AND_DEVICE_OWNERS,
permission.MANAGE_USERS, permission.INTERACT_ACROSS_USERS_FULL);
private DpmMockContext mContext;
public DevicePolicyManager dpm;
public DevicePolicyManagerServiceTestable dpms;
@@ -1543,4 +1548,156 @@ public class DevicePolicyManagerTest extends DpmTestBase {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
assertTrue(dpm.isAffiliatedUser());
}
public void testGetUserProvisioningState_defaultResult() {
assertEquals(DevicePolicyManager.STATE_USER_UNMANAGED, dpm.getUserProvisioningState());
}
public void testSetUserProvisioningState_permission() throws Exception {
setupProfileOwner();
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
exerciseUserProvisioningTransitions(DpmMockContext.CALLER_USER_HANDLE,
DevicePolicyManager.STATE_USER_SETUP_FINALIZED);
}
public void testSetUserProvisioningState_unprivileged() throws Exception {
setupProfileOwner();
try {
dpm.setUserProvisioningState(DevicePolicyManager.STATE_USER_SETUP_FINALIZED,
DpmMockContext.CALLER_USER_HANDLE);
fail("Expected SecurityException");
} catch (SecurityException expected) {
}
}
public void testSetUserProvisioningState_noManagement() {
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
try {
dpm.setUserProvisioningState(DevicePolicyManager.STATE_USER_SETUP_FINALIZED,
DpmMockContext.CALLER_USER_HANDLE);
fail("IllegalStateException expected");
} catch (IllegalStateException e) {
MoreAsserts.assertContainsRegex("change provisioning state unless a .* owner is set",
e.getMessage());
}
assertEquals(DevicePolicyManager.STATE_USER_UNMANAGED, dpm.getUserProvisioningState());
}
public void testSetUserProvisioningState_deviceOwnerFromSetupWizard() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
exerciseUserProvisioningTransitions(UserHandle.USER_SYSTEM,
DevicePolicyManager.STATE_USER_SETUP_COMPLETE,
DevicePolicyManager.STATE_USER_SETUP_FINALIZED);
}
public void testSetUserProvisioningState_deviceOwnerFromSetupWizardAlternative()
throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
exerciseUserProvisioningTransitions(UserHandle.USER_SYSTEM,
DevicePolicyManager.STATE_USER_SETUP_INCOMPLETE,
DevicePolicyManager.STATE_USER_SETUP_FINALIZED);
}
public void testSetUserProvisioningState_deviceOwnerWithoutSetupWizard() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
exerciseUserProvisioningTransitions(UserHandle.USER_SYSTEM,
DevicePolicyManager.STATE_USER_SETUP_FINALIZED);
}
public void testSetUserProvisioningState_managedProfileFromSetupWizard_primaryUser()
throws Exception {
setupProfileOwner();
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
exerciseUserProvisioningTransitions(DpmMockContext.CALLER_USER_HANDLE,
DevicePolicyManager.STATE_USER_PROFILE_COMPLETE,
DevicePolicyManager.STATE_USER_UNMANAGED);
}
public void testSetUserProvisioningState_managedProfileFromSetupWizard_managedProfile()
throws Exception {
setupProfileOwner();
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
exerciseUserProvisioningTransitions(DpmMockContext.CALLER_USER_HANDLE,
DevicePolicyManager.STATE_USER_SETUP_COMPLETE,
DevicePolicyManager.STATE_USER_SETUP_FINALIZED);
}
public void testSetUserProvisioningState_managedProfileWithoutSetupWizard() throws Exception {
setupProfileOwner();
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
exerciseUserProvisioningTransitions(DpmMockContext.CALLER_USER_HANDLE,
DevicePolicyManager.STATE_USER_SETUP_FINALIZED);
}
public void testSetUserProvisioningState_illegalTransitionOutOfFinalized1() throws Exception {
setupProfileOwner();
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
try {
exerciseUserProvisioningTransitions(DpmMockContext.CALLER_USER_HANDLE,
DevicePolicyManager.STATE_USER_SETUP_FINALIZED,
DevicePolicyManager.STATE_USER_UNMANAGED);
fail("Expected IllegalStateException");
} catch (IllegalStateException e) {
MoreAsserts.assertContainsRegex("Cannot move to user provisioning state",
e.getMessage());
}
}
public void testSetUserProvisioningState_illegalTransitionToAnotherInProgressState()
throws Exception {
setupProfileOwner();
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
try {
exerciseUserProvisioningTransitions(DpmMockContext.CALLER_USER_HANDLE,
DevicePolicyManager.STATE_USER_SETUP_INCOMPLETE,
DevicePolicyManager.STATE_USER_SETUP_COMPLETE);
fail("Expected IllegalStateException");
} catch (IllegalStateException e) {
MoreAsserts.assertContainsRegex("Cannot move to user provisioning state",
e.getMessage());
}
}
private void exerciseUserProvisioningTransitions(int userId, int... states) {
assertEquals(DevicePolicyManager.STATE_USER_UNMANAGED, dpm.getUserProvisioningState());
for (int state : states) {
dpm.setUserProvisioningState(state, userId);
assertEquals(state, dpm.getUserProvisioningState());
}
}
private void setupProfileOwner() throws Exception {
mContext.callerPermissions.addAll(OWNER_SETUP_PERMISSIONS);
setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID);
dpm.setActiveAdmin(admin1, false);
assertTrue(dpm.setProfileOwner(admin1, null, DpmMockContext.CALLER_USER_HANDLE));
mContext.callerPermissions.removeAll(OWNER_SETUP_PERMISSIONS);
}
private void setupDeviceOwner() throws Exception {
mContext.callerPermissions.addAll(OWNER_SETUP_PERMISSIONS);
setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
dpm.setActiveAdmin(admin1, false);
assertTrue(dpm.setDeviceOwner(admin1, null, UserHandle.USER_SYSTEM));
mContext.callerPermissions.removeAll(OWNER_SETUP_PERMISSIONS);
}
}