From f3d9b3f1b2369b6527242ca04ba2ee361f762f28 Mon Sep 17 00:00:00 2001 From: Gabriele M Date: Thu, 30 Nov 2017 23:41:00 +0100 Subject: [PATCH] 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 --- .../updater/controller/ABUpdateInstaller.java | 104 +++++++++++++----- .../updater/controller/UpdaterController.java | 6 +- .../updater/controller/UpdaterService.java | 22 +++- .../lineageos/updater/model/UpdateStatus.java | 3 +- 4 files changed, 99 insertions(+), 36 deletions(-) diff --git a/src/org/lineageos/updater/controller/ABUpdateInstaller.java b/src/org/lineageos/updater/controller/ABUpdateInstaller.java index c8a632f..d5ccb9c 100644 --- a/src/org/lineageos/updater/controller/ABUpdateInstaller.java +++ b/src/org/lineageos/updater/controller/ABUpdateInstaller.java @@ -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(); + } } diff --git a/src/org/lineageos/updater/controller/UpdaterController.java b/src/org/lineageos/updater/controller/UpdaterController.java index 6ac9e26..9efa8e5 100644 --- a/src/org/lineageos/updater/controller/UpdaterController.java +++ b/src/org/lineageos/updater/controller/UpdaterController.java @@ -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); } } diff --git a/src/org/lineageos/updater/controller/UpdaterService.java b/src/org/lineageos/updater/controller/UpdaterService.java index 317e185..0e0d4dc 100644 --- a/src/org/lineageos/updater/controller/UpdaterService.java +++ b/src/org/lineageos/updater/controller/UpdaterService.java @@ -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; + } } } diff --git a/src/org/lineageos/updater/model/UpdateStatus.java b/src/org/lineageos/updater/model/UpdateStatus.java index c8da537..d1ec67d 100644 --- a/src/org/lineageos/updater/model/UpdateStatus.java +++ b/src/org/lineageos/updater/model/UpdateStatus.java @@ -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;