blob: a1611f29881d18edff5fc8fa2b2d4bffbabcc2ef [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 android.app.AlarmManager;
import android.app.Service;
import android.app.admin.DeviceInitializerStatus;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.UserHandle;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import com.android.internal.app.LocalePicker;
import com.android.managedprovisioning.ProvisioningParams.PackageDownloadInfo;
import com.android.managedprovisioning.proxy.BluetoothConnectionService;
import com.android.managedprovisioning.task.AddWifiNetworkTask;
import com.android.managedprovisioning.task.BluetoothConnectTask;
import com.android.managedprovisioning.task.DeleteNonRequiredAppsTask;
import com.android.managedprovisioning.task.DownloadPackageTask;
import com.android.managedprovisioning.task.InstallPackageTask;
import com.android.managedprovisioning.task.SetDevicePolicyTask;
import com.android.managedprovisioning.task.WipeResetProtectionTask;
import java.lang.Runnable;
import java.util.Locale;
/**
* 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 {
private static final boolean DEBUG = false; // To control logging.
private static final String DEVICE_OWNER = "deviceOwner";
private static final String DEVICE_INITIALIZER = "deviceInitializer";
/**
* Intent action to activate the CDMA phone connection by OTASP.
* This is not necessary for a GSM phone connection, which is activated automatically.
* String must agree with the constants in com.android.phone.InCallScreenShowActivation.
*/
private static final String ACTION_PERFORM_CDMA_PROVISIONING =
"com.android.phone.PERFORM_CDMA_PROVISIONING";
// Intent actions and extras for communication from DeviceOwnerProvisioningService to Activity.
protected static final String EXTRA_PROVISIONING_PARAMS =
"ProvisioningParams";
// Intent actions and extras for communication from DeviceOwnerProvisioningActivity to Service.
protected static final String ACTION_PROVISIONING_SUCCESS =
"com.android.managedprovisioning.provisioning_success";
protected static final String ACTION_PROVISIONING_ERROR =
"com.android.managedprovisioning.error";
protected static final String EXTRA_USER_VISIBLE_ERROR_ID_KEY =
"UserVisibleErrorMessage-Id";
protected static final String ACTION_PROGRESS_UPDATE =
"com.android.managedprovisioning.progress_update";
protected static final String EXTRA_PROGRESS_MESSAGE_ID_KEY =
"ProgressMessageId";
protected static final String ACTION_REQUEST_WIFI_PICK =
"com.android.managedprovisioning.request_wifi_pick";
// Intent action used by the HomeReceiverActivity to notify this Service that a HOME intent was
// received, which indicates that the Setup wizard has closed after provisioning completed.
protected static final String ACTION_HOME_INDIRECT =
"com.android.managedprovisioning.home_indirect";
// Indicates whether provisioning has started.
private boolean mProvisioningInFlight = false;
// MessageId of the last progress message.
private int mLastProgressMessage = -1;
// MessageId of the last error message.
private int mLastErrorMessage = -1;
// Indicates whether provisioning has finished successfully (service waiting to stop).
private volatile boolean mDone = false;
// Provisioning tasks.
private BluetoothConnectTask mBluetoothConnectTask;
private AddWifiNetworkTask mAddWifiNetworkTask;
private WipeResetProtectionTask mWipeResetProtectionTask;
private DownloadPackageTask mDownloadPackageTask;
private InstallPackageTask mInstallPackageTask;
private SetDevicePolicyTask mSetDevicePolicyTask;
private DeleteNonRequiredAppsTask mDeleteNonRequiredAppsTask;
private ProvisioningParams mParams;
@Override
public int onStartCommand(final Intent intent, int flags, int startId) {
if (DEBUG) ProvisionLogger.logd("Device owner provisioning service ONSTARTCOMMAND.");
synchronized (this) { // Make operations on mProvisioningInFlight atomic.
if (mProvisioningInFlight) {
if (DEBUG) ProvisionLogger.logd("Provisioning already in flight.");
sendProgressUpdateToActivity();
// Send error message if currently in error state.
if (mLastErrorMessage >= 0) {
sendError();
}
// Send success if provisioning was successful.
if (mDone) {
onProvisioningSuccess();
}
} else {
mProvisioningInFlight = true;
if (DEBUG) ProvisionLogger.logd("First start of the service.");
progressUpdate(R.string.progress_data_process);
// Load the ProvisioningParams (from message in Intent).
mParams = (ProvisioningParams) intent.getParcelableExtra(EXTRA_PROVISIONING_PARAMS);
// Do the work on a separate thread.
new Thread(new Runnable() {
public void run() {
initializeProvisioningEnvironment(mParams);
startDeviceOwnerProvisioning(mParams);
}
}).start();
}
}
return START_NOT_STICKY;
}
/**
* This is the core method of this class. It goes through every provisioning step.
* Each task checks if it is required and executes if it is.
*/
private void startDeviceOwnerProvisioning(final ProvisioningParams params) {
if (DEBUG) ProvisionLogger.logd("Starting device owner provisioning");
// Construct Tasks. Do not start them yet.
mBluetoothConnectTask = new BluetoothConnectTask(this, params.bluetoothInfo,
!TextUtils.isEmpty(params.wifiInfo.ssid),
new BluetoothConnectTask.Callback() {
@Override
public void onSuccess() {
progressUpdate(R.string.progress_connect_to_wifi);
mAddWifiNetworkTask.run();
}
@Override
public void onError() {
error(R.string.device_owner_error_bluetooth);
}
});
mAddWifiNetworkTask = new AddWifiNetworkTask(this, params.wifiInfo,
new AddWifiNetworkTask.Callback() {
@Override
public void onSuccess() {
progressUpdate(R.string.progress_wipe_frp);
mWipeResetProtectionTask.run();
}
@Override
public void onError(){
error(R.string.device_owner_error_wifi);
statusUpdate(DeviceInitializerStatus.STATUS_ERROR_CONNECT_WIFI);
}
});
mWipeResetProtectionTask = new WipeResetProtectionTask(this, params.frpChallengeBundle,
new WipeResetProtectionTask.Callback() {
@Override
public void onSuccess() {
progressUpdate(R.string.progress_download);
mDownloadPackageTask.run();
}
@Override
public void onError() {
error(R.string.device_owner_error_frp);
statusUpdate(DeviceInitializerStatus.
STATUS_ERROR_RESET_PROTECTION_BLOCKING_PROVISIONING);
}
});
mDownloadPackageTask = new DownloadPackageTask(this,
new DownloadPackageTask.Callback() {
@Override
public void onSuccess() {
progressUpdate(R.string.progress_install);
mInstallPackageTask.addInstallIfNecessary(
params.inferDeviceAdminPackageName(),
mDownloadPackageTask.getDownloadedPackageLocation(
DEVICE_OWNER));
mInstallPackageTask.addInstallIfNecessary(
params.getDeviceInitializerPackageName(),
mDownloadPackageTask.getDownloadedPackageLocation(
DEVICE_INITIALIZER));
mInstallPackageTask.run();
}
@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;
}
statusUpdate(DeviceInitializerStatus.STATUS_ERROR_DOWNLOAD_PACKAGE);
}
});
// Add packages to download to the DownloadPackageTask.
mDownloadPackageTask.addDownloadIfNecessary(params.inferDeviceAdminPackageName(),
params.deviceAdminDownloadInfo, DEVICE_OWNER);
mDownloadPackageTask.addDownloadIfNecessary(params.getDeviceInitializerPackageName(),
params.deviceInitializerDownloadInfo, DEVICE_INITIALIZER);
mInstallPackageTask = new InstallPackageTask(this,
new InstallPackageTask.Callback() {
@Override
public void onSuccess() {
progressUpdate(R.string.progress_set_owner);
try {
// Now that the app has been installed, we can look for the device admin
// component in it.
mSetDevicePolicyTask.run(mParams.inferDeviceAdminComponentName(
DeviceOwnerProvisioningService.this));
} catch (Utils.IllegalProvisioningArgumentException e) {
error(R.string.device_owner_error_general);
ProvisionLogger.loge("Failed to infer the device admin component name",
e);
return;
}
}
@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;
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;
}
statusUpdate(DeviceInitializerStatus.STATUS_ERROR_INSTALL_PACKAGE);
}
});
mSetDevicePolicyTask = new SetDevicePolicyTask(this,
getResources().getString(R.string.default_owned_device_username),
params.deviceInitializerComponentName,
getResources().getString(R.string.default_owned_device_username),
new SetDevicePolicyTask.Callback() {
@Override
public void onSuccess() {
mDeleteNonRequiredAppsTask.run();
}
@Override
public void onError(int errorCode) {
switch(errorCode) {
case SetDevicePolicyTask.ERROR_PACKAGE_NOT_INSTALLED:
error(R.string.device_owner_error_package_not_installed);
break;
case SetDevicePolicyTask.ERROR_NO_RECEIVER:
error(R.string.device_owner_error_package_invalid);
break;
default:
error(R.string.device_owner_error_general);
break;
}
statusUpdate(DeviceInitializerStatus.STATUS_ERROR_SET_DEVICE_POLICY);
}
});
mDeleteNonRequiredAppsTask = new DeleteNonRequiredAppsTask(
this, params.inferDeviceAdminPackageName(), R.array.required_apps_managed_device,
R.array.vendor_required_apps_managed_device, true /* creating new profile */,
UserHandle.USER_OWNER, params.leaveAllSystemAppsEnabled,
new DeleteNonRequiredAppsTask.Callback() {
@Override
public void onSuccess() {
// Done with provisioning. Success.
onProvisioningSuccess();
}
@Override
public void onError() {
error(R.string.device_owner_error_general);
statusUpdate(DeviceInitializerStatus.STATUS_ERROR_DELETE_APPS);
}
});
// Start first task, which starts next task in its callback, etc.
progressUpdate(R.string.progress_start_bluetooth);
mBluetoothConnectTask.run();
}
private void error(int dialogMessage) {
mLastErrorMessage = dialogMessage;
sendError();
// Wait for stopService() call from the activity.
}
private void statusUpdate(int statusCode) {
BluetoothConnectionService.sendStatusUpdate(this, statusCode);
}
private void sendError() {
if (DEBUG) {
ProvisionLogger.logd("Reporting Error: " + getResources()
.getString(mLastErrorMessage));
}
Intent intent = new Intent(ACTION_PROVISIONING_ERROR);
intent.setClass(this, DeviceOwnerProvisioningActivity.ServiceMessageReceiver.class);
intent.putExtra(EXTRA_USER_VISIBLE_ERROR_ID_KEY, mLastErrorMessage);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
private void progressUpdate(int progressMessage) {
if (DEBUG) {
ProvisionLogger.logd("Reporting progress update: " + getResources()
.getString(progressMessage));
}
mLastProgressMessage = progressMessage;
sendProgressUpdateToActivity();
}
private void sendProgressUpdateToActivity() {
Intent intent = new Intent(ACTION_PROGRESS_UPDATE);
intent.putExtra(EXTRA_PROGRESS_MESSAGE_ID_KEY, mLastProgressMessage);
intent.setClass(this, DeviceOwnerProvisioningActivity.ServiceMessageReceiver.class);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
private void onProvisioningSuccess() {
if (DEBUG) ProvisionLogger.logd("Reporting success.");
mDone = true;
// Persist mParams so HomeReceiverActivity can later retrieve them to finalize provisioning.
// This is necessary to deal with accidental reboots during DIA setup, which happens between
// the end of this method and HomeReceiverActivity captures the home intent.
IntentStore store = BootReminder.getDeviceOwnerFinalizingIntentStore(this);
Bundle resumeBundle = new Bundle();
(new MessageParser()).addProvisioningParamsToBundle(resumeBundle, mParams);
store.save(resumeBundle);
// Enable the HomeReceiverActivity, since the DeviceOwnerProvisioningActivity will shutdown
// the Setup wizard soon, which will result in a home intent that should be caught by the
// HomeReceiverActivity.
PackageManager pm = getPackageManager();
pm.setComponentEnabledSetting(new ComponentName(DeviceOwnerProvisioningService.this,
HomeReceiverActivity.class), PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
Intent successIntent = new Intent(ACTION_PROVISIONING_SUCCESS);
successIntent.setClass(this, DeviceOwnerProvisioningActivity.ServiceMessageReceiver.class);
LocalBroadcastManager.getInstance(this).sendBroadcast(successIntent);
// Wait for stopService() call from the activity.
}
private void initializeProvisioningEnvironment(ProvisioningParams params) {
setTimeAndTimezone(params.timeZone, params.localTime);
setLocale(params.locale);
// Start CDMA activation to enable phone calls.
final Intent intent = new Intent(ACTION_PERFORM_CDMA_PROVISIONING);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (DEBUG) ProvisionLogger.logd("Starting cdma activation activity");
startActivity(intent); // Activity will be a Nop if not a CDMA device.
}
private void setTimeAndTimezone(String timeZone, long localTime) {
try {
final AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
if (timeZone != null) {
if (DEBUG) ProvisionLogger.logd("Setting time zone to " + timeZone);
am.setTimeZone(timeZone);
}
if (localTime > 0) {
if (DEBUG) 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 {
if (DEBUG) 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 void onCreate () {
if (DEBUG) ProvisionLogger.logd("Device owner provisioning service ONCREATE.");
}
@Override
public void onDestroy () {
if (DEBUG) ProvisionLogger.logd("Device owner provisioning service ONDESTROY");
if (mAddWifiNetworkTask != null) {
mAddWifiNetworkTask.cleanUp();
}
if (mDownloadPackageTask != null) {
mDownloadPackageTask.cleanUp();
}
if (mBluetoothConnectTask != null) {
mBluetoothConnectTask.cleanUp();
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}