summaryrefslogtreecommitdiffstats
path: root/updater
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2018-06-01 11:31:14 +0200
committerandroid-build-team Robot <android-build-team-robot@google.com>2018-06-01 11:31:14 +0200
commit6611560b8c828874738e537a4e1f787fc34db783 (patch)
tree863032ef175cf97c713f7cb16d2a3d8efe38c654 /updater
parentSnap for 4806815 from e76ed816b94f1e89be3f965152ea83dd48f67b51 to qt-release (diff)
parentMerge "recovery: add --fsck_unshare_blocks option for adb remount" am: bda4495176 am: b0d9b3594b (diff)
downloadandroid_bootable_recovery-6611560b8c828874738e537a4e1f787fc34db783.tar
android_bootable_recovery-6611560b8c828874738e537a4e1f787fc34db783.tar.gz
android_bootable_recovery-6611560b8c828874738e537a4e1f787fc34db783.tar.bz2
android_bootable_recovery-6611560b8c828874738e537a4e1f787fc34db783.tar.lz
android_bootable_recovery-6611560b8c828874738e537a4e1f787fc34db783.tar.xz
android_bootable_recovery-6611560b8c828874738e537a4e1f787fc34db783.tar.zst
android_bootable_recovery-6611560b8c828874738e537a4e1f787fc34db783.zip
Diffstat (limited to '')
-rw-r--r--updater/Android.mk1
-rw-r--r--updater/blockimg.cpp140
-rw-r--r--updater/commands.cpp43
-rw-r--r--updater/include/private/commands.h35
-rw-r--r--updater_sample/Android.mk2
-rw-r--r--updater_sample/README.md68
-rw-r--r--updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java266
-rw-r--r--updater_sample/src/com/example/android/systemupdatersample/UpdaterState.java103
-rw-r--r--updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java14
-rw-r--r--updater_sample/src/com/example/android/systemupdatersample/util/UpdaterStates.java50
10 files changed, 545 insertions, 177 deletions
diff --git a/updater/Android.mk b/updater/Android.mk
index 476266400..46c56f4a0 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -56,6 +56,7 @@ include $(CLEAR_VARS)
LOCAL_MODULE := libupdater
LOCAL_SRC_FILES := \
+ commands.cpp \
install.cpp \
blockimg.cpp
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index 4a70b98a1..4adb974cb 100644
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -57,6 +57,7 @@
#include "otautil/paths.h"
#include "otautil/print_sha1.h"
#include "otautil/rangeset.h"
+#include "private/commands.h"
#include "updater/install.h"
#include "updater/updater.h"
@@ -546,8 +547,8 @@ static int WriteBlocks(const RangeSet& tgt, const std::vector<uint8_t>& buffer,
struct CommandParameters {
std::vector<std::string> tokens;
size_t cpos;
- const char* cmdname;
- const char* cmdline;
+ std::string cmdname;
+ std::string cmdline;
std::string freestash;
std::string stashbase;
bool canwrite;
@@ -750,7 +751,7 @@ static void DeleteStash(const std::string& base) {
}
}
-static int LoadStash(CommandParameters& params, const std::string& id, bool verify, size_t* blocks,
+static int LoadStash(CommandParameters& params, const std::string& id, bool verify,
std::vector<uint8_t>& buffer, bool printnoent) {
// In verify mode, if source range_set was saved for the given hash, check contents in the source
// blocks first. If the check fails, search for the stashed files on /cache as usual.
@@ -772,11 +773,6 @@ static int LoadStash(CommandParameters& params, const std::string& id, bool veri
}
}
- size_t blockcount = 0;
- if (!blocks) {
- blocks = &blockcount;
- }
-
std::string fn = GetStashFileName(params.stashbase, id, "");
struct stat sb;
@@ -807,9 +803,8 @@ static int LoadStash(CommandParameters& params, const std::string& id, bool veri
return -1;
}
- *blocks = sb.st_size / BLOCKSIZE;
-
- if (verify && VerifyBlocks(id, buffer, *blocks, true) != 0) {
+ size_t blocks = sb.st_size / BLOCKSIZE;
+ if (verify && VerifyBlocks(id, buffer, blocks, true) != 0) {
LOG(ERROR) << "unexpected contents in " << fn;
if (stash_map.find(id) == stash_map.end()) {
LOG(ERROR) << "failed to find source blocks number for stash " << id
@@ -1055,7 +1050,7 @@ static int LoadSourceBlocks(CommandParameters& params, const RangeSet& tgt, size
}
std::vector<uint8_t> stash;
- if (LoadStash(params, tokens[0], false, nullptr, stash, true) == -1) {
+ if (LoadStash(params, tokens[0], false, stash, true) == -1) {
// These source blocks will fail verification if used later, but we
// will let the caller decide if this is a fatal failure
LOG(ERROR) << "failed to load stash " << tokens[0];
@@ -1170,7 +1165,7 @@ static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t*
return 0;
}
- if (*overlap && LoadStash(params, srchash, true, nullptr, params.buffer, true) == 0) {
+ if (*overlap && LoadStash(params, srchash, true, params.buffer, true) == 0) {
// Overlapping source blocks were previously stashed, command can proceed. We are recovering
// from an interrupted command, so we don't know if the stash can safely be deleted after this
// command.
@@ -1236,8 +1231,7 @@ static int PerformCommandStash(CommandParameters& params) {
}
const std::string& id = params.tokens[params.cpos++];
- size_t blocks = 0;
- if (LoadStash(params, id, true, &blocks, params.buffer, false) == 0) {
+ if (LoadStash(params, id, true, params.buffer, false) == 0) {
// Stash file already exists and has expected contents. Do not read from source again, as the
// source may have been already overwritten during a previous attempt.
return 0;
@@ -1246,11 +1240,11 @@ static int PerformCommandStash(CommandParameters& params) {
RangeSet src = RangeSet::Parse(params.tokens[params.cpos++]);
CHECK(static_cast<bool>(src));
- allocate(src.blocks() * BLOCKSIZE, params.buffer);
+ size_t blocks = src.blocks();
+ allocate(blocks * BLOCKSIZE, params.buffer);
if (ReadBlocks(src, params.buffer, params.fd) == -1) {
return -1;
}
- blocks = src.blocks();
stash_map[id] = src;
if (VerifyBlocks(id, params.buffer, blocks, true) != 0) {
@@ -1496,23 +1490,13 @@ static int PerformCommandErase(CommandParameters& params) {
return 0;
}
-// Definitions for transfer list command functions
-typedef int (*CommandFunction)(CommandParameters&);
+using CommandFunction = std::function<int(CommandParameters&)>;
-struct Command {
- const char* name;
- CommandFunction f;
-};
-
-// args:
-// - block device (or file) to modify in-place
-// - transfer list (blob)
-// - new data stream (filename within package.zip)
-// - patch stream (filename within package.zip, must be uncompressed)
+using CommandMap = std::unordered_map<Command::Type, CommandFunction>;
static Value* PerformBlockImageUpdate(const char* name, State* state,
const std::vector<std::unique_ptr<Expr>>& argv,
- const Command* commands, size_t cmdcount, bool dryrun) {
+ const CommandMap& command_map, bool dryrun) {
CommandParameters params = {};
params.canwrite = !dryrun;
@@ -1532,6 +1516,11 @@ static Value* PerformBlockImageUpdate(const char* name, State* state,
return nullptr;
}
+ // args:
+ // - block device (or file) to modify in-place
+ // - transfer list (blob)
+ // - new data stream (filename within package.zip)
+ // - patch stream (filename within package.zip, must be uncompressed)
const std::unique_ptr<Value>& blockdev_filename = args[0];
const std::unique_ptr<Value>& transfer_list_value = args[1];
const std::unique_ptr<Value>& new_data_fn = args[2];
@@ -1707,16 +1696,6 @@ static Value* PerformBlockImageUpdate(const char* name, State* state,
skip_executed_command = false;
}
- // Build a map of the available commands
- std::unordered_map<std::string, const Command*> cmd_map;
- for (size_t i = 0; i < cmdcount; ++i) {
- if (cmd_map.find(commands[i].name) != cmd_map.end()) {
- LOG(ERROR) << "Error: command [" << commands[i].name << "] already exists in the cmd map.";
- return StringValue("");
- }
- cmd_map[commands[i].name] = &commands[i];
- }
-
int rc = -1;
static constexpr size_t kTransferListHeaderLines = 4;
@@ -1728,36 +1707,35 @@ static Value* PerformBlockImageUpdate(const char* name, State* state,
size_t cmdindex = i - kTransferListHeaderLines;
params.tokens = android::base::Split(line, " ");
params.cpos = 0;
- params.cmdname = params.tokens[params.cpos++].c_str();
- params.cmdline = line.c_str();
+ params.cmdname = params.tokens[params.cpos++];
+ params.cmdline = line;
params.target_verified = false;
- if (cmd_map.find(params.cmdname) == cmd_map.end()) {
+ Command::Type cmd_type = Command::ParseType(params.cmdname);
+ if (cmd_type == Command::Type::LAST) {
LOG(ERROR) << "unexpected command [" << params.cmdname << "]";
goto pbiudone;
}
- const Command* cmd = cmd_map[params.cmdname];
+ const CommandFunction& performer = command_map.at(cmd_type);
// Skip the command if we explicitly set the corresponding function pointer to nullptr, e.g.
// "erase" during block_image_verify.
- if (cmd->f == nullptr) {
+ if (performer == nullptr) {
LOG(DEBUG) << "skip executing command [" << line << "]";
continue;
}
- std::string cmdname = std::string(params.cmdname);
-
// Skip all commands before the saved last command index when resuming an update, except for
// "new" command. Because new commands read in the data sequentially.
if (params.canwrite && skip_executed_command && cmdindex <= saved_last_command_index &&
- cmdname != "new") {
+ cmd_type != Command::Type::NEW) {
LOG(INFO) << "Skipping already executed command: " << cmdindex
<< ", last executed command for previous update: " << saved_last_command_index;
continue;
}
- if (cmd->f(params) == -1) {
+ if (performer(params) == -1) {
LOG(ERROR) << "failed to execute command [" << line << "]";
goto pbiudone;
}
@@ -1767,7 +1745,8 @@ static Value* PerformBlockImageUpdate(const char* name, State* state,
// that we will resume the update from the first command in the transfer list.
if (!params.canwrite && skip_executed_command && cmdindex <= saved_last_command_index) {
// TODO(xunchang) check that the cmdline of the saved index is correct.
- if ((cmdname == "move" || cmdname == "bsdiff" || cmdname == "imgdiff") &&
+ if ((cmd_type == Command::Type::MOVE || cmd_type == Command::Type::BSDIFF ||
+ cmd_type == Command::Type::IMGDIFF) &&
!params.target_verified) {
LOG(WARNING) << "Previously executed command " << saved_last_command_index << ": "
<< params.cmdline << " doesn't produce expected target blocks.";
@@ -1775,6 +1754,7 @@ static Value* PerformBlockImageUpdate(const char* name, State* state,
DeleteLastCommandFile();
}
}
+
if (params.canwrite) {
if (ota_fsync(params.fd) == -1) {
failure_type = kFsyncFailure;
@@ -1911,38 +1891,42 @@ pbiudone:
*/
Value* BlockImageVerifyFn(const char* name, State* state,
const std::vector<std::unique_ptr<Expr>>& argv) {
- // Commands which are not tested are set to nullptr to skip them completely
- const Command commands[] = {
- { "bsdiff", PerformCommandDiff },
- { "erase", nullptr },
- { "free", PerformCommandFree },
- { "imgdiff", PerformCommandDiff },
- { "move", PerformCommandMove },
- { "new", nullptr },
- { "stash", PerformCommandStash },
- { "zero", nullptr }
- };
-
- // Perform a dry run without writing to test if an update can proceed
- return PerformBlockImageUpdate(name, state, argv, commands,
- sizeof(commands) / sizeof(commands[0]), true);
+ // Commands which are not allowed are set to nullptr to skip them completely.
+ const CommandMap command_map{
+ // clang-format off
+ { Command::Type::BSDIFF, PerformCommandDiff },
+ { Command::Type::ERASE, nullptr },
+ { Command::Type::FREE, PerformCommandFree },
+ { Command::Type::IMGDIFF, PerformCommandDiff },
+ { Command::Type::MOVE, PerformCommandMove },
+ { Command::Type::NEW, nullptr },
+ { Command::Type::STASH, PerformCommandStash },
+ { Command::Type::ZERO, nullptr },
+ // clang-format on
+ };
+ CHECK_EQ(static_cast<size_t>(Command::Type::LAST), command_map.size());
+
+ // Perform a dry run without writing to test if an update can proceed.
+ return PerformBlockImageUpdate(name, state, argv, command_map, true);
}
Value* BlockImageUpdateFn(const char* name, State* state,
const std::vector<std::unique_ptr<Expr>>& argv) {
- const Command commands[] = {
- { "bsdiff", PerformCommandDiff },
- { "erase", PerformCommandErase },
- { "free", PerformCommandFree },
- { "imgdiff", PerformCommandDiff },
- { "move", PerformCommandMove },
- { "new", PerformCommandNew },
- { "stash", PerformCommandStash },
- { "zero", PerformCommandZero }
- };
-
- return PerformBlockImageUpdate(name, state, argv, commands,
- sizeof(commands) / sizeof(commands[0]), false);
+ const CommandMap command_map{
+ // clang-format off
+ { Command::Type::BSDIFF, PerformCommandDiff },
+ { Command::Type::ERASE, PerformCommandErase },
+ { Command::Type::FREE, PerformCommandFree },
+ { Command::Type::IMGDIFF, PerformCommandDiff },
+ { Command::Type::MOVE, PerformCommandMove },
+ { Command::Type::NEW, PerformCommandNew },
+ { Command::Type::STASH, PerformCommandStash },
+ { Command::Type::ZERO, PerformCommandZero },
+ // clang-format on
+ };
+ CHECK_EQ(static_cast<size_t>(Command::Type::LAST), command_map.size());
+
+ return PerformBlockImageUpdate(name, state, argv, command_map, false);
}
Value* RangeSha1Fn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
diff --git a/updater/commands.cpp b/updater/commands.cpp
new file mode 100644
index 000000000..f798c6a73
--- /dev/null
+++ b/updater/commands.cpp
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+#include "private/commands.h"
+
+#include <string>
+
+#include <android-base/logging.h>
+
+Command::Type Command::ParseType(const std::string& type_str) {
+ if (type_str == "zero") {
+ return Type::ZERO;
+ } else if (type_str == "new") {
+ return Type::NEW;
+ } else if (type_str == "erase") {
+ return Type::ERASE;
+ } else if (type_str == "move") {
+ return Type::MOVE;
+ } else if (type_str == "bsdiff") {
+ return Type::BSDIFF;
+ } else if (type_str == "imgdiff") {
+ return Type::IMGDIFF;
+ } else if (type_str == "stash") {
+ return Type::STASH;
+ } else if (type_str == "free") {
+ return Type::FREE;
+ }
+ LOG(ERROR) << "Invalid type: " << type_str;
+ return Type::LAST;
+};
diff --git a/updater/include/private/commands.h b/updater/include/private/commands.h
new file mode 100644
index 000000000..b36000072
--- /dev/null
+++ b/updater/include/private/commands.h
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <string>
+
+struct Command {
+ enum class Type {
+ ZERO,
+ NEW,
+ ERASE,
+ MOVE,
+ BSDIFF,
+ IMGDIFF,
+ STASH,
+ FREE,
+ LAST, // Not a valid type.
+ };
+
+ static Type ParseType(const std::string& type_str);
+};
diff --git a/updater_sample/Android.mk b/updater_sample/Android.mk
index 056ad66be..7662111b7 100644
--- a/updater_sample/Android.mk
+++ b/updater_sample/Android.mk
@@ -18,8 +18,8 @@ LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_PACKAGE_NAME := SystemUpdaterSample
-LOCAL_SDK_VERSION := system_current
LOCAL_MODULE_TAGS := samples
+LOCAL_SDK_VERSION := system_current
# TODO: enable proguard and use proguard.flags file
LOCAL_PROGUARD_ENABLED := disabled
diff --git a/updater_sample/README.md b/updater_sample/README.md
index 3f211ddba..f6c63a7b6 100644
--- a/updater_sample/README.md
+++ b/updater_sample/README.md
@@ -65,6 +65,32 @@ purpose only.
6. Push OTA packages to the device.
+## Sample App State vs UpdateEngine Status
+
+UpdateEngine provides status for different stages of update application
+process. But it lacks of proper status codes when update fails.
+
+This creates two problems:
+
+1. If sample app is unbound from update_engine (MainActivity is paused, destroyed),
+ app doesn't receive onStatusUpdate and onPayloadApplicationCompleted notifications.
+ If app binds to update_engine after update is completed,
+ only onStatusUpdate is called, but status becomes IDLE in most cases.
+ And there is no way to know if update was successful or not.
+
+2. This sample app demostrates suspend/resume using update_engins's
+ `cancel` and `applyPayload` (which picks up from where it left).
+ When `cancel` is called, status is set to `IDLE`, which doesn't allow
+ tracking suspended state properly.
+
+To solve these problems sample app implements its own separate update
+state - `UpdaterState`. To solve the first problem, sample app persists
+`UpdaterState` on a device. When app is resumed, it checks if `UpdaterState`
+matches the update_engine's status (as onStatusUpdate is guaranteed to be called).
+If they doesn't match, sample app calls `applyPayload` again with the same
+parameters, and handles update completion properly using `onPayloadApplicationCompleted`
+callback. The second problem is solved by adding `PAUSED` updater state.
+
## Sending HTTP headers from UpdateEngine
Sometimes OTA package server might require some HTTP headers to be present,
@@ -76,6 +102,44 @@ as of writing this sample app, these headers are `Authorization` and `User-Agent
which HTTP headers are supported.
+## Used update_engine APIs
+
+### UpdateEngine#bind
+
+Binds given callbacks to update_engine. When update_engine successfully
+initialized, it's guaranteed to invoke callback onStatusUpdate.
+
+### UpdateEngine#applyPayload
+
+Start an update attempt to download an apply the provided `payload_url` if
+no other update is running. The extra `key_value_pair_headers` will be
+included when fetching the payload.
+
+### UpdateEngine#cancel
+
+Cancel the ongoing update. The update could be running or suspended, but it
+can't be canceled after it was done.
+
+### UpdateEngine#resetStatus
+
+Reset the already applied update back to an idle state. This method can
+only be called when no update attempt is going on, and it will reset the
+status back to idle, deleting the currently applied update if any.
+
+### Callback: onStatusUpdate
+
+Called whenever the value of `status` or `progress` changes. For
+`progress` values changes, this method will be called only if it changes significantly.
+At this time of writing this doc, delta for `progress` is `0.005`.
+
+`onStatusUpdate` is always called when app binds to update_engine,
+except when update_engine fails to initialize.
+
+### Callback: onPayloadApplicationComplete
+
+Called whenever an update attempt is completed.
+
+
## Development
- [x] Create a UI with list of configs, current version,
@@ -90,6 +154,10 @@ which HTTP headers are supported.
- [x] Add demo for passing HTTP headers to `UpdateEngine#applyPayload`
- [x] [Package compatibility check](https://source.android.com/devices/architecture/vintf/match-rules)
- [x] Deferred switch slot demo
+- [x] Add UpdateManager; extract update logic from MainActivity
+- [x] Add Sample app update state (separate from update_engine status)
+- [-] Add smart update completion detection using onStatusUpdate
+- [ ] Add pause/resume demo
- [ ] Add demo for passing NETWORK_ID to `UpdateEngine#applyPayload`
- [ ] Verify system partition checksum for package
- [?] Add non-A/B updates demo
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);
+ }
+ }
+ }
+
}
diff --git a/updater_sample/src/com/example/android/systemupdatersample/UpdaterState.java b/updater_sample/src/com/example/android/systemupdatersample/UpdaterState.java
new file mode 100644
index 000000000..36a90982e
--- /dev/null
+++ b/updater_sample/src/com/example/android/systemupdatersample/UpdaterState.java
@@ -0,0 +1,103 @@
+/*
+ * 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;
+
+import android.util.SparseArray;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Controls updater state.
+ */
+public class UpdaterState {
+
+ public static final int IDLE = 0;
+ public static final int ERROR = 1;
+ public static final int RUNNING = 2;
+ public static final int PAUSED = 3;
+ public static final int SLOT_SWITCH_REQUIRED = 4;
+ public static final int REBOOT_REQUIRED = 5;
+
+ private static final SparseArray<String> STATE_MAP = new SparseArray<>();
+
+ static {
+ STATE_MAP.put(0, "IDLE");
+ STATE_MAP.put(1, "ERROR");
+ STATE_MAP.put(2, "RUNNING");
+ STATE_MAP.put(3, "PAUSED");
+ STATE_MAP.put(4, "SLOT_SWITCH_REQUIRED");
+ STATE_MAP.put(5, "REBOOT_REQUIRED");
+ }
+
+ /**
+ * Allowed state transitions. It's a map: key is a state, value is a set of states that
+ * are allowed to transition to from key.
+ */
+ private static final ImmutableMap<Integer, ImmutableSet<Integer>> TRANSITIONS =
+ ImmutableMap.of(
+ IDLE, ImmutableSet.of(RUNNING),
+ RUNNING, ImmutableSet.of(ERROR, PAUSED, REBOOT_REQUIRED, SLOT_SWITCH_REQUIRED),
+ PAUSED, ImmutableSet.of(RUNNING),
+ SLOT_SWITCH_REQUIRED, ImmutableSet.of(ERROR)
+ );
+
+ private AtomicInteger mState;
+
+ public UpdaterState(int state) {
+ this.mState = new AtomicInteger(state);
+ }
+
+ /**
+ * Returns updater state.
+ */
+ public int get() {
+ return mState.get();
+ }
+
+ /**
+ * Sets the updater state.
+ *
+ * @throws InvalidTransitionException if transition is not allowed.
+ */
+ public void set(int newState) throws InvalidTransitionException {
+ int oldState = mState.get();
+ if (!TRANSITIONS.get(oldState).contains(newState)) {
+ throw new InvalidTransitionException(
+ "Can't transition from " + oldState + " to " + newState);
+ }
+ mState.set(newState);
+ }
+
+ /**
+ * Converts status code to status name.
+ */
+ public static String getStateText(int state) {
+ return STATE_MAP.get(state);
+ }
+
+ /**
+ * Defines invalid state transition exception.
+ */
+ public static class InvalidTransitionException extends Exception {
+ public InvalidTransitionException(String msg) {
+ super(msg);
+ }
+ }
+}
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 9983fe316..1de72c2d6 100644
--- a/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java
+++ b/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java
@@ -33,11 +33,11 @@ import android.widget.TextView;
import com.example.android.systemupdatersample.R;
import com.example.android.systemupdatersample.UpdateConfig;
import com.example.android.systemupdatersample.UpdateManager;
+import com.example.android.systemupdatersample.UpdaterState;
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.UpdateEngineStatuses;
-import com.example.android.systemupdatersample.util.UpdaterStates;
import java.util.List;
@@ -108,12 +108,16 @@ public class MainActivity extends Activity {
@Override
protected void onResume() {
super.onResume();
+ // TODO(zhomart) load saved states
+ // Binding to UpdateEngine invokes onStatusUpdate callback,
+ // persisted updater state has to be loaded and prepared beforehand.
this.mUpdateManager.bind();
}
@Override
protected void onPause() {
this.mUpdateManager.unbind();
+ // TODO(zhomart) save state
super.onPause();
}
@@ -192,7 +196,7 @@ public class MainActivity extends Activity {
/**
* Invoked when SystemUpdaterSample app state changes.
* Value of {@code state} will be one of the
- * values from {@link UpdaterStates}.
+ * values from {@link UpdaterState}.
*/
private void onUpdaterStateChange(int state) {
Log.i(TAG, "onUpdaterStateChange invoked state=" + state);
@@ -233,8 +237,8 @@ public class MainActivity extends Activity {
runOnUiThread(() -> {
Log.i(TAG,
"Completed - errorCode="
- + UpdateEngineErrorCodes.getCodeName(errorCode) + "/" + errorCode
- + " " + completionState);
+ + UpdateEngineErrorCodes.getCodeName(errorCode) + "/" + errorCode
+ + " " + completionState);
setUiEngineErrorCode(errorCode);
if (errorCode == UpdateEngineErrorCodes.UPDATED_BUT_NOT_ACTIVE) {
// if update was successfully applied.
@@ -323,7 +327,7 @@ public class MainActivity extends Activity {
* @param state updater sample state
*/
private void setUiUpdaterState(int state) {
- String stateText = UpdaterStates.getStateText(state);
+ String stateText = UpdaterState.getStateText(state);
mTextViewUpdaterState.setText(stateText + "/" + state);
}
diff --git a/updater_sample/src/com/example/android/systemupdatersample/util/UpdaterStates.java b/updater_sample/src/com/example/android/systemupdatersample/util/UpdaterStates.java
deleted file mode 100644
index fc20a7941..000000000
--- a/updater_sample/src/com/example/android/systemupdatersample/util/UpdaterStates.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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;
-
-import android.util.SparseArray;
-
-/**
- * SystemUpdaterSample app state.
- */
-public class UpdaterStates {
-
- public static final int IDLE = 0;
- public static final int ERROR = 1;
- public static final int RUNNING = 2;
- public static final int PAUSED = 3;
- public static final int FINISHED = 4;
-
- private static final SparseArray<String> STATE_MAP = new SparseArray<>();
-
- static {
- STATE_MAP.put(0, "IDLE");
- STATE_MAP.put(1, "ERROR");
- STATE_MAP.put(2, "RUNNING");
- STATE_MAP.put(3, "PAUSED");
- STATE_MAP.put(4, "FINISHED");
- }
-
- /**
- * converts status code to status name
- */
- public static String getStateText(int state) {
- return STATE_MAP.get(state);
- }
-
- private UpdaterStates() {}
-}