summaryrefslogtreecommitdiffstats
path: root/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java')
-rw-r--r--updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java266
1 files changed, 223 insertions, 43 deletions
diff --git a/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java b/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java
index c370a4eb5..145cc83b1 100644
--- a/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java
+++ b/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java
@@ -25,11 +25,13 @@ import com.example.android.systemupdatersample.services.PrepareStreamingService;
import com.example.android.systemupdatersample.util.PayloadSpecs;
import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes;
import com.example.android.systemupdatersample.util.UpdateEngineProperties;
-import com.example.android.systemupdatersample.util.UpdaterStates;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.AtomicDouble;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -37,6 +39,8 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.DoubleConsumer;
import java.util.function.IntConsumer;
+import javax.annotation.concurrent.GuardedBy;
+
/**
* Manages the update flow. It has its own state (in memory), separate from
* {@link UpdateEngine}'s state. Asynchronously interacts with the {@link UpdateEngine}.
@@ -56,22 +60,27 @@ public class UpdateManager {
new AtomicInteger(UpdateEngine.UpdateStatusConstants.IDLE);
private AtomicInteger mEngineErrorCode = new AtomicInteger(UpdateEngineErrorCodes.UNKNOWN);
private AtomicDouble mProgress = new AtomicDouble(0);
+ private UpdaterState mUpdaterState = new UpdaterState(UpdaterState.IDLE);
- private AtomicInteger mState = new AtomicInteger(UpdaterStates.IDLE);
-
- private final UpdateManager.UpdateEngineCallbackImpl
- mUpdateEngineCallback = new UpdateManager.UpdateEngineCallbackImpl();
-
- private PayloadSpec mLastPayloadSpec;
private AtomicBoolean mManualSwitchSlotRequired = new AtomicBoolean(true);
+ @GuardedBy("mLock")
+ private UpdateData mLastUpdateData = null;
+
+ @GuardedBy("mLock")
private IntConsumer mOnStateChangeCallback = null;
+ @GuardedBy("mLock")
private IntConsumer mOnEngineStatusUpdateCallback = null;
+ @GuardedBy("mLock")
private DoubleConsumer mOnProgressUpdateCallback = null;
+ @GuardedBy("mLock")
private IntConsumer mOnEngineCompleteCallback = null;
private final Object mLock = new Object();
+ private final UpdateManager.UpdateEngineCallbackImpl
+ mUpdateEngineCallback = new UpdateManager.UpdateEngineCallbackImpl();
+
public UpdateManager(UpdateEngine updateEngine, PayloadSpecs payloadSpecs) {
this.mUpdateEngine = updateEngine;
this.mPayloadSpecs = payloadSpecs;
@@ -108,7 +117,7 @@ public class UpdateManager {
/**
* Sets SystemUpdaterSample app state change callback. Value of {@code state} will be one
- * of the values from {@link UpdaterStates}.
+ * of the values from {@link UpdaterState}.
*
* @param onStateChangeCallback a callback with parameter {@code state}.
*/
@@ -190,8 +199,14 @@ public class UpdateManager {
* it also notifies {@link this.mOnStateChangeCallback}.
*/
private void setUpdaterState(int updaterState) {
- int previousState = mState.get();
- mState.set(updaterState);
+ int previousState = mUpdaterState.get();
+ try {
+ mUpdaterState.set(updaterState);
+ } catch (UpdaterState.InvalidTransitionException e) {
+ // Note: invalid state transitions should be handled properly,
+ // but to make sample app simple, we just throw runtime exception.
+ throw new RuntimeException("Can't set state " + updaterState, e);
+ }
if (previousState != updaterState) {
getOnStateChangeCallback().ifPresent(callback -> callback.accept(updaterState));
}
@@ -208,7 +223,7 @@ public class UpdateManager {
public void cancelRunningUpdate() {
try {
mUpdateEngine.cancel();
- setUpdaterState(UpdaterStates.IDLE);
+ setUpdaterState(UpdaterState.IDLE);
} catch (Exception e) {
Log.w(TAG, "UpdateEngine failed to stop the ongoing update", e);
}
@@ -224,7 +239,7 @@ public class UpdateManager {
public void resetUpdate() {
try {
mUpdateEngine.resetStatus();
- setUpdaterState(UpdaterStates.IDLE);
+ setUpdaterState(UpdaterState.IDLE);
} catch (Exception e) {
Log.w(TAG, "UpdateEngine failed to reset the update", e);
}
@@ -238,7 +253,12 @@ public class UpdateManager {
*/
public void applyUpdate(Context context, UpdateConfig config) {
mEngineErrorCode.set(UpdateEngineErrorCodes.UNKNOWN);
- setUpdaterState(UpdaterStates.RUNNING);
+ setUpdaterState(UpdaterState.RUNNING);
+
+ synchronized (mLock) {
+ // Cleaning up previous update data.
+ mLastUpdateData = null;
+ }
if (!config.getAbConfig().getForceSwitchSlot()) {
mManualSwitchSlotRequired.set(true);
@@ -254,33 +274,35 @@ public class UpdateManager {
}
private void applyAbNonStreamingUpdate(UpdateConfig config) {
- List<String> extraProperties = prepareExtraProperties(config);
+ UpdateData.Builder builder = UpdateData.builder()
+ .setExtraProperties(prepareExtraProperties(config));
- PayloadSpec payload;
try {
- payload = mPayloadSpecs.forNonStreaming(config.getUpdatePackageFile());
+ builder.setPayload(mPayloadSpecs.forNonStreaming(config.getUpdatePackageFile()));
} catch (IOException e) {
Log.e(TAG, "Error creating payload spec", e);
- setUpdaterState(UpdaterStates.ERROR);
+ setUpdaterState(UpdaterState.ERROR);
return;
}
- updateEngineApplyPayload(payload, extraProperties);
+ updateEngineApplyPayload(builder.build());
}
private void applyAbStreamingUpdate(Context context, UpdateConfig config) {
- List<String> extraProperties = prepareExtraProperties(config);
+ UpdateData.Builder builder = UpdateData.builder()
+ .setExtraProperties(prepareExtraProperties(config));
Log.d(TAG, "Starting PrepareStreamingService");
PrepareStreamingService.startService(context, config, (code, payloadSpec) -> {
if (code == PrepareStreamingService.RESULT_CODE_SUCCESS) {
- extraProperties.add("USER_AGENT=" + HTTP_USER_AGENT);
+ builder.setPayload(payloadSpec);
+ builder.addExtraProperty("USER_AGENT=" + HTTP_USER_AGENT);
config.getStreamingMetadata()
.getAuthorization()
- .ifPresent(s -> extraProperties.add("AUTHORIZATION=" + s));
- updateEngineApplyPayload(payloadSpec, extraProperties);
+ .ifPresent(s -> builder.addExtraProperty("AUTHORIZATION=" + s));
+ updateEngineApplyPayload(builder.build());
} else {
Log.e(TAG, "PrepareStreamingService failed, result code is " + code);
- setUpdaterState(UpdaterStates.ERROR);
+ setUpdaterState(UpdaterState.ERROR);
}
});
}
@@ -305,29 +327,40 @@ public class UpdateManager {
* <p>It's possible that the update engine throws a generic error, such as upon seeing invalid
* payload properties (which come from OTA packages), or failing to set up the network
* with the given id.</p>
- *
- * @param payloadSpec contains url, offset and size to {@code PAYLOAD_BINARY_FILE_NAME}
- * @param extraProperties additional properties to pass to {@link UpdateEngine#applyPayload}
*/
- private void updateEngineApplyPayload(PayloadSpec payloadSpec, List<String> extraProperties) {
- mLastPayloadSpec = payloadSpec;
-
- ArrayList<String> properties = new ArrayList<>(payloadSpec.getProperties());
- if (extraProperties != null) {
- properties.addAll(extraProperties);
+ private void updateEngineApplyPayload(UpdateData update) {
+ synchronized (mLock) {
+ mLastUpdateData = update;
}
+
+ ArrayList<String> properties = new ArrayList<>(update.getPayload().getProperties());
+ properties.addAll(update.getExtraProperties());
+
try {
mUpdateEngine.applyPayload(
- payloadSpec.getUrl(),
- payloadSpec.getOffset(),
- payloadSpec.getSize(),
+ update.getPayload().getUrl(),
+ update.getPayload().getOffset(),
+ update.getPayload().getSize(),
properties.toArray(new String[0]));
} catch (Exception e) {
Log.e(TAG, "UpdateEngine failed to apply the update", e);
- setUpdaterState(UpdaterStates.ERROR);
+ setUpdaterState(UpdaterState.ERROR);
}
}
+ private void updateEngineReApplyPayload() {
+ UpdateData lastUpdate;
+ synchronized (mLock) {
+ // mLastPayloadSpec might be empty in some cases.
+ // But to make this sample app simple, we will not handle it.
+ Preconditions.checkArgument(
+ mLastUpdateData != null,
+ "mLastUpdateData must be present.");
+ lastUpdate = mLastUpdateData;
+ }
+ updateEngineApplyPayload(lastUpdate);
+ }
+
/**
* Sets the new slot that has the updated partitions as the active slot,
* which device will boot into next time.
@@ -342,19 +375,101 @@ public class UpdateManager {
*/
public void setSwitchSlotOnReboot() {
Log.d(TAG, "setSwitchSlotOnReboot invoked");
- List<String> extraProperties = new ArrayList<>();
+ UpdateData.Builder builder;
+ synchronized (mLock) {
+ // To make sample app simple, we don't handle it.
+ Preconditions.checkArgument(
+ mLastUpdateData != null,
+ "mLastUpdateData must be present.");
+ builder = mLastUpdateData.toBuilder();
+ }
// PROPERTY_SKIP_POST_INSTALL should be passed on to skip post-installation hooks.
- extraProperties.add(UpdateEngineProperties.PROPERTY_SKIP_POST_INSTALL);
- // It sets property SWITCH_SLOT_ON_REBOOT=1 by default.
+ builder.setExtraProperties(
+ Collections.singletonList(UpdateEngineProperties.PROPERTY_SKIP_POST_INSTALL));
+ // UpdateEngine sets property SWITCH_SLOT_ON_REBOOT=1 by default.
// HTTP headers are not required, UpdateEngine is not expected to stream payload.
- updateEngineApplyPayload(mLastPayloadSpec, extraProperties);
+ updateEngineApplyPayload(builder.build());
+ }
+
+ /**
+ * Verifies if mUpdaterState matches mUpdateEngineStatus.
+ * If they don't match, runs applyPayload to trigger onPayloadApplicationComplete
+ * callback, which updates mUpdaterState.
+ */
+ private void ensureCorrectUpdaterState() {
+ // When mUpdaterState is one of IDLE, PAUSED, ERROR, SLOT_SWITCH_REQUIRED
+ // then mUpdateEngineStatus must be IDLE.
+ // When mUpdaterState is RUNNING,
+ // then mUpdateEngineStatus must not be IDLE or UPDATED_NEED_REBOOT.
+ // When mUpdaterState is REBOOT_REQUIRED,
+ // then mUpdateEngineStatus must be UPDATED_NEED_REBOOT.
+ int state = mUpdaterState.get();
+ int updateEngineStatus = mUpdateEngineStatus.get();
+ if (state == UpdaterState.IDLE
+ || state == UpdaterState.ERROR
+ || state == UpdaterState.PAUSED
+ || state == UpdaterState.SLOT_SWITCH_REQUIRED) {
+ ensureUpdateEngineStatusIdle(state, updateEngineStatus);
+ } else if (state == UpdaterState.RUNNING) {
+ ensureUpdateEngineStatusRunning(state, updateEngineStatus);
+ } else if (state == UpdaterState.REBOOT_REQUIRED) {
+ ensureUpdateEngineStatusReboot(state, updateEngineStatus);
+ }
}
+ private void ensureUpdateEngineStatusIdle(int state, int updateEngineStatus) {
+ if (updateEngineStatus == UpdateEngine.UpdateStatusConstants.IDLE) {
+ return;
+ }
+ // It might happen when update is started not from the sample app.
+ // To make the sample app simple, we won't handle this case.
+ throw new RuntimeException("When mUpdaterState is " + state
+ + " mUpdateEngineStatus expected to be "
+ + UpdateEngine.UpdateStatusConstants.IDLE
+ + ", but it is " + updateEngineStatus);
+ }
+
+ private void ensureUpdateEngineStatusRunning(int state, int updateEngineStatus) {
+ if (updateEngineStatus != UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT
+ && updateEngineStatus != UpdateEngine.UpdateStatusConstants.IDLE) {
+ return;
+ }
+ // Re-apply latest update. It makes update_engine to invoke
+ // onPayloadApplicationComplete callback. The callback notifies
+ // if update was successful or not.
+ updateEngineReApplyPayload();
+ }
+
+ private void ensureUpdateEngineStatusReboot(int state, int updateEngineStatus) {
+ if (updateEngineStatus == UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT) {
+ return;
+ }
+ // This might happen when update is installed by other means,
+ // and sample app is not aware of it. To make the sample app simple,
+ // we won't handle this case.
+ throw new RuntimeException("When mUpdaterState is " + state
+ + " mUpdateEngineStatus expected to be "
+ + UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT
+ + ", but it is " + updateEngineStatus);
+ }
+
+ /**
+ * Invoked by update_engine whenever update status or progress changes.
+ * It's also guaranteed to be invoked when app binds to the update_engine, except
+ * when update_engine fails to initialize (as defined in
+ * system/update_engine/binder_service_android.cc in
+ * function BinderUpdateEngineAndroidService::bind).
+ *
+ * @param status one of {@link UpdateEngine.UpdateStatusConstants}.
+ * @param progress a number from 0.0 to 1.0.
+ */
private void onStatusUpdate(int status, float progress) {
int previousStatus = mUpdateEngineStatus.get();
mUpdateEngineStatus.set(status);
mProgress.set(progress);
+ ensureCorrectUpdaterState();
+
getOnProgressUpdateCallback().ifPresent(callback -> callback.accept(progress));
if (previousStatus != status) {
@@ -367,9 +482,11 @@ public class UpdateManager {
mEngineErrorCode.set(errorCode);
if (errorCode == UpdateEngine.ErrorCodeConstants.SUCCESS
|| errorCode == UpdateEngineErrorCodes.UPDATED_BUT_NOT_ACTIVE) {
- setUpdaterState(UpdaterStates.FINISHED);
+ setUpdaterState(isManualSwitchSlotRequired()
+ ? UpdaterState.SLOT_SWITCH_REQUIRED
+ : UpdaterState.REBOOT_REQUIRED);
} else if (errorCode != UpdateEngineErrorCodes.USER_CANCELLED) {
- setUpdaterState(UpdaterStates.ERROR);
+ setUpdaterState(UpdaterState.ERROR);
}
getOnEngineCompleteCallback()
@@ -377,7 +494,7 @@ public class UpdateManager {
}
/**
- * Helper class to delegate {@code update_engine} callbacks to UpdateManager
+ * Helper class to delegate {@code update_engine} callback invocations to UpdateManager.
*/
class UpdateEngineCallbackImpl extends UpdateEngineCallback {
@Override
@@ -391,4 +508,67 @@ public class UpdateManager {
}
}
+ /**
+ *
+ * Contains update data - PayloadSpec and extra properties list.
+ *
+ * <p>{@code mPayload} contains url, offset and size to {@code PAYLOAD_BINARY_FILE_NAME}.
+ * {@code mExtraProperties} is a list of additional properties to pass to
+ * {@link UpdateEngine#applyPayload}.</p>
+ */
+ private static class UpdateData {
+ private final PayloadSpec mPayload;
+ private final ImmutableList<String> mExtraProperties;
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ UpdateData(Builder builder) {
+ this.mPayload = builder.mPayload;
+ this.mExtraProperties = ImmutableList.copyOf(builder.mExtraProperties);
+ }
+
+ public PayloadSpec getPayload() {
+ return mPayload;
+ }
+
+ public ImmutableList<String> getExtraProperties() {
+ return mExtraProperties;
+ }
+
+ public Builder toBuilder() {
+ return builder()
+ .setPayload(mPayload)
+ .setExtraProperties(mExtraProperties);
+ }
+
+ static class Builder {
+ private PayloadSpec mPayload;
+ private List<String> mExtraProperties;
+
+ public Builder setPayload(PayloadSpec payload) {
+ this.mPayload = payload;
+ return this;
+ }
+
+ public Builder setExtraProperties(List<String> extraProperties) {
+ this.mExtraProperties = new ArrayList<>(extraProperties);
+ return this;
+ }
+
+ public Builder addExtraProperty(String property) {
+ if (this.mExtraProperties == null) {
+ this.mExtraProperties = new ArrayList<>();
+ }
+ this.mExtraProperties.add(property);
+ return this;
+ }
+
+ public UpdateData build() {
+ return new UpdateData(this);
+ }
+ }
+ }
+
}