summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--applypatch/freecache.cpp16
-rw-r--r--applypatch/imgpatch.cpp2
-rw-r--r--bootloader_message/bootloader_message.cpp19
-rw-r--r--minui/events.cpp2
-rw-r--r--recovery.cpp10
-rw-r--r--screen_ui.cpp22
-rw-r--r--screen_ui.h11
-rw-r--r--stub_ui.h7
-rw-r--r--tests/unit/screen_ui_test.cpp36
-rw-r--r--ui.h7
-rw-r--r--uncrypt/uncrypt.cpp358
-rw-r--r--updater_sample/AndroidManifest.xml2
-rw-r--r--updater_sample/README.md4
-rw-r--r--updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java79
-rw-r--r--updater_sample/src/com/example/android/systemupdatersample/services/PrepareUpdateService.java (renamed from updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java)106
-rw-r--r--updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java4
-rw-r--r--updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java2
-rw-r--r--updater_sample/tests/src/com/example/android/systemupdatersample/UpdateManagerTest.java75
18 files changed, 423 insertions, 339 deletions
diff --git a/applypatch/freecache.cpp b/applypatch/freecache.cpp
index e4878655e..3868ef230 100644
--- a/applypatch/freecache.cpp
+++ b/applypatch/freecache.cpp
@@ -141,8 +141,9 @@ static int64_t FreeSpaceForFile(const std::string& filename) {
return -1;
}
- int64_t free_space = static_cast<int64_t>(sf.f_bsize) * sf.f_bavail;
- if (sf.f_bsize == 0 || free_space / sf.f_bsize != sf.f_bavail) {
+ auto f_bsize = static_cast<int64_t>(sf.f_bsize);
+ auto free_space = sf.f_bsize * sf.f_bavail;
+ if (f_bsize == 0 || free_space / f_bsize != static_cast<int64_t>(sf.f_bavail)) {
LOG(ERROR) << "Invalid block size or overflow (sf.f_bsize " << sf.f_bsize << ", sf.f_bavail "
<< sf.f_bavail << ")";
return -1;
@@ -170,6 +171,13 @@ bool CheckAndFreeSpaceOnCache(size_t bytes) {
bool RemoveFilesInDirectory(size_t bytes_needed, const std::string& dirname,
const std::function<int64_t(const std::string&)>& space_checker) {
+ // The requested size cannot exceed max int64_t.
+ if (static_cast<uint64_t>(bytes_needed) >
+ static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) {
+ LOG(ERROR) << "Invalid arg of bytes_needed: " << bytes_needed;
+ return false;
+ }
+
struct stat st;
if (stat(dirname.c_str(), &st) == -1) {
PLOG(ERROR) << "Failed to stat " << dirname;
@@ -187,7 +195,7 @@ bool RemoveFilesInDirectory(size_t bytes_needed, const std::string& dirname,
}
LOG(INFO) << free_now << " bytes free on " << dirname << " (" << bytes_needed << " needed)";
- if (free_now >= bytes_needed) {
+ if (free_now >= static_cast<int64_t>(bytes_needed)) {
return true;
}
@@ -230,7 +238,7 @@ bool RemoveFilesInDirectory(size_t bytes_needed, const std::string& dirname,
return false;
}
LOG(INFO) << "Deleted " << file << "; now " << free_now << " bytes free";
- if (free_now >= bytes_needed) {
+ if (free_now >= static_cast<int64_t>(bytes_needed)) {
return true;
}
}
diff --git a/applypatch/imgpatch.cpp b/applypatch/imgpatch.cpp
index e6be39a2f..f4c33e5a3 100644
--- a/applypatch/imgpatch.cpp
+++ b/applypatch/imgpatch.cpp
@@ -54,7 +54,7 @@ static bool ApplyBSDiffPatchAndStreamOutput(const uint8_t* src_data, size_t src_
const Value& patch, size_t patch_offset,
const char* deflate_header, SinkFn sink) {
size_t expected_target_length = static_cast<size_t>(Read8(deflate_header + 32));
- CHECK_GT(expected_target_length, 0);
+ CHECK_GT(expected_target_length, static_cast<size_t>(0));
int level = Read4(deflate_header + 40);
int method = Read4(deflate_header + 44);
int window_bits = Read4(deflate_header + 48);
diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp
index aaeffdc5c..b933cbf8e 100644
--- a/bootloader_message/bootloader_message.cpp
+++ b/bootloader_message/bootloader_message.cpp
@@ -27,21 +27,22 @@
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
-#include <fs_mgr.h>
+#include <fstab/fstab.h>
static std::string get_misc_blk_device(std::string* err) {
- std::unique_ptr<fstab, decltype(&fs_mgr_free_fstab)> fstab(fs_mgr_read_fstab_default(),
- fs_mgr_free_fstab);
- if (!fstab) {
+ Fstab fstab;
+ if (!ReadDefaultFstab(&fstab)) {
*err = "failed to read default fstab";
return "";
}
- fstab_rec* record = fs_mgr_get_entry_for_mount_point(fstab.get(), "/misc");
- if (record == nullptr) {
- *err = "failed to find /misc partition";
- return "";
+ for (const auto& entry : fstab) {
+ if (entry.mount_point == "/misc") {
+ return entry.blk_device;
+ }
}
- return record->blk_device;
+
+ *err = "failed to find /misc partition";
+ return "";
}
// In recovery mode, recovery can get started and try to access the misc
diff --git a/minui/events.cpp b/minui/events.cpp
index 2894c3b6b..d94e97723 100644
--- a/minui/events.cpp
+++ b/minui/events.cpp
@@ -55,7 +55,7 @@ static bool test_bit(size_t bit, unsigned long* array) { // NOLINT
}
int ev_init(ev_callback input_cb, bool allow_touch_inputs) {
- g_epoll_fd = epoll_create(MAX_DEVICES + MAX_MISC_FDS);
+ g_epoll_fd = epoll_create1(EPOLL_CLOEXEC);
if (g_epoll_fd == -1) {
return -1;
}
diff --git a/recovery.cpp b/recovery.cpp
index 7e1fa43a6..de916c633 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -369,7 +369,14 @@ static bool yes_no(Device* device, const char* question1, const char* question2)
}
static bool ask_to_wipe_data(Device* device) {
- return yes_no(device, "Wipe all user data?", " THIS CAN NOT BE UNDONE!");
+ std::vector<std::string> headers{ "Wipe all user data?", " THIS CAN NOT BE UNDONE!" };
+ std::vector<std::string> items{ " Cancel", " Factory data reset" };
+
+ size_t chosen_item = ui->ShowPromptWipeDataConfirmationMenu(
+ headers, items,
+ std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
+
+ return (chosen_item == 1);
}
// Return true on success.
@@ -420,7 +427,6 @@ static InstallResult prompt_and_wipe_data(Device* device) {
return INSTALL_SUCCESS; // Just reboot, no wipe; not a failure, user asked for it
}
- // TODO(xunchang) localize the confirmation texts also.
if (ask_to_wipe_data(device)) {
if (wipe_data(device)) {
return INSTALL_SUCCESS;
diff --git a/screen_ui.cpp b/screen_ui.cpp
index 765d2fe60..575605452 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -844,9 +844,13 @@ bool ScreenRecoveryUI::InitTextParams() {
return true;
}
-// TODO(xunchang) load localized text icons for the menu. (Init for screenRecoveryUI but
-// not wearRecoveryUI).
bool ScreenRecoveryUI::LoadWipeDataMenuText() {
+ // Ignores the errors since the member variables will stay as nullptr.
+ cancel_wipe_data_text_ = LoadLocalizedBitmap("cancel_wipe_data_text");
+ factory_data_reset_text_ = LoadLocalizedBitmap("factory_data_reset_text");
+ try_again_text_ = LoadLocalizedBitmap("try_again_text");
+ wipe_data_confirmation_text_ = LoadLocalizedBitmap("wipe_data_confirmation_text");
+ wipe_data_menu_header_text_ = LoadLocalizedBitmap("wipe_data_menu_header_text");
return true;
}
@@ -1250,6 +1254,20 @@ size_t ScreenRecoveryUI::ShowPromptWipeDataMenu(const std::vector<std::string>&
return ShowMenu(std::move(wipe_data_menu), true, key_handler);
}
+size_t ScreenRecoveryUI::ShowPromptWipeDataConfirmationMenu(
+ const std::vector<std::string>& backup_headers, const std::vector<std::string>& backup_items,
+ const std::function<int(int, bool)>& key_handler) {
+ auto confirmation_menu =
+ CreateMenu(wipe_data_confirmation_text_.get(),
+ { cancel_wipe_data_text_.get(), factory_data_reset_text_.get() }, backup_headers,
+ backup_items, 0);
+ if (confirmation_menu == nullptr) {
+ return 0;
+ }
+
+ return ShowMenu(std::move(confirmation_menu), true, key_handler);
+}
+
bool ScreenRecoveryUI::IsTextVisible() {
std::lock_guard<std::mutex> lg(updateMutex);
int visible = show_text;
diff --git a/screen_ui.h b/screen_ui.h
index ff245a2fb..acd44c819 100644
--- a/screen_ui.h
+++ b/screen_ui.h
@@ -240,6 +240,11 @@ class ScreenRecoveryUI : public RecoveryUI, public DrawInterface {
const std::vector<std::string>& backup_items,
const std::function<int(int, bool)>& key_handler) override;
+ // Displays the localized wipe data confirmation menu.
+ size_t ShowPromptWipeDataConfirmationMenu(
+ const std::vector<std::string>& backup_headers, const std::vector<std::string>& backup_items,
+ const std::function<int(int, bool)>& key_handler) override;
+
protected:
static constexpr int kMenuIndent = 4;
@@ -334,9 +339,11 @@ class ScreenRecoveryUI : public RecoveryUI, public DrawInterface {
std::unique_ptr<GRSurface> no_command_text_;
// Localized text images for the wipe data menu.
- std::unique_ptr<GRSurface> wipe_data_menu_header_text_;
- std::unique_ptr<GRSurface> try_again_text_;
+ std::unique_ptr<GRSurface> cancel_wipe_data_text_;
std::unique_ptr<GRSurface> factory_data_reset_text_;
+ std::unique_ptr<GRSurface> try_again_text_;
+ std::unique_ptr<GRSurface> wipe_data_confirmation_text_;
+ std::unique_ptr<GRSurface> wipe_data_menu_header_text_;
// current_icon_ points to one of the frames in intro_frames_ or loop_frames_, indexed by
// current_frame_, or error_icon_.
diff --git a/stub_ui.h b/stub_ui.h
index ca137dff3..fb1d8c7a6 100644
--- a/stub_ui.h
+++ b/stub_ui.h
@@ -74,6 +74,13 @@ class StubRecoveryUI : public RecoveryUI {
return 0;
}
+ size_t ShowPromptWipeDataConfirmationMenu(
+ const std::vector<std::string>& /* backup_headers */,
+ const std::vector<std::string>& /* backup_items */,
+ const std::function<int(int, bool)>& /* key_handle */) override {
+ return 0;
+ }
+
void SetTitle(const std::vector<std::string>& /* lines */) override {}
};
diff --git a/tests/unit/screen_ui_test.cpp b/tests/unit/screen_ui_test.cpp
index 61a092551..647c7b2d3 100644
--- a/tests/unit/screen_ui_test.cpp
+++ b/tests/unit/screen_ui_test.cpp
@@ -308,11 +308,11 @@ class TestableScreenRecoveryUI : public ScreenRecoveryUI {
int KeyHandler(int key, bool visible) const;
private:
- FRIEND_TEST(ScreenRecoveryUITest, Init);
- FRIEND_TEST(ScreenRecoveryUITest, RtlLocale);
- FRIEND_TEST(ScreenRecoveryUITest, RtlLocaleWithSuffix);
- FRIEND_TEST(ScreenRecoveryUITest, LoadAnimation);
- FRIEND_TEST(ScreenRecoveryUITest, LoadAnimation_MissingAnimation);
+ FRIEND_TEST(DISABLED_ScreenRecoveryUITest, Init);
+ FRIEND_TEST(DISABLED_ScreenRecoveryUITest, RtlLocale);
+ FRIEND_TEST(DISABLED_ScreenRecoveryUITest, RtlLocaleWithSuffix);
+ FRIEND_TEST(DISABLED_ScreenRecoveryUITest, LoadAnimation);
+ FRIEND_TEST(DISABLED_ScreenRecoveryUITest, LoadAnimation_MissingAnimation);
std::vector<KeyCode> key_buffer_;
size_t key_buffer_index_;
@@ -340,7 +340,7 @@ int TestableScreenRecoveryUI::WaitKey() {
return static_cast<int>(key_buffer_[key_buffer_index_++]);
}
-class ScreenRecoveryUITest : public ::testing::Test {
+class DISABLED_ScreenRecoveryUITest : public ::testing::Test {
protected:
const std::string kTestLocale = "en-US";
const std::string kTestRtlLocale = "ar";
@@ -372,7 +372,7 @@ class ScreenRecoveryUITest : public ::testing::Test {
} \
} while (false)
-TEST_F(ScreenRecoveryUITest, Init) {
+TEST_F(DISABLED_ScreenRecoveryUITest, Init) {
RETURN_IF_NO_GRAPHICS;
ASSERT_TRUE(ui_->Init(kTestLocale));
@@ -382,12 +382,12 @@ TEST_F(ScreenRecoveryUITest, Init) {
ASSERT_FALSE(ui_->WasTextEverVisible());
}
-TEST_F(ScreenRecoveryUITest, dtor_NotCallingInit) {
+TEST_F(DISABLED_ScreenRecoveryUITest, dtor_NotCallingInit) {
ui_.reset();
ASSERT_FALSE(ui_);
}
-TEST_F(ScreenRecoveryUITest, ShowText) {
+TEST_F(DISABLED_ScreenRecoveryUITest, ShowText) {
RETURN_IF_NO_GRAPHICS;
ASSERT_TRUE(ui_->Init(kTestLocale));
@@ -401,21 +401,21 @@ TEST_F(ScreenRecoveryUITest, ShowText) {
ASSERT_TRUE(ui_->WasTextEverVisible());
}
-TEST_F(ScreenRecoveryUITest, RtlLocale) {
+TEST_F(DISABLED_ScreenRecoveryUITest, RtlLocale) {
RETURN_IF_NO_GRAPHICS;
ASSERT_TRUE(ui_->Init(kTestRtlLocale));
ASSERT_TRUE(ui_->rtl_locale_);
}
-TEST_F(ScreenRecoveryUITest, RtlLocaleWithSuffix) {
+TEST_F(DISABLED_ScreenRecoveryUITest, RtlLocaleWithSuffix) {
RETURN_IF_NO_GRAPHICS;
ASSERT_TRUE(ui_->Init(kTestRtlLocaleWithSuffix));
ASSERT_TRUE(ui_->rtl_locale_);
}
-TEST_F(ScreenRecoveryUITest, ShowMenu) {
+TEST_F(DISABLED_ScreenRecoveryUITest, ShowMenu) {
RETURN_IF_NO_GRAPHICS;
ASSERT_TRUE(ui_->Init(kTestLocale));
@@ -443,7 +443,7 @@ TEST_F(ScreenRecoveryUITest, ShowMenu) {
std::placeholders::_1, std::placeholders::_2)));
}
-TEST_F(ScreenRecoveryUITest, ShowMenu_NotMenuOnly) {
+TEST_F(DISABLED_ScreenRecoveryUITest, ShowMenu_NotMenuOnly) {
RETURN_IF_NO_GRAPHICS;
ASSERT_TRUE(ui_->Init(kTestLocale));
@@ -456,7 +456,7 @@ TEST_F(ScreenRecoveryUITest, ShowMenu_NotMenuOnly) {
std::placeholders::_1, std::placeholders::_2)));
}
-TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut) {
+TEST_F(DISABLED_ScreenRecoveryUITest, ShowMenu_TimedOut) {
RETURN_IF_NO_GRAPHICS;
ASSERT_TRUE(ui_->Init(kTestLocale));
@@ -467,7 +467,7 @@ TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut) {
ui_->ShowMenu(HEADERS, ITEMS, 3, true, nullptr));
}
-TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut_TextWasEverVisible) {
+TEST_F(DISABLED_ScreenRecoveryUITest, ShowMenu_TimedOut_TextWasEverVisible) {
RETURN_IF_NO_GRAPHICS;
ASSERT_TRUE(ui_->Init(kTestLocale));
@@ -485,7 +485,7 @@ TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut_TextWasEverVisible) {
std::placeholders::_1, std::placeholders::_2)));
}
-TEST_F(ScreenRecoveryUITest, ShowMenuWithInterrupt) {
+TEST_F(DISABLED_ScreenRecoveryUITest, ShowMenuWithInterrupt) {
RETURN_IF_NO_GRAPHICS;
ASSERT_TRUE(ui_->Init(kTestLocale));
@@ -517,7 +517,7 @@ TEST_F(ScreenRecoveryUITest, ShowMenuWithInterrupt) {
std::placeholders::_1, std::placeholders::_2)));
}
-TEST_F(ScreenRecoveryUITest, LoadAnimation) {
+TEST_F(DISABLED_ScreenRecoveryUITest, LoadAnimation) {
RETURN_IF_NO_GRAPHICS;
ASSERT_TRUE(ui_->Init(kTestLocale));
@@ -547,7 +547,7 @@ TEST_F(ScreenRecoveryUITest, LoadAnimation) {
}
}
-TEST_F(ScreenRecoveryUITest, LoadAnimation_MissingAnimation) {
+TEST_F(DISABLED_ScreenRecoveryUITest, LoadAnimation_MissingAnimation) {
RETURN_IF_NO_GRAPHICS;
ASSERT_TRUE(ui_->Init(kTestLocale));
diff --git a/ui.h b/ui.h
index 1e6186a11..4924fec52 100644
--- a/ui.h
+++ b/ui.h
@@ -169,6 +169,13 @@ class RecoveryUI {
const std::vector<std::string>& backup_items,
const std::function<int(int, bool)>& key_handler) = 0;
+ // Displays the localized wipe data confirmation menu with pre-generated images. Falls back to
+ // the text strings upon failures. The initial selection is the 0th item, which returns to the
+ // upper level menu.
+ virtual size_t ShowPromptWipeDataConfirmationMenu(
+ const std::vector<std::string>& backup_headers, const std::vector<std::string>& backup_items,
+ const std::function<int(int, bool)>& key_handler) = 0;
+
// Resets the key interrupt status.
void ResetKeyInterruptStatus() {
key_interrupted_ = false;
diff --git a/uncrypt/uncrypt.cpp b/uncrypt/uncrypt.cpp
index d1970e534..75595ac2c 100644
--- a/uncrypt/uncrypt.cpp
+++ b/uncrypt/uncrypt.cpp
@@ -89,7 +89,6 @@
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
-#include <libgen.h>
#include <linux/fs.h>
#include <stdarg.h>
#include <stdio.h>
@@ -103,6 +102,7 @@
#include <algorithm>
#include <memory>
+#include <string>
#include <vector>
#include <android-base/file.h>
@@ -115,6 +115,7 @@
#include <cutils/android_reboot.h>
#include <cutils/sockets.h>
#include <fs_mgr.h>
+#include <fstab/fstab.h>
#include "otautil/error_code.h"
@@ -136,7 +137,7 @@ static const std::string UNCRYPT_PATH_FILE = "/cache/recovery/uncrypt_file";
static const std::string UNCRYPT_STATUS = "/cache/recovery/uncrypt_status";
static const std::string UNCRYPT_SOCKET = "uncrypt";
-static struct fstab* fstab = nullptr;
+static Fstab fstab;
static int write_at_offset(unsigned char* buffer, size_t size, int wfd, off64_t offset) {
if (TEMP_FAILURE_RETRY(lseek64(wfd, offset, SEEK_SET)) == -1) {
@@ -162,49 +163,34 @@ static void add_block_to_ranges(std::vector<int>& ranges, int new_block) {
}
}
-static struct fstab* read_fstab() {
- fstab = fs_mgr_read_fstab_default();
- if (!fstab) {
- LOG(ERROR) << "failed to read default fstab";
- return NULL;
- }
-
- return fstab;
-}
-
-static const char* find_block_device(const char* path, bool* encryptable,
- bool* encrypted, bool* f2fs_fs) {
- // Look for a volume whose mount point is the prefix of path and
- // return its block device. Set encrypted if it's currently
- // encrypted.
-
- // ensure f2fs_fs is set to false first.
- *f2fs_fs = false;
-
- for (int i = 0; i < fstab->num_entries; ++i) {
- struct fstab_rec* v = &fstab->recs[i];
- if (!v->mount_point) {
- continue;
- }
- int len = strlen(v->mount_point);
- if (strncmp(path, v->mount_point, len) == 0 &&
- (path[len] == '/' || path[len] == 0)) {
- *encrypted = false;
- *encryptable = false;
- if (fs_mgr_is_encryptable(v) || fs_mgr_is_file_encrypted(v)) {
- *encryptable = true;
- if (android::base::GetProperty("ro.crypto.state", "") == "encrypted") {
- *encrypted = true;
- }
- }
- if (strcmp(v->fs_type, "f2fs") == 0) {
- *f2fs_fs = true;
- }
- return v->blk_device;
+// Looks for a volume whose mount point is the prefix of path and returns its block device or an
+// empty string. Sets encryption flags accordingly.
+static std::string FindBlockDevice(const std::string& path, bool* encryptable, bool* encrypted,
+ bool* f2fs_fs) {
+ // Ensure f2fs_fs is set to false first.
+ *f2fs_fs = false;
+
+ for (const auto& entry : fstab) {
+ if (entry.mount_point.empty()) {
+ continue;
+ }
+ if (android::base::StartsWith(path, entry.mount_point + "/")) {
+ *encrypted = false;
+ *encryptable = false;
+ if (entry.is_encryptable() || entry.fs_mgr_flags.file_encryption) {
+ *encryptable = true;
+ if (android::base::GetProperty("ro.crypto.state", "") == "encrypted") {
+ *encrypted = true;
}
+ }
+ if (entry.fs_type == "f2fs") {
+ *f2fs_fs = true;
+ }
+ return entry.blk_device;
}
+ }
- return NULL;
+ return "";
}
static bool write_status_to_socket(int status, int socket) {
@@ -217,103 +203,102 @@ static bool write_status_to_socket(int status, int socket) {
return android::base::WriteFully(socket, &status_out, sizeof(int));
}
-// Parse uncrypt_file to find the update package name.
-static bool find_uncrypt_package(const std::string& uncrypt_path_file, std::string* package_name) {
- CHECK(package_name != nullptr);
- std::string uncrypt_path;
- if (!android::base::ReadFileToString(uncrypt_path_file, &uncrypt_path)) {
- PLOG(ERROR) << "failed to open \"" << uncrypt_path_file << "\"";
- return false;
- }
-
- // Remove the trailing '\n' if present.
- *package_name = android::base::Trim(uncrypt_path);
- return true;
+// Parses the given path file to find the update package name.
+static bool FindUncryptPackage(const std::string& uncrypt_path_file, std::string* package_name) {
+ CHECK(package_name != nullptr);
+ std::string uncrypt_path;
+ if (!android::base::ReadFileToString(uncrypt_path_file, &uncrypt_path)) {
+ PLOG(ERROR) << "failed to open \"" << uncrypt_path_file << "\"";
+ return false;
+ }
+
+ // Remove the trailing '\n' if present.
+ *package_name = android::base::Trim(uncrypt_path);
+ return true;
}
-static int retry_fibmap(const int fd, const char* name, int* block, const int head_block) {
- CHECK(block != nullptr);
- for (size_t i = 0; i < FIBMAP_RETRY_LIMIT; i++) {
- if (fsync(fd) == -1) {
- PLOG(ERROR) << "failed to fsync \"" << name << "\"";
- return kUncryptFileSyncError;
- }
- if (ioctl(fd, FIBMAP, block) != 0) {
- PLOG(ERROR) << "failed to find block " << head_block;
- return kUncryptIoctlError;
- }
- if (*block != 0) {
- return kUncryptNoError;
- }
- sleep(1);
- }
- LOG(ERROR) << "fibmap of " << head_block << "always returns 0";
- return kUncryptIoctlError;
-}
-
-static int produce_block_map(const char* path, const char* map_file, const char* blk_dev,
- bool encrypted, bool f2fs_fs, int socket) {
- std::string err;
- if (!android::base::RemoveFileIfExists(map_file, &err)) {
- LOG(ERROR) << "failed to remove the existing map file " << map_file << ": " << err;
- return kUncryptFileRemoveError;
- }
- std::string tmp_map_file = std::string(map_file) + ".tmp";
- android::base::unique_fd mapfd(open(tmp_map_file.c_str(),
- O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR));
- if (mapfd == -1) {
- PLOG(ERROR) << "failed to open " << tmp_map_file;
- return kUncryptFileOpenError;
- }
-
- // Make sure we can write to the socket.
- if (!write_status_to_socket(0, socket)) {
- LOG(ERROR) << "failed to write to socket " << socket;
- return kUncryptSocketWriteError;
- }
-
- struct stat sb;
- if (stat(path, &sb) != 0) {
- LOG(ERROR) << "failed to stat " << path;
- return kUncryptFileStatError;
+static int RetryFibmap(int fd, const std::string& name, int* block, const int head_block) {
+ CHECK(block != nullptr);
+ for (size_t i = 0; i < FIBMAP_RETRY_LIMIT; i++) {
+ if (fsync(fd) == -1) {
+ PLOG(ERROR) << "failed to fsync \"" << name << "\"";
+ return kUncryptFileSyncError;
}
-
- LOG(INFO) << " block size: " << sb.st_blksize << " bytes";
-
- int blocks = ((sb.st_size-1) / sb.st_blksize) + 1;
- LOG(INFO) << " file size: " << sb.st_size << " bytes, " << blocks << " blocks";
-
- std::vector<int> ranges;
-
- std::string s = android::base::StringPrintf("%s\n%" PRId64 " %" PRId64 "\n",
- blk_dev, static_cast<int64_t>(sb.st_size),
- static_cast<int64_t>(sb.st_blksize));
- if (!android::base::WriteStringToFd(s, mapfd)) {
- PLOG(ERROR) << "failed to write " << tmp_map_file;
- return kUncryptWriteError;
+ if (ioctl(fd, FIBMAP, block) != 0) {
+ PLOG(ERROR) << "failed to find block " << head_block;
+ return kUncryptIoctlError;
}
-
- std::vector<std::vector<unsigned char>> buffers;
- if (encrypted) {
- buffers.resize(WINDOW_SIZE, std::vector<unsigned char>(sb.st_blksize));
+ if (*block != 0) {
+ return kUncryptNoError;
}
- int head_block = 0;
- int head = 0, tail = 0;
+ sleep(1);
+ }
+ LOG(ERROR) << "fibmap of " << head_block << " always returns 0";
+ return kUncryptIoctlError;
+}
- android::base::unique_fd fd(open(path, O_RDWR));
- if (fd == -1) {
- PLOG(ERROR) << "failed to open " << path << " for reading";
- return kUncryptFileOpenError;
- }
-
- android::base::unique_fd wfd;
- if (encrypted) {
- wfd.reset(open(blk_dev, O_WRONLY));
- if (wfd == -1) {
- PLOG(ERROR) << "failed to open " << blk_dev << " for writing";
- return kUncryptBlockOpenError;
- }
- }
+static int ProductBlockMap(const std::string& path, const std::string& map_file,
+ const std::string& blk_dev, bool encrypted, bool f2fs_fs, int socket) {
+ std::string err;
+ if (!android::base::RemoveFileIfExists(map_file, &err)) {
+ LOG(ERROR) << "failed to remove the existing map file " << map_file << ": " << err;
+ return kUncryptFileRemoveError;
+ }
+ std::string tmp_map_file = map_file + ".tmp";
+ android::base::unique_fd mapfd(open(tmp_map_file.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR));
+ if (mapfd == -1) {
+ PLOG(ERROR) << "failed to open " << tmp_map_file;
+ return kUncryptFileOpenError;
+ }
+
+ // Make sure we can write to the socket.
+ if (!write_status_to_socket(0, socket)) {
+ LOG(ERROR) << "failed to write to socket " << socket;
+ return kUncryptSocketWriteError;
+ }
+
+ struct stat sb;
+ if (stat(path.c_str(), &sb) != 0) {
+ PLOG(ERROR) << "failed to stat " << path;
+ return kUncryptFileStatError;
+ }
+
+ LOG(INFO) << " block size: " << sb.st_blksize << " bytes";
+
+ int blocks = ((sb.st_size - 1) / sb.st_blksize) + 1;
+ LOG(INFO) << " file size: " << sb.st_size << " bytes, " << blocks << " blocks";
+
+ std::vector<int> ranges;
+
+ std::string s = android::base::StringPrintf("%s\n%" PRId64 " %" PRId64 "\n", blk_dev.c_str(),
+ static_cast<int64_t>(sb.st_size),
+ static_cast<int64_t>(sb.st_blksize));
+ if (!android::base::WriteStringToFd(s, mapfd)) {
+ PLOG(ERROR) << "failed to write " << tmp_map_file;
+ return kUncryptWriteError;
+ }
+
+ std::vector<std::vector<unsigned char>> buffers;
+ if (encrypted) {
+ buffers.resize(WINDOW_SIZE, std::vector<unsigned char>(sb.st_blksize));
+ }
+ int head_block = 0;
+ int head = 0, tail = 0;
+
+ android::base::unique_fd fd(open(path.c_str(), O_RDWR));
+ if (fd == -1) {
+ PLOG(ERROR) << "failed to open " << path << " for reading";
+ return kUncryptFileOpenError;
+ }
+
+ android::base::unique_fd wfd;
+ if (encrypted) {
+ wfd.reset(open(blk_dev.c_str(), O_WRONLY));
+ if (wfd == -1) {
+ PLOG(ERROR) << "failed to open " << blk_dev << " for writing";
+ return kUncryptBlockOpenError;
+ }
+ }
// F2FS-specific ioctl
// It requires the below kernel commit merged in v4.16-rc1.
@@ -361,7 +346,7 @@ static int produce_block_map(const char* path, const char* map_file, const char*
if (block == 0) {
LOG(ERROR) << "failed to find block " << head_block << ", retrying";
- int error = retry_fibmap(fd, path, &block, head_block);
+ int error = RetryFibmap(fd, path, &block, head_block);
if (error != kUncryptNoError) {
return error;
}
@@ -406,7 +391,7 @@ static int produce_block_map(const char* path, const char* map_file, const char*
if (block == 0) {
LOG(ERROR) << "failed to find block " << head_block << ", retrying";
- int error = retry_fibmap(fd, path, &block, head_block);
+ int error = RetryFibmap(fd, path, &block, head_block);
if (error != kUncryptNoError) {
return error;
}
@@ -456,13 +441,12 @@ static int produce_block_map(const char* path, const char* map_file, const char*
}
}
- if (rename(tmp_map_file.c_str(), map_file) == -1) {
- PLOG(ERROR) << "failed to rename " << tmp_map_file << " to " << map_file;
- return kUncryptFileRenameError;
+ if (rename(tmp_map_file.c_str(), map_file.c_str()) == -1) {
+ PLOG(ERROR) << "failed to rename " << tmp_map_file << " to " << map_file;
+ return kUncryptFileRenameError;
}
// Sync dir to make rename() result written to disk.
- std::string file_name = map_file;
- std::string dir_name = dirname(&file_name[0]);
+ std::string dir_name = android::base::Dirname(map_file);
android::base::unique_fd dfd(open(dir_name.c_str(), O_RDONLY | O_DIRECTORY));
if (dfd == -1) {
PLOG(ERROR) << "failed to open dir " << dir_name;
@@ -479,45 +463,42 @@ static int produce_block_map(const char* path, const char* map_file, const char*
return 0;
}
-static int uncrypt(const char* input_path, const char* map_file, const int socket) {
- LOG(INFO) << "update package is \"" << input_path << "\"";
-
- // Turn the name of the file we're supposed to convert into an absolute path, so we can find
- // what filesystem it's on.
- char path[PATH_MAX+1];
- if (realpath(input_path, path) == nullptr) {
- PLOG(ERROR) << "failed to convert \"" << input_path << "\" to absolute path";
- return kUncryptRealpathFindError;
- }
-
- bool encryptable;
- bool encrypted;
- bool f2fs_fs;
- const char* blk_dev = find_block_device(path, &encryptable, &encrypted, &f2fs_fs);
- if (blk_dev == nullptr) {
- LOG(ERROR) << "failed to find block device for " << path;
- return kUncryptBlockDeviceFindError;
- }
-
- // If the filesystem it's on isn't encrypted, we only produce the
- // block map, we don't rewrite the file contents (it would be
- // pointless to do so).
- LOG(INFO) << "encryptable: " << (encryptable ? "yes" : "no");
- LOG(INFO) << " encrypted: " << (encrypted ? "yes" : "no");
-
- // Recovery supports installing packages from 3 paths: /cache,
- // /data, and /sdcard. (On a particular device, other locations
- // may work, but those are three we actually expect.)
- //
- // On /data we want to convert the file to a block map so that we
- // can read the package without mounting the partition. On /cache
- // and /sdcard we leave the file alone.
- if (strncmp(path, "/data/", 6) == 0) {
- LOG(INFO) << "writing block map " << map_file;
- return produce_block_map(path, map_file, blk_dev, encrypted, f2fs_fs, socket);
- }
-
- return 0;
+static int Uncrypt(const std::string& input_path, const std::string& map_file, int socket) {
+ LOG(INFO) << "update package is \"" << input_path << "\"";
+
+ // Turn the name of the file we're supposed to convert into an absolute path, so we can find what
+ // filesystem it's on.
+ std::string path;
+ if (!android::base::Realpath(input_path, &path)) {
+ PLOG(ERROR) << "Failed to convert \"" << input_path << "\" to absolute path";
+ return kUncryptRealpathFindError;
+ }
+
+ bool encryptable;
+ bool encrypted;
+ bool f2fs_fs;
+ const std::string blk_dev = FindBlockDevice(path, &encryptable, &encrypted, &f2fs_fs);
+ if (blk_dev.empty()) {
+ LOG(ERROR) << "Failed to find block device for " << path;
+ return kUncryptBlockDeviceFindError;
+ }
+
+ // If the filesystem it's on isn't encrypted, we only produce the block map, we don't rewrite the
+ // file contents (it would be pointless to do so).
+ LOG(INFO) << "encryptable: " << (encryptable ? "yes" : "no");
+ LOG(INFO) << " encrypted: " << (encrypted ? "yes" : "no");
+
+ // Recovery supports installing packages from 3 paths: /cache, /data, and /sdcard. (On a
+ // particular device, other locations may work, but those are three we actually expect.)
+ //
+ // On /data we want to convert the file to a block map so that we can read the package without
+ // mounting the partition. On /cache and /sdcard we leave the file alone.
+ if (android::base::StartsWith(path, "/data/")) {
+ LOG(INFO) << "writing block map " << map_file;
+ return ProductBlockMap(path, map_file, blk_dev, encrypted, f2fs_fs, socket);
+ }
+
+ return 0;
}
static void log_uncrypt_error_code(UncryptErrorCode error_code) {
@@ -533,18 +514,18 @@ static bool uncrypt_wrapper(const char* input_path, const char* map_file, const
std::string package;
if (input_path == nullptr) {
- if (!find_uncrypt_package(UNCRYPT_PATH_FILE, &package)) {
- write_status_to_socket(-1, socket);
- // Overwrite the error message.
- log_uncrypt_error_code(kUncryptPackageMissingError);
- return false;
- }
- input_path = package.c_str();
+ if (!FindUncryptPackage(UNCRYPT_PATH_FILE, &package)) {
+ write_status_to_socket(-1, socket);
+ // Overwrite the error message.
+ log_uncrypt_error_code(kUncryptPackageMissingError);
+ return false;
+ }
+ input_path = package.c_str();
}
CHECK(map_file != nullptr);
auto start = std::chrono::system_clock::now();
- int status = uncrypt(input_path, map_file, socket);
+ int status = Uncrypt(input_path, map_file, socket);
std::chrono::duration<double> duration = std::chrono::system_clock::now() - start;
int count = static_cast<int>(duration.count());
@@ -654,7 +635,8 @@ int main(int argc, char** argv) {
return 2;
}
- if ((fstab = read_fstab()) == nullptr) {
+ if (!ReadDefaultFstab(&fstab)) {
+ LOG(ERROR) << "failed to read default fstab";
log_uncrypt_error_code(kUncryptFstabReadError);
return 1;
}
diff --git a/updater_sample/AndroidManifest.xml b/updater_sample/AndroidManifest.xml
index 18d8425e1..0a2511617 100644
--- a/updater_sample/AndroidManifest.xml
+++ b/updater_sample/AndroidManifest.xml
@@ -33,7 +33,7 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- <service android:name=".services.PrepareStreamingService"/>
+ <service android:name=".services.PrepareUpdateService"/>
</application>
</manifest>
diff --git a/updater_sample/README.md b/updater_sample/README.md
index f9c3fb8ec..bc66a9bb4 100644
--- a/updater_sample/README.md
+++ b/updater_sample/README.md
@@ -235,8 +235,8 @@ privileged system app, so it's granted the required permissions to access
5. Run a test file
```
adb shell am instrument \
- -w com.example.android.systemupdatersample.tests/android.support.test.runner.AndroidJUnitRunner \
- -c com.example.android.systemupdatersample.util.PayloadSpecsTest
+ -w -e class com.example.android.systemupdatersample.UpdateManagerTest#applyUpdate_appliesPayloadToUpdateEngine \
+ com.example.android.systemupdatersample.tests/android.support.test.runner.AndroidJUnitRunner
```
diff --git a/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java b/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java
index 12a8f3f5f..c02e60846 100644
--- a/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java
+++ b/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java
@@ -17,19 +17,18 @@
package com.example.android.systemupdatersample;
import android.content.Context;
+import android.os.Handler;
import android.os.UpdateEngine;
import android.os.UpdateEngineCallback;
import android.util.Log;
-import com.example.android.systemupdatersample.services.PrepareStreamingService;
-import com.example.android.systemupdatersample.util.PayloadSpecs;
+import com.example.android.systemupdatersample.services.PrepareUpdateService;
import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes;
import com.example.android.systemupdatersample.util.UpdateEngineProperties;
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;
@@ -50,11 +49,10 @@ public class UpdateManager {
private static final String TAG = "UpdateManager";
/** HTTP Header: User-Agent; it will be sent to the server when streaming the payload. */
- private static final String HTTP_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
+ static final String HTTP_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
+ "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36";
private final UpdateEngine mUpdateEngine;
- private final PayloadSpecs mPayloadSpecs;
private AtomicInteger mUpdateEngineStatus =
new AtomicInteger(UpdateEngine.UpdateStatusConstants.IDLE);
@@ -84,9 +82,15 @@ public class UpdateManager {
private final UpdateManager.UpdateEngineCallbackImpl
mUpdateEngineCallback = new UpdateManager.UpdateEngineCallbackImpl();
- public UpdateManager(UpdateEngine updateEngine, PayloadSpecs payloadSpecs) {
+ private final Handler mHandler;
+
+ /**
+ * @param updateEngine UpdateEngine instance.
+ * @param handler Handler for {@link PrepareUpdateService} intent service.
+ */
+ public UpdateManager(UpdateEngine updateEngine, Handler handler) {
this.mUpdateEngine = updateEngine;
- this.mPayloadSpecs = payloadSpecs;
+ this.mHandler = handler;
}
/**
@@ -293,45 +297,17 @@ public class UpdateManager {
mManualSwitchSlotRequired.set(false);
}
- if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING) {
- applyAbNonStreamingUpdate(config);
- } else {
- applyAbStreamingUpdate(context, config);
- }
- }
-
- private void applyAbNonStreamingUpdate(UpdateConfig config)
- throws UpdaterState.InvalidTransitionException {
- UpdateData.Builder builder = UpdateData.builder()
- .setExtraProperties(prepareExtraProperties(config));
-
- try {
- builder.setPayload(mPayloadSpecs.forNonStreaming(config.getUpdatePackageFile()));
- } catch (IOException e) {
- Log.e(TAG, "Error creating payload spec", e);
- setUpdaterState(UpdaterState.ERROR);
- return;
- }
- updateEngineApplyPayload(builder.build());
- }
-
- private void applyAbStreamingUpdate(Context context, UpdateConfig 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) {
- builder.setPayload(payloadSpec);
- builder.addExtraProperty("USER_AGENT=" + HTTP_USER_AGENT);
- config.getAbConfig()
- .getAuthorization()
- .ifPresent(s -> builder.addExtraProperty("AUTHORIZATION=" + s));
- updateEngineApplyPayload(builder.build());
- } else {
- Log.e(TAG, "PrepareStreamingService failed, result code is " + code);
+ Log.d(TAG, "Starting PrepareUpdateService");
+ PrepareUpdateService.startService(context, config, mHandler, (code, payloadSpec) -> {
+ if (code != PrepareUpdateService.RESULT_CODE_SUCCESS) {
+ Log.e(TAG, "PrepareUpdateService failed, result code is " + code);
setUpdaterStateSilent(UpdaterState.ERROR);
+ return;
}
+ updateEngineApplyPayload(UpdateData.builder()
+ .setExtraProperties(prepareExtraProperties(config))
+ .setPayload(payloadSpec)
+ .build());
});
}
@@ -343,6 +319,12 @@ public class UpdateManager {
// User will enable it manually by clicking "Switch Slot" button on the screen.
extraProperties.add(UpdateEngineProperties.PROPERTY_DISABLE_SWITCH_SLOT_ON_REBOOT);
}
+ if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_STREAMING) {
+ extraProperties.add("USER_AGENT=" + HTTP_USER_AGENT);
+ config.getAbConfig()
+ .getAuthorization()
+ .ifPresent(s -> extraProperties.add("AUTHORIZATION=" + s));
+ }
return extraProperties;
}
@@ -497,14 +479,14 @@ public class UpdateManager {
* system/update_engine/binder_service_android.cc in
* function BinderUpdateEngineAndroidService::bind).
*
- * @param status one of {@link UpdateEngine.UpdateStatusConstants}.
+ * @param status one of {@link UpdateEngine.UpdateStatusConstants}.
* @param progress a number from 0.0 to 1.0.
*/
private void onStatusUpdate(int status, float progress) {
Log.d(TAG, String.format(
- "onStatusUpdate invoked, status=%s, progress=%.2f",
- status,
- progress));
+ "onStatusUpdate invoked, status=%s, progress=%.2f",
+ status,
+ progress));
int previousStatus = mUpdateEngineStatus.get();
mUpdateEngineStatus.set(status);
@@ -555,7 +537,6 @@ 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}.
diff --git a/updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java b/updater_sample/src/com/example/android/systemupdatersample/services/PrepareUpdateService.java
index 931404857..29eb13da7 100644
--- a/updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java
+++ b/updater_sample/src/com/example/android/systemupdatersample/services/PrepareUpdateService.java
@@ -28,6 +28,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.RecoverySystem;
import android.os.ResultReceiver;
+import android.os.UpdateEngine;
import android.util.Log;
import com.example.android.systemupdatersample.PayloadSpec;
@@ -41,7 +42,9 @@ import com.google.common.collect.ImmutableSet;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Arrays;
import java.util.Optional;
/**
@@ -49,10 +52,10 @@ import java.util.Optional;
* without downloading the whole package. And it constructs {@link PayloadSpec}.
* All this work required to install streaming A/B updates.
*
- * PrepareStreamingService runs on it's own thread. It will notify activity
+ * PrepareUpdateService runs on it's own thread. It will notify activity
* using interface {@link UpdateResultCallback} when update is ready to install.
*/
-public class PrepareStreamingService extends IntentService {
+public class PrepareUpdateService extends IntentService {
/**
* UpdateResultCallback result codes.
@@ -61,62 +64,63 @@ public class PrepareStreamingService extends IntentService {
public static final int RESULT_CODE_ERROR = 1;
/**
- * This interface is used to send results from {@link PrepareStreamingService} to
+ * Extra params that will be sent to IntentService.
+ */
+ public static final String EXTRA_PARAM_CONFIG = "config";
+ public static final String EXTRA_PARAM_RESULT_RECEIVER = "result-receiver";
+
+ /**
+ * This interface is used to send results from {@link PrepareUpdateService} to
* {@code MainActivity}.
*/
public interface UpdateResultCallback {
-
/**
* Invoked when files are downloaded and payload spec is constructed.
*
- * @param resultCode result code, values are defined in {@link PrepareStreamingService}
+ * @param resultCode result code, values are defined in {@link PrepareUpdateService}
* @param payloadSpec prepared payload spec for streaming update
*/
void onReceiveResult(int resultCode, PayloadSpec payloadSpec);
}
/**
- * Starts PrepareStreamingService.
+ * Starts PrepareUpdateService.
*
- * @param context application context
- * @param config update config
+ * @param context application context
+ * @param config update config
* @param resultCallback callback that will be called when the update is ready to be installed
*/
public static void startService(Context context,
UpdateConfig config,
+ Handler handler,
UpdateResultCallback resultCallback) {
- Log.d(TAG, "Starting PrepareStreamingService");
- ResultReceiver receiver = new CallbackResultReceiver(new Handler(), resultCallback);
- Intent intent = new Intent(context, PrepareStreamingService.class);
+ Log.d(TAG, "Starting PrepareUpdateService");
+ ResultReceiver receiver = new CallbackResultReceiver(handler, resultCallback);
+ Intent intent = new Intent(context, PrepareUpdateService.class);
intent.putExtra(EXTRA_PARAM_CONFIG, config);
intent.putExtra(EXTRA_PARAM_RESULT_RECEIVER, receiver);
context.startService(intent);
}
- public PrepareStreamingService() {
+ public PrepareUpdateService() {
super(TAG);
}
- private static final String TAG = "PrepareStreamingService";
-
- /**
- * Extra params that will be sent from Activity to IntentService.
- */
- private static final String EXTRA_PARAM_CONFIG = "config";
- private static final String EXTRA_PARAM_RESULT_RECEIVER = "result-receiver";
+ private static final String TAG = "PrepareUpdateService";
/**
* The files that should be downloaded before streaming.
*/
private static final ImmutableSet<String> PRE_STREAMING_FILES_SET =
ImmutableSet.of(
- PackageFiles.CARE_MAP_FILE_NAME,
- PackageFiles.COMPATIBILITY_ZIP_FILE_NAME,
- PackageFiles.METADATA_FILE_NAME,
- PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME
+ PackageFiles.CARE_MAP_FILE_NAME,
+ PackageFiles.COMPATIBILITY_ZIP_FILE_NAME,
+ PackageFiles.METADATA_FILE_NAME,
+ PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME
);
private final PayloadSpecs mPayloadSpecs = new PayloadSpecs();
+ private final UpdateEngine mUpdateEngine = new UpdateEngine();
@Override
protected void onHandleIntent(Intent intent) {
@@ -142,6 +146,17 @@ public class PrepareStreamingService extends IntentService {
private PayloadSpec execute(UpdateConfig config)
throws IOException, PreparationFailedException {
+ if (config.getAbConfig().getVerifyPayloadMetadata()) {
+ Log.i(TAG, "Verifying payload metadata with UpdateEngine.");
+ if (!verifyPayloadMetadata(config)) {
+ throw new PreparationFailedException("Payload metadata is not compatible");
+ }
+ }
+
+ if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING) {
+ return mPayloadSpecs.forNonStreaming(config.getUpdatePackageFile());
+ }
+
downloadPreStreamingFiles(config, OTA_PACKAGE_DIR);
Optional<UpdateConfig.PackageFile> payloadBinary =
@@ -173,9 +188,52 @@ public class PrepareStreamingService extends IntentService {
}
/**
+ * Downloads only payload_metadata.bin and verifies with
+ * {@link UpdateEngine#verifyPayloadMetadata}.
+ * Returns {@code true} if the payload is verified or the result is unknown because of
+ * exception from UpdateEngine.
+ * By downloading only small portion of the package, it allows to verify if UpdateEngine
+ * will install the update.
+ */
+ private boolean verifyPayloadMetadata(UpdateConfig config) {
+ Optional<UpdateConfig.PackageFile> metadataPackageFile =
+ Arrays.stream(config.getAbConfig().getPropertyFiles())
+ .filter(p -> p.getFilename().equals(
+ PackageFiles.PAYLOAD_METADATA_FILE_NAME))
+ .findFirst();
+ if (!metadataPackageFile.isPresent()) {
+ Log.w(TAG, String.format("ab_config.property_files doesn't contain %s",
+ PackageFiles.PAYLOAD_METADATA_FILE_NAME));
+ return true;
+ }
+ Path metadataPath = Paths.get(OTA_PACKAGE_DIR, PackageFiles.PAYLOAD_METADATA_FILE_NAME);
+ try {
+ Files.deleteIfExists(metadataPath);
+ FileDownloader d = new FileDownloader(
+ config.getUrl(),
+ metadataPackageFile.get().getOffset(),
+ metadataPackageFile.get().getSize(),
+ metadataPath.toFile());
+ d.download();
+ } catch (IOException e) {
+ Log.w(TAG, String.format("Downloading %s from %s failed",
+ PackageFiles.PAYLOAD_METADATA_FILE_NAME,
+ config.getUrl()), e);
+ return true;
+ }
+ try {
+ return mUpdateEngine.verifyPayloadMetadata(metadataPath.toAbsolutePath().toString());
+ } catch (Exception e) {
+ Log.w(TAG, "UpdateEngine#verifyPayloadMetadata failed", e);
+ return true;
+ }
+ }
+
+ /**
* Downloads files defined in {@link UpdateConfig#getAbConfig()}
* and exists in {@code PRE_STREAMING_FILES_SET}, and put them
* in directory {@code dir}.
+ *
* @throws IOException when can't download a file
*/
private void downloadPreStreamingFiles(UpdateConfig config, String dir)
@@ -212,7 +270,7 @@ public class PrepareStreamingService extends IntentService {
}
/**
- * Used by {@link PrepareStreamingService} to pass {@link PayloadSpec}
+ * Used by {@link PrepareUpdateService} to pass {@link PayloadSpec}
* to {@link UpdateResultCallback#onReceiveResult}.
*/
private static class CallbackResultReceiver extends ResultReceiver {
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 fc9fddd70..6d1e4c35a 100644
--- a/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java
+++ b/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java
@@ -21,6 +21,7 @@ import android.app.AlertDialog;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
import android.os.UpdateEngine;
import android.util.Log;
import android.view.View;
@@ -34,7 +35,6 @@ 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;
@@ -67,7 +67,7 @@ public class MainActivity extends Activity {
private List<UpdateConfig> mConfigs;
private final UpdateManager mUpdateManager =
- new UpdateManager(new UpdateEngine(), new PayloadSpecs());
+ new UpdateManager(new UpdateEngine(), new Handler());
@Override
protected void onCreate(Bundle savedInstanceState) {
diff --git a/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java b/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java
index ddd0919b8..0f9083d27 100644
--- a/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java
+++ b/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java
@@ -30,7 +30,7 @@ import java.net.URLConnection;
* Downloads chunk of a file from given url using {@code offset} and {@code size},
* and saves to a given location.
*
- * In real-life application this helper class should download from HTTP Server,
+ * In a real-life application this helper class should download from HTTP Server,
* but in this sample app it will only download from a local file.
*/
public final class FileDownloader {
diff --git a/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateManagerTest.java b/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateManagerTest.java
index e05ad290c..5ad16d477 100644
--- a/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateManagerTest.java
+++ b/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateManagerTest.java
@@ -18,20 +18,25 @@ package com.example.android.systemupdatersample;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.ResultReceiver;
import android.os.UpdateEngine;
import android.os.UpdateEngineCallback;
import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import com.example.android.systemupdatersample.services.PrepareUpdateService;
import com.example.android.systemupdatersample.tests.R;
-import com.example.android.systemupdatersample.util.PayloadSpecs;
import com.google.common.collect.ImmutableList;
import com.google.common.io.CharStreams;
@@ -43,7 +48,6 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -60,49 +64,39 @@ public class UpdateManagerTest {
@Mock
private UpdateEngine mUpdateEngine;
@Mock
- private PayloadSpecs mPayloadSpecs;
+ private Context mMockContext;
private UpdateManager mSubject;
- private Context mContext;
- private UpdateConfig mNonStreamingUpdate003;
+ private Context mTestContext;
+ private UpdateConfig mStreamingUpdate002;
@Before
public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getContext();
- mSubject = new UpdateManager(mUpdateEngine, mPayloadSpecs);
- mNonStreamingUpdate003 =
- UpdateConfig.fromJson(readResource(R.raw.update_config_003_nonstream));
+ mTestContext = InstrumentationRegistry.getContext();
+ mSubject = new UpdateManager(mUpdateEngine, null);
+ mStreamingUpdate002 =
+ UpdateConfig.fromJson(readResource(R.raw.update_config_002_stream));
}
@Test
public void applyUpdate_appliesPayloadToUpdateEngine() throws Exception {
- PayloadSpec payload = buildMockPayloadSpec();
- when(mPayloadSpecs.forNonStreaming(any(File.class))).thenReturn(payload);
- when(mUpdateEngine.bind(any(UpdateEngineCallback.class))).thenAnswer(answer -> {
- // When UpdateManager is bound to update_engine, it passes
- // UpdateEngineCallback as a callback to update_engine.
- UpdateEngineCallback callback = answer.getArgument(0);
- callback.onStatusUpdate(
- UpdateEngine.UpdateStatusConstants.IDLE,
- /*engineProgress*/ 0.0f);
- return null;
- });
-
- mSubject.bind();
- mSubject.applyUpdate(null, mNonStreamingUpdate003);
+ mockContextStartServiceAnswer(buildMockPayloadSpec());
+ mSubject.applyUpdate(mMockContext, mStreamingUpdate002);
verify(mUpdateEngine).applyPayload(
"file://blah",
120,
340,
- new String[] {
- "SWITCH_SLOT_ON_REBOOT=0" // ab_config.force_switch_slot = false
+ new String[]{
+ "SWITCH_SLOT_ON_REBOOT=0", // ab_config.force_switch_slot = false
+ "USER_AGENT=" + UpdateManager.HTTP_USER_AGENT
});
}
@Test
- public void stateIsRunningAndEngineStatusIsIdle_reApplyLastUpdate() throws Exception {
- PayloadSpec payload = buildMockPayloadSpec();
- when(mPayloadSpecs.forNonStreaming(any(File.class))).thenReturn(payload);
+ @UiThreadTest
+ public void stateIsRunningAndEngineStatusIsIdle_reApplyLastUpdate() throws Throwable {
+ mockContextStartServiceAnswer(buildMockPayloadSpec());
+ // UpdateEngine always returns IDLE status.
when(mUpdateEngine.bind(any(UpdateEngineCallback.class))).thenAnswer(answer -> {
// When UpdateManager is bound to update_engine, it passes
// UpdateEngineCallback as a callback to update_engine.
@@ -114,21 +108,36 @@ public class UpdateManagerTest {
});
mSubject.bind();
- mSubject.applyUpdate(null, mNonStreamingUpdate003);
+ mSubject.applyUpdate(mMockContext, mStreamingUpdate002);
mSubject.unbind();
mSubject.bind(); // re-bind - now it should re-apply last update
assertEquals(mSubject.getUpdaterState(), UpdaterState.RUNNING);
- // it should be called 2 times
verify(mUpdateEngine, times(2)).applyPayload(
"file://blah",
120,
340,
- new String[] {
- "SWITCH_SLOT_ON_REBOOT=0" // ab_config.force_switch_slot = false
+ new String[]{
+ "SWITCH_SLOT_ON_REBOOT=0", // ab_config.force_switch_slot = false
+ "USER_AGENT=" + UpdateManager.HTTP_USER_AGENT
});
}
+ private void mockContextStartServiceAnswer(PayloadSpec payloadSpec) {
+ doAnswer(args -> {
+ Intent intent = args.getArgument(0);
+ ResultReceiver resultReceiver = intent.getParcelableExtra(
+ PrepareUpdateService.EXTRA_PARAM_RESULT_RECEIVER);
+ Bundle b = new Bundle();
+ b.putSerializable(
+ /* PrepareUpdateService.CallbackResultReceiver.BUNDLE_PARAM_PAYLOAD_SPEC */
+ "payload-spec",
+ payloadSpec);
+ resultReceiver.send(PrepareUpdateService.RESULT_CODE_SUCCESS, b);
+ return null;
+ }).when(mMockContext).startService(any(Intent.class));
+ }
+
private PayloadSpec buildMockPayloadSpec() {
PayloadSpec payload = mock(PayloadSpec.class);
when(payload.getUrl()).thenReturn("file://blah");
@@ -140,7 +149,7 @@ public class UpdateManagerTest {
private String readResource(int id) throws IOException {
return CharStreams.toString(new InputStreamReader(
- mContext.getResources().openRawResource(id)));
+ mTestContext.getResources().openRawResource(id)));
}
}