blob: 44a604815b1747695f9e2be938e8f115a04ff580 [file] [log] [blame]
/*
* 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 static android.app.admin.DeviceAdminReceiver.ACTION_PROFILE_PROVISIONING_COMPLETE;
import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE;
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.Manifest.permission.BIND_DEVICE_ADMIN;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
import android.app.Service;
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.ActivityInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.os.AsyncTask;
import android.os.IBinder;
import android.os.Parcelable;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.MediaStore;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import com.android.managedprovisioning.CrossProfileIntentFiltersHelper;
import com.android.managedprovisioning.task.DeleteNonRequiredAppsTask;
import java.io.IOException;
/**
* Service that runs the profile owner provisioning.
*
* <p>This service is started from and sends updates to the {@link ProfileOwnerProvisioningActivity},
* which contains the provisioning UI.
*/
public class ProfileOwnerProvisioningService extends Service {
// Extra keys for reporting back success to the activity.
public static final String EXTRA_PROFILE_USER_ID =
"com.android.managedprovisioning.extra.profile_user_id";
public static final String EXTRA_PROFILE_USER_SERIAL_NUMBER =
"com.android.managedprovisioning.extra.profile_user_serial_number";
public static final String EXTRA_PENDING_SUCCESS_INTENT =
"com.android.managedprovisioning.extra.pending_success_intent";
// Intent actions for communication with DeviceOwnerProvisioningService.
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 ACTION_PROVISIONING_CANCELLED =
"com.android.managedprovisioning.cancelled";
public static final String EXTRA_LOG_MESSAGE_KEY = "ProvisioingErrorLogMessage";
private String mMdmPackageName;
private ComponentName mActiveAdminComponentName;
// PersistableBundle extra received in starting intent.
// Should be passed through to device management application when provisioning is complete.
private PersistableBundle mAdminExtrasBundle;
private Account mAccountToMigrate;
private IPackageManager mIpm;
private UserInfo mManagedProfileUserInfo;
private AccountManager mAccountManager;
private UserManager mUserManager;
private int mStartIdProvisioning;
private AsyncTask<Intent, Void, Void> runnerTask;
// MessageId of the last error message.
private String mLastErrorMessage = null;
private boolean mDone = false;
private boolean mCancelInFuture = false;
private class RunnerTask extends AsyncTask<Intent, Void, Void> {
@Override
protected Void doInBackground(Intent ... intents) {
initialize(intents[0]);
startManagedProfileProvisioning();
return null;
}
}
@Override
public void onCreate() {
super.onCreate();
mIpm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
mAccountManager = (AccountManager) getSystemService(Context.ACCOUNT_SERVICE);
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
runnerTask = new RunnerTask();
}
@Override
public int onStartCommand(final Intent intent, int flags, int startId) {
if (ProfileOwnerProvisioningActivity.ACTION_CANCEL_PROVISIONING.equals(intent.getAction())) {
ProvisionLogger.logd("Cancelling profile owner provisioning service");
cancelProvisioning();
return START_NOT_STICKY;
}
ProvisionLogger.logd("Starting profile owner provisioning service");
try {
runnerTask.execute(intent);
} catch (IllegalStateException e) {
// runnerTask is either in progress, or finished.
ProvisionLogger.logd(
"ProfileOwnerProvisioningService: Provisioning already started, "
+ "second provisioning intent not being processed, only reporting status.");
reportStatus();
}
return START_NOT_STICKY;
}
private void reportStatus() {
if (mLastErrorMessage != null) {
sendError();
}
synchronized (this) {
if (mDone) {
notifyActivityOfSuccess();
}
}
}
private void cancelProvisioning() {
synchronized (this) {
if (!mDone) {
mCancelInFuture = true;
return;
}
cleanup();
}
}
private void initialize(Intent intent) {
mMdmPackageName = intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
mAccountToMigrate = (Account) intent.getParcelableExtra(
EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE);
if (mAccountToMigrate != null) {
ProvisionLogger.logi("Migrating account to managed profile");
}
// Cast is guaranteed by check in Activity.
mAdminExtrasBundle = (PersistableBundle) intent.getParcelableExtra(
EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE);
mActiveAdminComponentName = getAdminReceiverComponent(mMdmPackageName);
}
/**
* Find the Device admin receiver component from the manifest.
*/
private ComponentName getAdminReceiverComponent(String packageName) {
ComponentName adminReceiverComponent = null;
try {
PackageInfo pi = getPackageManager().getPackageInfo(packageName,
PackageManager.GET_RECEIVERS);
for (ActivityInfo ai : pi.receivers) {
if (!TextUtils.isEmpty(ai.permission) &&
ai.permission.equals(BIND_DEVICE_ADMIN)) {
adminReceiverComponent = new ComponentName(packageName, ai.name);
}
}
} catch (NameNotFoundException e) {
error("Error: The provided mobile device management package does not define a device"
+ "admin receiver component in its manifest.");
}
return adminReceiverComponent;
}
/**
* This is the core method of this class. It goes through every provisioning step.
*/
private void startManagedProfileProvisioning() {
ProvisionLogger.logd("Starting managed profile provisioning");
// Work through the provisioning steps in their corresponding order
createProfile(getString(R.string.default_managed_profile_name));
if (mManagedProfileUserInfo != null) {
new DeleteNonRequiredAppsTask(this,
mMdmPackageName, mManagedProfileUserInfo.id,
R.array.required_apps_managed_profile,
R.array.vendor_required_apps_managed_profile,
true /* We are creating a new profile */,
true /* Disable INSTALL_SHORTCUT listeners */,
new DeleteNonRequiredAppsTask.Callback() {
@Override
public void onSuccess() {
setUpProfileAndFinish();
}
@Override
public void onError() {
error("Delete non required apps task failed.");
}
}).run();
}
}
/**
* Called when the new profile is ready for provisioning (the profile is created and all the
* apps not needed have been deleted).
*/
private void setUpProfileAndFinish() {
installMdmOnManagedProfile();
setMdmAsActiveAdmin();
setMdmAsManagedProfileOwner();
CrossProfileIntentFiltersHelper.setFilters(
getPackageManager(), getUserId(), mManagedProfileUserInfo.id);
if (!startManagedProfile(mManagedProfileUserInfo.id)) {
error("Could not start user in background");
return;
}
copyAccount(mAccountToMigrate);
synchronized (this) {
mDone = true;
if (mCancelInFuture) {
cleanup();
} else {
// Notify activity of success.
notifyActivityOfSuccess();
}
}
}
/**
* Initialize the user that underlies the managed profile.
* This is required so that the provisioning complete broadcast can be sent across to the
* profile and apps can run on it.
*/
private boolean startManagedProfile(int userId) {
ProvisionLogger.logd("Starting user in background");
IActivityManager iActivityManager = ActivityManagerNative.getDefault();
try {
return iActivityManager.startUserInBackground(userId);
} catch (RemoteException neverThrown) {
// Never thrown, as we are making local calls.
ProvisionLogger.loge("This should not happen.", neverThrown);
}
return false;
}
private void notifyActivityOfSuccess() {
// Compose the intent that will be fired by the activity.
Intent completeIntent = new Intent(ACTION_PROFILE_PROVISIONING_COMPLETE);
completeIntent.setComponent(mActiveAdminComponentName);
completeIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES |
Intent.FLAG_RECEIVER_FOREGROUND);
if (mAdminExtrasBundle != null) {
completeIntent.putExtra(EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE,
mAdminExtrasBundle);
}
Intent successIntent = new Intent(ACTION_PROVISIONING_SUCCESS);
successIntent.putExtra(EXTRA_PROFILE_USER_ID, mManagedProfileUserInfo.id);
successIntent.putExtra(EXTRA_PENDING_SUCCESS_INTENT, completeIntent);
successIntent.putExtra(EXTRA_PROFILE_USER_SERIAL_NUMBER,
mManagedProfileUserInfo.serialNumber);
LocalBroadcastManager.getInstance(ProfileOwnerProvisioningService.this)
.sendBroadcast(successIntent);
}
private void copyAccount(Account account) {
if (account == null) {
ProvisionLogger.logd("No account to migrate to the managed profile.");
return;
}
ProvisionLogger.logd("Attempting to copy account to user " + mManagedProfileUserInfo.id);
try {
if (mAccountManager.copyAccountToUser(account, mManagedProfileUserInfo.getUserHandle(),
/* callback= */ null, /* handler= */ null).getResult()) {
ProvisionLogger.logi("Copied account to user " + mManagedProfileUserInfo.id);
} else {
ProvisionLogger.loge("Could not copy account to user "
+ mManagedProfileUserInfo.id);
}
} catch (OperationCanceledException | AuthenticatorException | IOException e) {
ProvisionLogger.logw("Exception copying account to user " + mManagedProfileUserInfo.id,
e);
}
}
private void createProfile(String profileName) {
ProvisionLogger.logd("Creating managed profile with name " + profileName);
mManagedProfileUserInfo = mUserManager.createProfileForUser(profileName,
UserInfo.FLAG_MANAGED_PROFILE | UserInfo.FLAG_DISABLED,
Process.myUserHandle().getIdentifier());
if (mManagedProfileUserInfo == null) {
if (UserManager.getMaxSupportedUsers() == mUserManager.getUserCount()) {
error("Profile creation failed, maximum number of users reached.");
} else {
error("Couldn't create profile. Reason unknown.");
}
}
}
private void installMdmOnManagedProfile() {
ProvisionLogger.logd("Installing mobile device management app " + mMdmPackageName +
" on managed profile");
try {
int status = mIpm.installExistingPackageAsUser(
mMdmPackageName, mManagedProfileUserInfo.id);
switch (status) {
case PackageManager.INSTALL_SUCCEEDED:
return;
case PackageManager.INSTALL_FAILED_USER_RESTRICTED:
// Should not happen because we're not installing a restricted user
error("Could not install mobile device management app on managed "
+ "profile because the user is restricted");
case PackageManager.INSTALL_FAILED_INVALID_URI:
// Should not happen because we already checked
error("Could not install mobile device management app on managed "
+ "profile because the package could not be found");
default:
error("Could not install mobile device management app on managed "
+ "profile. Unknown status: " + status);
}
} catch (RemoteException neverThrown) {
// Never thrown, as we are making local calls.
ProvisionLogger.loge("This should not happen.", neverThrown);
}
}
private void setMdmAsManagedProfileOwner() {
ProvisionLogger.logd("Setting package " + mMdmPackageName + " as managed profile owner.");
DevicePolicyManager dpm =
(DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
if (!dpm.setProfileOwner(mActiveAdminComponentName, mMdmPackageName,
mManagedProfileUserInfo.id)) {
ProvisionLogger.logw("Could not set profile owner.");
error("Could not set profile owner.");
}
}
private void setMdmAsActiveAdmin() {
ProvisionLogger.logd("Setting package " + mMdmPackageName + " as active admin.");
DevicePolicyManager dpm =
(DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
dpm.setActiveAdmin(mActiveAdminComponentName, true /* refreshing*/,
mManagedProfileUserInfo.id);
}
private void error(String dialogMessage) {
mLastErrorMessage = dialogMessage;
sendError();
}
private void sendError() {
Intent intent = new Intent(ACTION_PROVISIONING_ERROR);
intent.putExtra(EXTRA_LOG_MESSAGE_KEY, mLastErrorMessage);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
/**
* Performs cleanup of the device on failure.
*/
private void cleanup() {
// The only cleanup we need to do is remove the profile we created.
if (mManagedProfileUserInfo != null) {
ProvisionLogger.logd("Removing managed profile");
mUserManager.removeUser(mManagedProfileUserInfo.id);
}
Intent cancelIntent = new Intent(ACTION_PROVISIONING_CANCELLED);
LocalBroadcastManager.getInstance(ProfileOwnerProvisioningService.this)
.sendBroadcast(cancelIntent);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}