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:
Gabriele M
2017-11-30 23:41:00 +01:00
parent 6088f26f53
commit f3d9b3f1b2
4 changed files with 99 additions and 36 deletions

View File

@@ -41,15 +41,26 @@ class ABUpdateInstaller {
private static final String TAG = "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 UpdaterController mUpdaterController;
private final String mDownloadId;
private final Context mContext; private final Context mContext;
private String mDownloadId;
private boolean mReconnecting;
private UpdateEngine mUpdateEngine;
private final UpdateEngineCallback mUpdateEngineCallback = new UpdateEngineCallback() { private final UpdateEngineCallback mUpdateEngineCallback = new UpdateEngineCallback() {
@Override @Override
public void onStatusUpdate(int status, float percent) { 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) { switch (status) {
case UpdateEngine.UpdateStatusConstants.DOWNLOADING: case UpdateEngine.UpdateStatusConstants.DOWNLOADING:
case UpdateEngine.UpdateStatusConstants.FINALIZING: { case UpdateEngine.UpdateStatusConstants.FINALIZING: {
@@ -60,8 +71,7 @@ class ABUpdateInstaller {
break; break;
case UpdateEngine.UpdateStatusConstants.REPORTING_ERROR_EVENT: { case UpdateEngine.UpdateStatusConstants.REPORTING_ERROR_EVENT: {
sDownloadId = null; installationDone();
Update update = mUpdaterController.getActualUpdate(mDownloadId);
update.setInstallProgress(0); update.setInstallProgress(0);
update.setStatus(UpdateStatus.INSTALLATION_FAILED); update.setStatus(UpdateStatus.INSTALLATION_FAILED);
mUpdaterController.notifyUpdateChange(mDownloadId); mUpdaterController.notifyUpdateChange(mDownloadId);
@@ -69,8 +79,7 @@ class ABUpdateInstaller {
break; break;
case UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT: { case UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT: {
sDownloadId = null; installationDone();
Update update = mUpdaterController.getActualUpdate(mDownloadId);
update.setInstallProgress(0); update.setInstallProgress(0);
update.setStatus(UpdateStatus.INSTALLED); update.setStatus(UpdateStatus.INSTALLED);
mUpdaterController.notifyUpdateChange(mDownloadId); mUpdaterController.notifyUpdateChange(mDownloadId);
@@ -83,6 +92,16 @@ class ABUpdateInstaller {
} }
} }
break; 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, static synchronized boolean isInstallingUpdate(Context context) {
String downloadId) { return PreferenceManager.getDefaultSharedPreferences(context)
if (sDownloadId != null) { .getString(ABUpdateInstaller.PREF_INSTALLING_AB_ID, null) != null;
return false;
}
ABUpdateInstaller installer = new ABUpdateInstaller(context, updaterController, downloadId);
if (installer.startUpdate()) {
sDownloadId = downloadId;
return true;
}
return false;
} }
static synchronized boolean isInstallingUpdate() { static synchronized boolean isInstallingUpdate(Context context, String downloadId) {
return sDownloadId != null; return downloadId.equals(PreferenceManager.getDefaultSharedPreferences(context)
.getString(ABUpdateInstaller.PREF_INSTALLING_AB_ID, null));
} }
static synchronized boolean isInstallingUpdate(String downloadId) { ABUpdateInstaller(Context context, UpdaterController updaterController) {
return sDownloadId != null && sDownloadId.equals(downloadId);
}
private ABUpdateInstaller(Context context, UpdaterController updaterController,
String downloadId) {
mUpdaterController = updaterController; mUpdaterController = updaterController;
mDownloadId = downloadId;
mContext = context; 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(); File file = mUpdaterController.getActualUpdate(mDownloadId).getFile();
if (!file.exists()) { if (!file.exists()) {
Log.e(TAG, "The given update doesn't exist"); Log.e(TAG, "The given update doesn't exist");
@@ -154,11 +167,42 @@ class ABUpdateInstaller {
return false; return false;
} }
UpdateEngine updateEngine = new UpdateEngine(); mUpdateEngine = new UpdateEngine();
updateEngine.bind(mUpdateEngineCallback); if (!mUpdateEngine.bind(mUpdateEngineCallback)) {
Log.e(TAG, "Could not bind");
return false;
}
String zipFileUri = "file://" + file.getAbsolutePath(); 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; 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();
}
} }

View File

@@ -53,6 +53,7 @@ public class UpdaterController implements Controller {
private static final int MAX_REPORT_INTERVAL_MS = 1000; private static final int MAX_REPORT_INTERVAL_MS = 1000;
private final Context mContext;
private final LocalBroadcastManager mBroadcastManager; private final LocalBroadcastManager mBroadcastManager;
private final UpdatesDbHelper mUpdatesDbHelper; private final UpdatesDbHelper mUpdatesDbHelper;
@@ -81,6 +82,7 @@ public class UpdaterController implements Controller {
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Updater"); mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Updater");
mWakeLock.setReferenceCounted(false); mWakeLock.setReferenceCounted(false);
mContext = context.getApplicationContext();
Utils.cleanupDownloadsDir(context); Utils.cleanupDownloadsDir(context);
@@ -532,11 +534,11 @@ public class UpdaterController implements Controller {
@Override @Override
public boolean isInstallingUpdate() { public boolean isInstallingUpdate() {
return ABUpdateInstaller.isInstallingUpdate(); return ABUpdateInstaller.isInstallingUpdate(mContext);
} }
@Override @Override
public boolean isInstallingUpdate(String downloadId) { public boolean isInstallingUpdate(String downloadId) {
return ABUpdateInstaller.isInstallingUpdate(downloadId); return ABUpdateInstaller.isInstallingUpdate(mContext, downloadId);
} }
} }

View File

@@ -153,7 +153,16 @@ public class UpdaterService extends Service {
@Override @Override
public int onStartCommand(Intent intent, int flags, int startId) { 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); String downloadId = intent.getStringExtra(EXTRA_DOWNLOAD_ID);
int action = intent.getIntExtra(EXTRA_DOWNLOAD_CONTROL, -1); int action = intent.getIntExtra(EXTRA_DOWNLOAD_CONTROL, -1);
if (action == DOWNLOAD_RESUME) { if (action == DOWNLOAD_RESUME) {
@@ -171,7 +180,10 @@ public class UpdaterService extends Service {
} }
try { try {
if (Utils.isABUpdate(update.getFile())) { if (Utils.isABUpdate(update.getFile())) {
ABUpdateInstaller.start(this, mUpdaterController, downloadId); ABUpdateInstaller installer = new ABUpdateInstaller(this, mUpdaterController);
if (installer.install(downloadId)) {
return START_STICKY;
}
} else { } else {
boolean deleteUpdate = PreferenceManager.getDefaultSharedPreferences(this) boolean deleteUpdate = PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(Constants.PREF_AUTO_DELETE_UPDATES, false); .getBoolean(Constants.PREF_AUTO_DELETE_UPDATES, false);
@@ -202,7 +214,6 @@ public class UpdaterService extends Service {
// TODO: user facing message // TODO: user facing message
} }
} }
Log.d(TAG, "Service started");
return START_NOT_STICKY; return START_NOT_STICKY;
} }
@@ -370,6 +381,11 @@ public class UpdaterService extends Service {
tryStopSelf(); tryStopSelf();
break; break;
} }
case INSTALLATION_CANCELLED: {
stopForeground(true);
tryStopSelf();
break;
}
} }
} }

View File

@@ -28,7 +28,8 @@ public enum UpdateStatus {
VERIFICATION_FAILED, VERIFICATION_FAILED,
INSTALLING, INSTALLING,
INSTALLED, INSTALLED,
INSTALLATION_FAILED; INSTALLATION_FAILED,
INSTALLATION_CANCELLED;
public static final class Persistent { public static final class Persistent {
public static final int UNKNOWN = 0; public static final int UNKNOWN = 0;