Allow service restarts while installing AB updates
The update engine service is independent and once started doesn't need our service to install updates. Therefore we can't assume that our service will stay up as long as the installation is being performed. If the service gets terminated while an update is being installed, we simply lose our connection to the update engine service and stop receiving notifications, the installation itself won't stop. Keep track of ongoing installations using a shared preference and use a sticky service when installing updates. The service will try to re-connect to the update engine service and determine if the installation is still ongoing. Change-Id: Id2fc11cab51610d04bf41a0927824bb8c0c94d71
This commit is contained in:
@@ -41,15 +41,26 @@ class ABUpdateInstaller {
|
||||
|
||||
private static final String TAG = "ABUpdateInstaller";
|
||||
|
||||
private static String sDownloadId;
|
||||
private static final String PREF_INSTALLING_AB_ID = "installing_ab_id";
|
||||
|
||||
private final UpdaterController mUpdaterController;
|
||||
private final String mDownloadId;
|
||||
private final Context mContext;
|
||||
private String mDownloadId;
|
||||
private boolean mReconnecting;
|
||||
|
||||
private UpdateEngine mUpdateEngine;
|
||||
|
||||
private final UpdateEngineCallback mUpdateEngineCallback = new UpdateEngineCallback() {
|
||||
|
||||
@Override
|
||||
public void onStatusUpdate(int status, float percent) {
|
||||
Update update = mUpdaterController.getActualUpdate(mDownloadId);
|
||||
if (update == null) {
|
||||
// We read the id from a preference, the update could no longer exist
|
||||
installationDone();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case UpdateEngine.UpdateStatusConstants.DOWNLOADING:
|
||||
case UpdateEngine.UpdateStatusConstants.FINALIZING: {
|
||||
@@ -60,8 +71,7 @@ class ABUpdateInstaller {
|
||||
break;
|
||||
|
||||
case UpdateEngine.UpdateStatusConstants.REPORTING_ERROR_EVENT: {
|
||||
sDownloadId = null;
|
||||
Update update = mUpdaterController.getActualUpdate(mDownloadId);
|
||||
installationDone();
|
||||
update.setInstallProgress(0);
|
||||
update.setStatus(UpdateStatus.INSTALLATION_FAILED);
|
||||
mUpdaterController.notifyUpdateChange(mDownloadId);
|
||||
@@ -69,8 +79,7 @@ class ABUpdateInstaller {
|
||||
break;
|
||||
|
||||
case UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT: {
|
||||
sDownloadId = null;
|
||||
Update update = mUpdaterController.getActualUpdate(mDownloadId);
|
||||
installationDone();
|
||||
update.setInstallProgress(0);
|
||||
update.setStatus(UpdateStatus.INSTALLED);
|
||||
mUpdaterController.notifyUpdateChange(mDownloadId);
|
||||
@@ -83,6 +92,16 @@ class ABUpdateInstaller {
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case UpdateEngine.UpdateStatusConstants.IDLE: {
|
||||
if (mReconnecting) {
|
||||
// The service was restarted because we thought we were installing an
|
||||
// update, but we aren't, so clear everything.
|
||||
installationDone();
|
||||
mReconnecting = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,35 +110,29 @@ class ABUpdateInstaller {
|
||||
}
|
||||
};
|
||||
|
||||
static synchronized boolean start(Context context, UpdaterController updaterController,
|
||||
String downloadId) {
|
||||
if (sDownloadId != null) {
|
||||
return false;
|
||||
}
|
||||
ABUpdateInstaller installer = new ABUpdateInstaller(context, updaterController, downloadId);
|
||||
if (installer.startUpdate()) {
|
||||
sDownloadId = downloadId;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
static synchronized boolean isInstallingUpdate(Context context) {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getString(ABUpdateInstaller.PREF_INSTALLING_AB_ID, null) != null;
|
||||
}
|
||||
|
||||
static synchronized boolean isInstallingUpdate() {
|
||||
return sDownloadId != null;
|
||||
static synchronized boolean isInstallingUpdate(Context context, String downloadId) {
|
||||
return downloadId.equals(PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getString(ABUpdateInstaller.PREF_INSTALLING_AB_ID, null));
|
||||
}
|
||||
|
||||
static synchronized boolean isInstallingUpdate(String downloadId) {
|
||||
return sDownloadId != null && sDownloadId.equals(downloadId);
|
||||
}
|
||||
|
||||
private ABUpdateInstaller(Context context, UpdaterController updaterController,
|
||||
String downloadId) {
|
||||
ABUpdateInstaller(Context context, UpdaterController updaterController) {
|
||||
mUpdaterController = updaterController;
|
||||
mDownloadId = downloadId;
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
private boolean startUpdate() {
|
||||
public boolean install(String downloadId) {
|
||||
if (isInstallingUpdate(mContext)) {
|
||||
Log.e(TAG, "Already installing an update");
|
||||
return false;
|
||||
}
|
||||
|
||||
mDownloadId = downloadId;
|
||||
|
||||
File file = mUpdaterController.getActualUpdate(mDownloadId).getFile();
|
||||
if (!file.exists()) {
|
||||
Log.e(TAG, "The given update doesn't exist");
|
||||
@@ -154,11 +167,42 @@ class ABUpdateInstaller {
|
||||
return false;
|
||||
}
|
||||
|
||||
UpdateEngine updateEngine = new UpdateEngine();
|
||||
updateEngine.bind(mUpdateEngineCallback);
|
||||
mUpdateEngine = new UpdateEngine();
|
||||
if (!mUpdateEngine.bind(mUpdateEngineCallback)) {
|
||||
Log.e(TAG, "Could not bind");
|
||||
return false;
|
||||
}
|
||||
String zipFileUri = "file://" + file.getAbsolutePath();
|
||||
updateEngine.applyPayload(zipFileUri, offset, 0, headerKeyValuePairs);
|
||||
mUpdateEngine.applyPayload(zipFileUri, offset, 0, headerKeyValuePairs);
|
||||
|
||||
mUpdaterController.getActualUpdate(mDownloadId).setStatus(UpdateStatus.INSTALLING);
|
||||
mUpdaterController.notifyUpdateChange(mDownloadId);
|
||||
|
||||
PreferenceManager.getDefaultSharedPreferences(mContext).edit()
|
||||
.putString(PREF_INSTALLING_AB_ID, mDownloadId)
|
||||
.apply();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean reconnect() {
|
||||
if (!isInstallingUpdate(mContext)) {
|
||||
Log.e(TAG, "reconnect: Not installing any update");
|
||||
return false;
|
||||
}
|
||||
|
||||
mReconnecting = true;
|
||||
mDownloadId = PreferenceManager.getDefaultSharedPreferences(mContext)
|
||||
.getString(PREF_INSTALLING_AB_ID, null);
|
||||
|
||||
mUpdateEngine = new UpdateEngine();
|
||||
// We will get a status notification as soon as we are connected
|
||||
return mUpdateEngine.bind(mUpdateEngineCallback);
|
||||
}
|
||||
|
||||
private void installationDone() {
|
||||
PreferenceManager.getDefaultSharedPreferences(mContext).edit()
|
||||
.remove(PREF_INSTALLING_AB_ID)
|
||||
.apply();
|
||||
}
|
||||
}
|
||||
|
@@ -53,6 +53,7 @@ public class UpdaterController implements Controller {
|
||||
|
||||
private static final int MAX_REPORT_INTERVAL_MS = 1000;
|
||||
|
||||
private final Context mContext;
|
||||
private final LocalBroadcastManager mBroadcastManager;
|
||||
private final UpdatesDbHelper mUpdatesDbHelper;
|
||||
|
||||
@@ -81,6 +82,7 @@ public class UpdaterController implements Controller {
|
||||
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Updater");
|
||||
mWakeLock.setReferenceCounted(false);
|
||||
mContext = context.getApplicationContext();
|
||||
|
||||
Utils.cleanupDownloadsDir(context);
|
||||
|
||||
@@ -532,11 +534,11 @@ public class UpdaterController implements Controller {
|
||||
|
||||
@Override
|
||||
public boolean isInstallingUpdate() {
|
||||
return ABUpdateInstaller.isInstallingUpdate();
|
||||
return ABUpdateInstaller.isInstallingUpdate(mContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInstallingUpdate(String downloadId) {
|
||||
return ABUpdateInstaller.isInstallingUpdate(downloadId);
|
||||
return ABUpdateInstaller.isInstallingUpdate(mContext, downloadId);
|
||||
}
|
||||
}
|
||||
|
@@ -153,7 +153,16 @@ public class UpdaterService extends Service {
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (ACTION_DOWNLOAD_CONTROL.equals(intent.getAction())) {
|
||||
Log.d(TAG, "Starting service");
|
||||
|
||||
if ((intent == null || intent.getAction() == null) &&
|
||||
ABUpdateInstaller.isInstallingUpdate(this)) {
|
||||
// The service is being restarted.
|
||||
ABUpdateInstaller installer = new ABUpdateInstaller(this, mUpdaterController);
|
||||
if (installer.reconnect()) {
|
||||
return START_STICKY;
|
||||
}
|
||||
} else if (ACTION_DOWNLOAD_CONTROL.equals(intent.getAction())) {
|
||||
String downloadId = intent.getStringExtra(EXTRA_DOWNLOAD_ID);
|
||||
int action = intent.getIntExtra(EXTRA_DOWNLOAD_CONTROL, -1);
|
||||
if (action == DOWNLOAD_RESUME) {
|
||||
@@ -171,7 +180,10 @@ public class UpdaterService extends Service {
|
||||
}
|
||||
try {
|
||||
if (Utils.isABUpdate(update.getFile())) {
|
||||
ABUpdateInstaller.start(this, mUpdaterController, downloadId);
|
||||
ABUpdateInstaller installer = new ABUpdateInstaller(this, mUpdaterController);
|
||||
if (installer.install(downloadId)) {
|
||||
return START_STICKY;
|
||||
}
|
||||
} else {
|
||||
boolean deleteUpdate = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.getBoolean(Constants.PREF_AUTO_DELETE_UPDATES, false);
|
||||
@@ -202,7 +214,6 @@ public class UpdaterService extends Service {
|
||||
// TODO: user facing message
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "Service started");
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@@ -370,6 +381,11 @@ public class UpdaterService extends Service {
|
||||
tryStopSelf();
|
||||
break;
|
||||
}
|
||||
case INSTALLATION_CANCELLED: {
|
||||
stopForeground(true);
|
||||
tryStopSelf();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -28,7 +28,8 @@ public enum UpdateStatus {
|
||||
VERIFICATION_FAILED,
|
||||
INSTALLING,
|
||||
INSTALLED,
|
||||
INSTALLATION_FAILED;
|
||||
INSTALLATION_FAILED,
|
||||
INSTALLATION_CANCELLED;
|
||||
|
||||
public static final class Persistent {
|
||||
public static final int UNKNOWN = 0;
|
||||
|
Reference in New Issue
Block a user