From 238beb73739071735a6bcbe462e27ab09a747f02 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Wed, 9 May 2018 16:25:40 -0700 Subject: updater_sample: add switch slot demo - Add util/UpdateEngineProperties.java - Set SWITCH_SLOT_ON_REBOOT=0 when update is applied - Allow user to switch slot to the updated partition manually - Add config "ab_config.force_switch_slot" - Add ab_force_switch_slot to tools/gen_update_config.py Test: manually on the marlin device Bug: 79492522 Change-Id: I52f818b576d52a052b5427ba3f732cb2371ddb06 Signed-off-by: Zhomart Mukhamejanov --- .../android/systemupdatersample/UpdateConfig.java | 48 +++++++++++-- .../systemupdatersample/ui/MainActivity.java | 83 ++++++++++++++++++++-- .../util/UpdateEngineProperties.java | 37 ++++++++++ 3 files changed, 157 insertions(+), 11 deletions(-) create mode 100644 updater_sample/src/com/example/android/systemupdatersample/util/UpdateEngineProperties.java (limited to 'updater_sample/src') diff --git a/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java b/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java index 9bdd8b9e8..db99f7c74 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java +++ b/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java @@ -71,7 +71,7 @@ public class UpdateConfig implements Parcelable { JSONObject meta = o.getJSONObject("ab_streaming_metadata"); JSONArray propertyFilesJson = meta.getJSONArray("property_files"); PackageFile[] propertyFiles = - new PackageFile[propertyFilesJson.length()]; + new PackageFile[propertyFilesJson.length()]; for (int i = 0; i < propertyFilesJson.length(); i++) { JSONObject p = propertyFilesJson.getJSONObject(i); propertyFiles[i] = new PackageFile( @@ -87,6 +87,12 @@ public class UpdateConfig implements Parcelable { propertyFiles, authorization); } + + // TODO: parse only for A/B updates when non-A/B is implemented + JSONObject ab = o.getJSONObject("ab_config"); + boolean forceSwitchSlot = ab.getBoolean("force_switch_slot"); + c.mAbConfig = new AbConfig(forceSwitchSlot); + c.mRawJson = json; return c; } @@ -109,6 +115,9 @@ public class UpdateConfig implements Parcelable { /** metadata is required only for streaming update */ private StreamingMetadata mAbStreamingMetadata; + /** A/B update configurations */ + private AbConfig mAbConfig; + private String mRawJson; protected UpdateConfig() { @@ -119,6 +128,7 @@ public class UpdateConfig implements Parcelable { this.mUrl = in.readString(); this.mAbInstallType = in.readInt(); this.mAbStreamingMetadata = (StreamingMetadata) in.readSerializable(); + this.mAbConfig = (AbConfig) in.readSerializable(); this.mRawJson = in.readString(); } @@ -148,6 +158,10 @@ public class UpdateConfig implements Parcelable { return mAbStreamingMetadata; } + public AbConfig getAbConfig() { + return mAbConfig; + } + /** * @return File object for given url */ @@ -172,6 +186,7 @@ public class UpdateConfig implements Parcelable { dest.writeString(mUrl); dest.writeInt(mAbInstallType); dest.writeSerializable(mAbStreamingMetadata); + dest.writeSerializable(mAbConfig); dest.writeString(mRawJson); } @@ -185,9 +200,11 @@ public class UpdateConfig implements Parcelable { /** defines beginning of update data in archive */ private PackageFile[] mPropertyFiles; - /** SystemUpdaterSample receives the authorization token from the OTA server, in addition + /** + * SystemUpdaterSample receives the authorization token from the OTA server, in addition * to the package URL. It passes on the info to update_engine, so that the latter can - * fetch the data from the package server directly with the token. */ + * fetch the data from the package server directly with the token. + */ private String mAuthorization; public StreamingMetadata(PackageFile[] propertyFiles, String authorization) { @@ -239,4 +256,27 @@ public class UpdateConfig implements Parcelable { } } -} + /** + * A/B (seamless) update configurations. + */ + public static class AbConfig implements Serializable { + + private static final long serialVersionUID = 31044L; + + /** + * if set true device will boot to new slot, otherwise user manually + * switches slot on the screen. + */ + private boolean mForceSwitchSlot; + + public AbConfig(boolean forceSwitchSlot) { + this.mForceSwitchSlot = forceSwitchSlot; + } + + public boolean getForceSwitchSlot() { + return mForceSwitchSlot; + } + + } + +} \ No newline at end of file diff --git a/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java b/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java index 170825635..c5a7f9556 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java +++ b/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java @@ -18,6 +18,7 @@ package com.example.android.systemupdatersample.ui; import android.app.Activity; import android.app.AlertDialog; +import android.graphics.Color; import android.os.Build; import android.os.Bundle; import android.os.UpdateEngine; @@ -38,11 +39,13 @@ import com.example.android.systemupdatersample.services.PrepareStreamingService; import com.example.android.systemupdatersample.util.PayloadSpecs; import com.example.android.systemupdatersample.util.UpdateConfigs; import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes; +import com.example.android.systemupdatersample.util.UpdateEngineProperties; import com.example.android.systemupdatersample.util.UpdateEngineStatuses; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; /** @@ -66,10 +69,14 @@ public class MainActivity extends Activity { private ProgressBar mProgressBar; private TextView mTextViewStatus; private TextView mTextViewCompletion; + private TextView mTextViewUpdateInfo; + private Button mButtonSwitchSlot; private List mConfigs; private AtomicInteger mUpdateEngineStatus = new AtomicInteger(UpdateEngine.UpdateStatusConstants.IDLE); + private PayloadSpec mLastPayloadSpec; + private AtomicBoolean mManualSwitchSlotRequired = new AtomicBoolean(true); /** * Listen to {@code update_engine} events. @@ -93,6 +100,8 @@ public class MainActivity extends Activity { this.mProgressBar = findViewById(R.id.progressBar); this.mTextViewStatus = findViewById(R.id.textViewStatus); this.mTextViewCompletion = findViewById(R.id.textViewCompletion); + this.mTextViewUpdateInfo = findViewById(R.id.textViewUpdateInfo); + this.mButtonSwitchSlot = findViewById(R.id.buttonSwitchSlot); this.mTextViewConfigsDirHint.setText(UpdateConfigs.getConfigsRoot(this)); @@ -172,6 +181,13 @@ public class MainActivity extends Activity { .setNegativeButton(android.R.string.cancel, null).show(); } + /** + * switch slot button clicked + */ + public void onSwitchSlotClick(View view) { + setSwitchSlotOnReboot(); + } + /** * Invoked when anything changes. The value of {@code status} will * be one of the values from {@link UpdateEngine.UpdateStatusConstants}, @@ -185,16 +201,16 @@ public class MainActivity extends Activity { Log.e("UpdateEngine", "StatusUpdate - status=" + UpdateEngineStatuses.getStatusText(status) + "/" + status); - setUiStatus(status); Toast.makeText(this, "Update Status changed", Toast.LENGTH_LONG) .show(); - if (status != UpdateEngine.UpdateStatusConstants.IDLE) { - Log.d(TAG, "status changed, setting ui to updating mode"); - uiSetUpdating(); - } else { + if (status == UpdateEngine.UpdateStatusConstants.IDLE) { Log.d(TAG, "status changed, resetting ui"); uiReset(); + } else { + Log.d(TAG, "status changed, setting ui to updating mode"); + uiSetUpdating(); } + setUiStatus(status); }); } } @@ -215,6 +231,13 @@ public class MainActivity extends Activity { + " " + state); Toast.makeText(this, "Update completed", Toast.LENGTH_LONG).show(); setUiCompletion(errorCode); + if (errorCode == UpdateEngineErrorCodes.UPDATED_BUT_NOT_ACTIVE) { + // if update was successfully applied. + if (mManualSwitchSlotRequired.get()) { + // Show "Switch Slot" button. + uiShowSwitchSlotInfo(); + } + } }); } @@ -231,6 +254,7 @@ public class MainActivity extends Activity { mProgressBar.setVisibility(ProgressBar.INVISIBLE); mTextViewStatus.setText(R.string.unknown); mTextViewCompletion.setText(R.string.unknown); + uiHideSwitchSlotInfo(); } /** sets ui updating mode */ @@ -245,6 +269,16 @@ public class MainActivity extends Activity { mProgressBar.setVisibility(ProgressBar.VISIBLE); } + private void uiShowSwitchSlotInfo() { + mButtonSwitchSlot.setEnabled(true); + mTextViewUpdateInfo.setTextColor(Color.parseColor("#777777")); + } + + private void uiHideSwitchSlotInfo() { + mTextViewUpdateInfo.setTextColor(Color.parseColor("#AAAAAA")); + mButtonSwitchSlot.setEnabled(false); + } + /** * loads json configurations from configs dir that is defined in {@link UpdateConfigs}. */ @@ -290,6 +324,17 @@ public class MainActivity extends Activity { * Applies the given update */ private void applyUpdate(final UpdateConfig config) { + List extraProperties = new ArrayList<>(); + + if (!config.getAbConfig().getForceSwitchSlot()) { + // Disable switch slot on reboot, which is enabled by default. + // User will enable it manually by clicking "Switch Slot" button on the screen. + extraProperties.add(UpdateEngineProperties.PROPERTY_DISABLE_SWITCH_SLOT_ON_REBOOT); + mManualSwitchSlotRequired.set(true); + } else { + mManualSwitchSlotRequired.set(false); + } + if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING) { PayloadSpec payload; try { @@ -300,12 +345,11 @@ public class MainActivity extends Activity { .show(); return; } - updateEngineApplyPayload(payload, null); + updateEngineApplyPayload(payload, extraProperties); } else { Log.d(TAG, "Starting PrepareStreamingService"); PrepareStreamingService.startService(this, config, (code, payloadSpec) -> { if (code == PrepareStreamingService.RESULT_CODE_SUCCESS) { - List extraProperties = new ArrayList<>(); extraProperties.add("USER_AGENT=" + HTTP_USER_AGENT); config.getStreamingMetadata() .getAuthorization() @@ -332,6 +376,8 @@ public class MainActivity extends Activity { * @param extraProperties additional properties to pass to {@link UpdateEngine#applyPayload} */ private void updateEngineApplyPayload(PayloadSpec payloadSpec, List extraProperties) { + mLastPayloadSpec = payloadSpec; + ArrayList properties = new ArrayList<>(payloadSpec.getProperties()); if (extraProperties != null) { properties.addAll(extraProperties); @@ -351,6 +397,29 @@ public class MainActivity extends Activity { } } + /** + * Sets the new slot that has the updated partitions as the active slot, + * which device will boot into next time. + * This method is only supposed to be called after the payload is applied. + * + * Invoking {@link UpdateEngine#applyPayload} with the same payload url, offset, size + * and payload metadata headers doesn't trigger new update. It can be used to just switch + * active A/B slot. + * + * {@link UpdateEngine#applyPayload} might take several seconds to finish, and it will + * invoke callbacks {@link this#onStatusUpdate} and {@link this#onPayloadApplicationComplete)}. + */ + private void setSwitchSlotOnReboot() { + Log.d(TAG, "setSwitchSlotOnReboot invoked"); + List extraProperties = new ArrayList<>(); + // 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. + // HTTP headers are not required, UpdateEngine is not expected to stream payload. + updateEngineApplyPayload(mLastPayloadSpec, extraProperties); + uiHideSwitchSlotInfo(); + } + /** * Requests update engine to stop any ongoing update. If an update has been applied, * leave it as is. diff --git a/updater_sample/src/com/example/android/systemupdatersample/util/UpdateEngineProperties.java b/updater_sample/src/com/example/android/systemupdatersample/util/UpdateEngineProperties.java new file mode 100644 index 000000000..e368f14d2 --- /dev/null +++ b/updater_sample/src/com/example/android/systemupdatersample/util/UpdateEngineProperties.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2018 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.example.android.systemupdatersample.util; + +/** + * Utility class for properties that will be passed to {@code UpdateEngine#applyPayload}. + */ +public final class UpdateEngineProperties { + + /** + * The property indicating that the update engine should not switch slot + * when the device reboots. + */ + public static final String PROPERTY_DISABLE_SWITCH_SLOT_ON_REBOOT = "SWITCH_SLOT_ON_REBOOT=0"; + + /** + * The property to skip post-installation. + * https://source.android.com/devices/tech/ota/ab/#post-installation + */ + public static final String PROPERTY_SKIP_POST_INSTALL = "RUN_POST_INSTALL=0"; + + private UpdateEngineProperties() {} +} -- cgit v1.2.3