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 <zhomart@google.com>
This commit is contained in:
Zhomart Mukhamejanov
2018-05-09 16:25:40 -07:00
parent 5b3d6cf58b
commit 238beb7373
11 changed files with 210 additions and 16 deletions
@@ -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;
}
}
}
@@ -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<UpdateConfig> 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<String> 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<String> 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<String> extraProperties) {
mLastPayloadSpec = payloadSpec;
ArrayList<String> 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<String> 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.
@@ -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() {}
}