From 0dd5a83d0ec4c9d7b51843dd6eebd5ce92a1ad92 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Mon, 23 Apr 2018 11:38:54 -0700 Subject: updater_sample: add streaming support - UpdateConfigs: add helper methods for streaming - add PrepareStreamingService intent service Test: manually and junit4 Bug: 77148143 Change-Id: I61711eb9abe051987e725fbd94f8cd029ff21dd3 --- .../android/systemupdatersample/UpdateConfig.java | 18 +- .../services/PrepareStreamingService.java | 216 +++++++++++++++++++++ .../systemupdatersample/ui/MainActivity.java | 12 ++ .../systemupdatersample/util/FileDownloader.java | 13 +- .../systemupdatersample/util/UpdateConfigs.java | 26 ++- 5 files changed, 265 insertions(+), 20 deletions(-) create mode 100644 updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java (limited to 'updater_sample/src/com/example') diff --git a/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java b/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java index 23510e426..1851724ed 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java +++ b/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java @@ -69,11 +69,11 @@ public class UpdateConfig implements Parcelable { if (c.mAbInstallType == AB_INSTALL_TYPE_STREAMING) { JSONObject meta = o.getJSONObject("ab_streaming_metadata"); JSONArray propertyFilesJson = meta.getJSONArray("property_files"); - InnerFile[] propertyFiles = - new InnerFile[propertyFilesJson.length()]; + PackageFile[] propertyFiles = + new PackageFile[propertyFilesJson.length()]; for (int i = 0; i < propertyFilesJson.length(); i++) { JSONObject p = propertyFilesJson.getJSONObject(i); - propertyFiles[i] = new InnerFile( + propertyFiles[i] = new PackageFile( p.getString("filename"), p.getLong("offset"), p.getLong("size")); @@ -176,17 +176,17 @@ public class UpdateConfig implements Parcelable { private static final long serialVersionUID = 31042L; /** defines beginning of update data in archive */ - private InnerFile[] mPropertyFiles; + private PackageFile[] mPropertyFiles; public StreamingMetadata() { - mPropertyFiles = new InnerFile[0]; + mPropertyFiles = new PackageFile[0]; } - public StreamingMetadata(InnerFile[] propertyFiles) { + public StreamingMetadata(PackageFile[] propertyFiles) { this.mPropertyFiles = propertyFiles; } - public InnerFile[] getPropertyFiles() { + public PackageFile[] getPropertyFiles() { return mPropertyFiles; } } @@ -194,7 +194,7 @@ public class UpdateConfig implements Parcelable { /** * Description of a file in an OTA package zip file. */ - public static class InnerFile implements Serializable { + public static class PackageFile implements Serializable { private static final long serialVersionUID = 31043L; @@ -207,7 +207,7 @@ public class UpdateConfig implements Parcelable { /** size of the update data in archive */ private long mSize; - public InnerFile(String filename, long offset, long size) { + public PackageFile(String filename, long offset, long size) { this.mFilename = filename; this.mOffset = offset; this.mSize = size; diff --git a/updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java b/updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java new file mode 100644 index 000000000..840a6d607 --- /dev/null +++ b/updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java @@ -0,0 +1,216 @@ +/* + * 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.services; + +import static com.example.android.systemupdatersample.util.PackageFiles.OTA_PACKAGE_DIR; +import static com.example.android.systemupdatersample.util.PackageFiles.PAYLOAD_BINARY_FILE_NAME; +import static com.example.android.systemupdatersample.util.PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME; + +import android.app.IntentService; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.ResultReceiver; +import android.util.Log; + +import com.example.android.systemupdatersample.PayloadSpec; +import com.example.android.systemupdatersample.UpdateConfig; +import com.example.android.systemupdatersample.util.FileDownloader; +import com.example.android.systemupdatersample.util.PackageFiles; +import com.example.android.systemupdatersample.util.PayloadSpecs; +import com.example.android.systemupdatersample.util.UpdateConfigs; +import com.google.common.collect.ImmutableSet; + +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Optional; + +/** + * This IntentService will download/extract the necessary files from the package zip + * 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 + * using interface {@link UpdateResultCallback} when update is ready to install. + */ +public class PrepareStreamingService extends IntentService { + + /** + * UpdateResultCallback result codes. + */ + public static final int RESULT_CODE_SUCCESS = 0; + public static final int RESULT_CODE_ERROR = 1; + + /** + * This interface is used to send results from {@link PrepareStreamingService} 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 payloadSpec prepared payload spec for streaming update + */ + void onReceiveResult(int resultCode, PayloadSpec payloadSpec); + } + + /** + * Starts PrepareStreamingService. + * + * @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, + UpdateResultCallback resultCallback) { + Log.d(TAG, "Starting PrepareStreamingService"); + ResultReceiver receiver = new CallbackResultReceiver(new Handler(), resultCallback); + Intent intent = new Intent(context, PrepareStreamingService.class); + intent.putExtra(EXTRA_PARAM_CONFIG, config); + intent.putExtra(EXTRA_PARAM_RESULT_RECEIVER, receiver); + context.startService(intent); + } + + public PrepareStreamingService() { + 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"; + + /** + * The files that should be downloaded before streaming. + */ + private static final ImmutableSet 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 + ); + + @Override + protected void onHandleIntent(Intent intent) { + Log.d(TAG, "On handle intent is called"); + UpdateConfig config = intent.getParcelableExtra(EXTRA_PARAM_CONFIG); + ResultReceiver resultReceiver = intent.getParcelableExtra(EXTRA_PARAM_RESULT_RECEIVER); + + try { + downloadPreStreamingFiles(config, OTA_PACKAGE_DIR); + } catch (IOException e) { + Log.e(TAG, "Failed to download pre-streaming files", e); + resultReceiver.send(RESULT_CODE_ERROR, null); + return; + } + + Optional payloadBinary = + UpdateConfigs.getPropertyFile(PAYLOAD_BINARY_FILE_NAME, config); + + if (!payloadBinary.isPresent()) { + Log.e(TAG, "Failed to find " + PAYLOAD_BINARY_FILE_NAME + " in config"); + resultReceiver.send(RESULT_CODE_ERROR, null); + return; + } + + Optional properties = + UpdateConfigs.getPropertyFile(PAYLOAD_PROPERTIES_FILE_NAME, config); + + if (!properties.isPresent()) { + Log.e(TAG, "Failed to find " + PAYLOAD_PROPERTIES_FILE_NAME + " in config"); + resultReceiver.send(RESULT_CODE_ERROR, null); + return; + } + + PayloadSpec spec; + try { + spec = PayloadSpecs.forStreaming(config.getUrl(), + payloadBinary.get().getOffset(), + payloadBinary.get().getSize(), + Paths.get(OTA_PACKAGE_DIR, properties.get().getFilename()).toFile() + ); + } catch (IOException e) { + Log.e(TAG, "PayloadSpecs failed to create PayloadSpec for streaming", e); + resultReceiver.send(RESULT_CODE_ERROR, null); + return; + } + + resultReceiver.send(RESULT_CODE_SUCCESS, CallbackResultReceiver.createBundle(spec)); + } + + /** + * Downloads files defined in {@link UpdateConfig#getStreamingMetadata()} + * and exists in {@code PRE_STREAMING_FILES_SET}, and put them + * in directory {@code dir}. + * @throws IOException when can't download a file + */ + private static void downloadPreStreamingFiles(UpdateConfig config, String dir) + throws IOException { + Log.d(TAG, "Downloading files to " + dir); + for (UpdateConfig.PackageFile file : config.getStreamingMetadata().getPropertyFiles()) { + if (PRE_STREAMING_FILES_SET.contains(file.getFilename())) { + Log.d(TAG, "Downloading file " + file.getFilename()); + FileDownloader downloader = new FileDownloader( + config.getUrl(), + file.getOffset(), + file.getSize(), + Paths.get(dir, file.getFilename()).toFile()); + downloader.download(); + } + } + } + + /** + * Used by {@link PrepareStreamingService} to pass {@link PayloadSpec} + * to {@link UpdateResultCallback#onReceiveResult}. + */ + private static class CallbackResultReceiver extends ResultReceiver { + + static Bundle createBundle(PayloadSpec payloadSpec) { + Bundle b = new Bundle(); + b.putSerializable(BUNDLE_PARAM_PAYLOAD_SPEC, payloadSpec); + return b; + } + + private static final String BUNDLE_PARAM_PAYLOAD_SPEC = "payload-spec"; + + private UpdateResultCallback mUpdateResultCallback; + + CallbackResultReceiver(Handler handler, UpdateResultCallback updateResultCallback) { + super(handler); + this.mUpdateResultCallback = updateResultCallback; + } + + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + PayloadSpec payloadSpec = null; + if (resultCode == RESULT_CODE_SUCCESS) { + payloadSpec = (PayloadSpec) resultData.getSerializable(BUNDLE_PARAM_PAYLOAD_SPEC); + } + mUpdateResultCallback.onReceiveResult(resultCode, payloadSpec); + } + } + +} 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 d6a6ce3f5..359e2b10c 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java +++ b/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java @@ -34,6 +34,7 @@ import android.widget.Toast; import com.example.android.systemupdatersample.PayloadSpec; import com.example.android.systemupdatersample.R; import com.example.android.systemupdatersample.UpdateConfig; +import com.example.android.systemupdatersample.services.PrepareStreamingService; import com.example.android.systemupdatersample.util.PayloadSpecs; import com.example.android.systemupdatersample.util.UpdateConfigs; import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes; @@ -297,6 +298,17 @@ public class MainActivity extends Activity { updateEngineApplyPayload(payload); } else { Log.d(TAG, "Starting PrepareStreamingService"); + PrepareStreamingService.startService(this, config, (code, payloadSpec) -> { + if (code == PrepareStreamingService.RESULT_CODE_SUCCESS) { + updateEngineApplyPayload(payloadSpec); + } else { + Log.e(TAG, "PrepareStreamingService failed, result code is " + code); + Toast.makeText( + MainActivity.this, + "PrepareStreamingService failed, result code is " + code, + Toast.LENGTH_LONG).show(); + } + }); } } 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 5c1d71117..ddd0919b8 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java +++ b/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java @@ -38,22 +38,23 @@ public final class FileDownloader { private String mUrl; private long mOffset; private long mSize; - private File mOut; + private File mDestination; - public FileDownloader(String url, long offset, long size, File out) { + public FileDownloader(String url, long offset, long size, File destination) { this.mUrl = url; this.mOffset = offset; this.mSize = size; - this.mOut = out; + this.mDestination = destination; } /** * Downloads the file with given offset and size. + * @throws IOException when can't download the file */ public void download() throws IOException { - Log.d("FileDownloader", "downloading " + mOut.getName() + Log.d("FileDownloader", "downloading " + mDestination.getName() + " from " + mUrl - + " to " + mOut.getAbsolutePath()); + + " to " + mDestination.getAbsolutePath()); URL url = new URL(mUrl); URLConnection connection = url.openConnection(); @@ -61,7 +62,7 @@ public final class FileDownloader { // download the file try (InputStream input = connection.getInputStream()) { - try (OutputStream output = new FileOutputStream(mOut)) { + try (OutputStream output = new FileOutputStream(mDestination)) { long skipped = input.skip(mOffset); if (skipped != mOffset) { throw new IOException("Can't download file " diff --git a/updater_sample/src/com/example/android/systemupdatersample/util/UpdateConfigs.java b/updater_sample/src/com/example/android/systemupdatersample/util/UpdateConfigs.java index 71d4df8ab..5080cb6d8 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/util/UpdateConfigs.java +++ b/updater_sample/src/com/example/android/systemupdatersample/util/UpdateConfigs.java @@ -26,14 +26,16 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Optional; /** * Utility class for working with json update configurations. */ public final class UpdateConfigs { - private static final String UPDATE_CONFIGS_ROOT = "configs/"; + public static final String UPDATE_CONFIGS_ROOT = "configs/"; /** * @param configs update configs @@ -48,13 +50,12 @@ public final class UpdateConfigs { * @return configs root directory */ public static String getConfigsRoot(Context context) { - return Paths.get(context.getFilesDir().toString(), - UPDATE_CONFIGS_ROOT).toString(); + return Paths + .get(context.getFilesDir().toString(), UPDATE_CONFIGS_ROOT) + .toString(); } /** - * It parses only {@code .json} files. - * * @param context application context * @return list of configs from directory {@link UpdateConfigs#getConfigsRoot} */ @@ -80,5 +81,20 @@ public final class UpdateConfigs { return configs; } + /** + * @param filename searches by given filename + * @param config searches in {@link UpdateConfig#getStreamingMetadata()} + * @return offset and size of {@code filename} in the package zip file + * stored as {@link UpdateConfig.PackageFile}. + */ + public static Optional getPropertyFile( + final String filename, + UpdateConfig config) { + return Arrays + .stream(config.getStreamingMetadata().getPropertyFiles()) + .filter(file -> filename.equals(file.getFilename())) + .findFirst(); + } + private UpdateConfigs() {} } -- cgit v1.2.3 From 6aa5fb0bbe8d074208648593d177553e732e6d9d Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Wed, 9 May 2018 14:28:49 -0700 Subject: updater_sample: add http header demo Added demo passing http headers to UpdateEngine#applyPayload. Bug: 79483768 Test: manually Change-Id: I3e9c812dba2066acadbcea8d07c933368806e20c Signed-off-by: Zhomart Mukhamejanov --- .../android/systemupdatersample/UpdateConfig.java | 20 +++++++++++------ .../systemupdatersample/ui/MainActivity.java | 25 ++++++++++++++++++---- 2 files changed, 35 insertions(+), 10 deletions(-) (limited to 'updater_sample/src/com/example') diff --git a/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java b/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java index 1851724ed..b08bfd0f6 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java +++ b/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java @@ -25,6 +25,7 @@ import org.json.JSONObject; import java.io.File; import java.io.Serializable; +import java.util.Optional; /** * An update description. It will be parsed from JSON, which is intended to @@ -78,7 +79,9 @@ public class UpdateConfig implements Parcelable { p.getLong("offset"), p.getLong("size")); } - c.mAbStreamingMetadata = new StreamingMetadata(propertyFiles); + c.mAbStreamingMetadata = new StreamingMetadata( + propertyFiles, + meta.getString("authorization_token")); } c.mRawJson = json; return c; @@ -178,17 +181,23 @@ public class UpdateConfig implements Parcelable { /** defines beginning of update data in archive */ private PackageFile[] mPropertyFiles; - public StreamingMetadata() { - mPropertyFiles = new PackageFile[0]; - } + /** SystemUpdaterSample receives the authorization token from the OTA server, in addition + * to the package URL. It passes on the info to update_engine, so that the latter can + * fetch the data from the package server directly with the token. */ + private String mAuthorization; - public StreamingMetadata(PackageFile[] propertyFiles) { + public StreamingMetadata(PackageFile[] propertyFiles, String authorization) { this.mPropertyFiles = propertyFiles; + this.mAuthorization = authorization; } public PackageFile[] getPropertyFiles() { return mPropertyFiles; } + + public Optional getAuthorization() { + return Optional.of(mAuthorization); + } } /** @@ -224,7 +233,6 @@ public class UpdateConfig implements Parcelable { public long getSize() { return mSize; } - } } 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 359e2b10c..170825635 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java +++ b/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java @@ -41,6 +41,7 @@ import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes; import com.example.android.systemupdatersample.util.UpdateEngineStatuses; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -51,6 +52,10 @@ public class MainActivity extends Activity { private static final String TAG = "MainActivity"; + /** 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) " + + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36"; + private TextView mTextViewBuild; private Spinner mSpinnerConfigs; private TextView mTextViewConfigsDirHint; @@ -295,12 +300,17 @@ public class MainActivity extends Activity { .show(); return; } - updateEngineApplyPayload(payload); + updateEngineApplyPayload(payload, null); } else { Log.d(TAG, "Starting PrepareStreamingService"); PrepareStreamingService.startService(this, config, (code, payloadSpec) -> { if (code == PrepareStreamingService.RESULT_CODE_SUCCESS) { - updateEngineApplyPayload(payloadSpec); + List extraProperties = new ArrayList<>(); + extraProperties.add("USER_AGENT=" + HTTP_USER_AGENT); + config.getStreamingMetadata() + .getAuthorization() + .ifPresent(s -> extraProperties.add("AUTHORIZATION=" + s)); + updateEngineApplyPayload(payloadSpec, extraProperties); } else { Log.e(TAG, "PrepareStreamingService failed, result code is " + code); Toast.makeText( @@ -317,14 +327,21 @@ public class MainActivity extends Activity { * * UpdateEngine works asynchronously. This method doesn't wait until * end of the update. + * + * @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) { + private void updateEngineApplyPayload(PayloadSpec payloadSpec, List extraProperties) { + ArrayList properties = new ArrayList<>(payloadSpec.getProperties()); + if (extraProperties != null) { + properties.addAll(extraProperties); + } try { mUpdateEngine.applyPayload( payloadSpec.getUrl(), payloadSpec.getOffset(), payloadSpec.getSize(), - payloadSpec.getProperties().toArray(new String[0])); + properties.toArray(new String[0])); } catch (Exception e) { Log.e(TAG, "UpdateEngine failed to apply the update", e); Toast.makeText( -- cgit v1.2.3 From bb8a2151387148e66bdd0b0ceb5d70d9db84ba69 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Thu, 10 May 2018 12:19:16 -0700 Subject: updater_sample: fix UpdateConfig - fix UpdateConfig - fix smaple.json - fix FileDownloaderTest Test: manually Change-Id: I09d272f77c89a02f54a24cf753fdfda4ce243927 Signed-off-by: Zhomart Mukhamejanov --- .../src/com/example/android/systemupdatersample/UpdateConfig.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'updater_sample/src/com/example') diff --git a/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java b/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java index b08bfd0f6..9bdd8b9e8 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java +++ b/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java @@ -79,9 +79,13 @@ public class UpdateConfig implements Parcelable { p.getLong("offset"), p.getLong("size")); } + String authorization = null; + if (meta.has("authorization")) { + authorization = meta.getString("authorization"); + } c.mAbStreamingMetadata = new StreamingMetadata( propertyFiles, - meta.getString("authorization_token")); + authorization); } c.mRawJson = json; return c; @@ -196,7 +200,7 @@ public class UpdateConfig implements Parcelable { } public Optional getAuthorization() { - return Optional.of(mAuthorization); + return mAuthorization == null ? Optional.empty() : Optional.of(mAuthorization); } } -- cgit v1.2.3 From 46a51ac4b651518e7160d83c8462ab7a556bd344 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Wed, 9 May 2018 09:53:45 -0700 Subject: updater_sample: add HAL compatibility check - Refactor PrepareStreamingService#onHandleIntent - Add PrepareStreamingService#verifyPackageCompatibility Test: on the device Bug: 79471299 Signed-off-by: Zhomart Mukhamejanov Change-Id: I1b18502f0638d66810a3f7ada582e4c7cea20cdb --- .../services/PrepareStreamingService.java | 85 +++++++++++++++------- 1 file changed, 59 insertions(+), 26 deletions(-) (limited to 'updater_sample/src/com/example') diff --git a/updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java b/updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java index 840a6d607..222bb0a58 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java +++ b/updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java @@ -16,6 +16,7 @@ package com.example.android.systemupdatersample.services; +import static com.example.android.systemupdatersample.util.PackageFiles.COMPATIBILITY_ZIP_FILE_NAME; import static com.example.android.systemupdatersample.util.PackageFiles.OTA_PACKAGE_DIR; import static com.example.android.systemupdatersample.util.PackageFiles.PAYLOAD_BINARY_FILE_NAME; import static com.example.android.systemupdatersample.util.PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME; @@ -25,6 +26,7 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.Handler; +import android.os.RecoverySystem; import android.os.ResultReceiver; import android.util.Log; @@ -36,7 +38,9 @@ import com.example.android.systemupdatersample.util.PayloadSpecs; import com.example.android.systemupdatersample.util.UpdateConfigs; import com.google.common.collect.ImmutableSet; +import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Paths; import java.util.Optional; @@ -119,45 +123,51 @@ public class PrepareStreamingService extends IntentService { ResultReceiver resultReceiver = intent.getParcelableExtra(EXTRA_PARAM_RESULT_RECEIVER); try { - downloadPreStreamingFiles(config, OTA_PACKAGE_DIR); - } catch (IOException e) { - Log.e(TAG, "Failed to download pre-streaming files", e); + PayloadSpec spec = execute(config); + resultReceiver.send(RESULT_CODE_SUCCESS, CallbackResultReceiver.createBundle(spec)); + } catch (Exception e) { + Log.e(TAG, "Failed to prepare streaming update", e); resultReceiver.send(RESULT_CODE_ERROR, null); - return; } + } + + /** + * 1. Downloads files for streaming updates. + * 2. Makes sure required files are present. + * 3. Checks OTA package compatibility with the device. + * 4. Constructs {@link PayloadSpec} for streaming update. + */ + private static PayloadSpec execute(UpdateConfig config) + throws IOException, PreparationFailedException { + + downloadPreStreamingFiles(config, OTA_PACKAGE_DIR); Optional payloadBinary = UpdateConfigs.getPropertyFile(PAYLOAD_BINARY_FILE_NAME, config); if (!payloadBinary.isPresent()) { - Log.e(TAG, "Failed to find " + PAYLOAD_BINARY_FILE_NAME + " in config"); - resultReceiver.send(RESULT_CODE_ERROR, null); - return; + throw new PreparationFailedException( + "Failed to find " + PAYLOAD_BINARY_FILE_NAME + " in config"); } - Optional properties = - UpdateConfigs.getPropertyFile(PAYLOAD_PROPERTIES_FILE_NAME, config); - - if (!properties.isPresent()) { - Log.e(TAG, "Failed to find " + PAYLOAD_PROPERTIES_FILE_NAME + " in config"); - resultReceiver.send(RESULT_CODE_ERROR, null); - return; + if (!UpdateConfigs.getPropertyFile(PAYLOAD_PROPERTIES_FILE_NAME, config).isPresent() + || !Paths.get(OTA_PACKAGE_DIR, PAYLOAD_PROPERTIES_FILE_NAME).toFile().exists()) { + throw new IOException(PAYLOAD_PROPERTIES_FILE_NAME + " not found"); } - PayloadSpec spec; - try { - spec = PayloadSpecs.forStreaming(config.getUrl(), - payloadBinary.get().getOffset(), - payloadBinary.get().getSize(), - Paths.get(OTA_PACKAGE_DIR, properties.get().getFilename()).toFile() - ); - } catch (IOException e) { - Log.e(TAG, "PayloadSpecs failed to create PayloadSpec for streaming", e); - resultReceiver.send(RESULT_CODE_ERROR, null); - return; + File compatibilityFile = Paths.get(OTA_PACKAGE_DIR, COMPATIBILITY_ZIP_FILE_NAME).toFile(); + if (compatibilityFile.isFile()) { + Log.i(TAG, "Verifying OTA package for compatibility with the device"); + if (!verifyPackageCompatibility(compatibilityFile)) { + throw new PreparationFailedException( + "OTA package is not compatible with this device"); + } } - resultReceiver.send(RESULT_CODE_SUCCESS, CallbackResultReceiver.createBundle(spec)); + return PayloadSpecs.forStreaming(config.getUrl(), + payloadBinary.get().getOffset(), + payloadBinary.get().getSize(), + Paths.get(OTA_PACKAGE_DIR, PAYLOAD_PROPERTIES_FILE_NAME).toFile()); } /** @@ -168,6 +178,10 @@ public class PrepareStreamingService extends IntentService { */ private static void downloadPreStreamingFiles(UpdateConfig config, String dir) throws IOException { + Log.d(TAG, "Deleting existing files from " + dir); + for (String file : PRE_STREAMING_FILES_SET) { + Files.deleteIfExists(Paths.get(OTA_PACKAGE_DIR, file)); + } Log.d(TAG, "Downloading files to " + dir); for (UpdateConfig.PackageFile file : config.getStreamingMetadata().getPropertyFiles()) { if (PRE_STREAMING_FILES_SET.contains(file.getFilename())) { @@ -182,6 +196,19 @@ public class PrepareStreamingService extends IntentService { } } + /** + * @param file physical location of {@link PackageFiles#COMPATIBILITY_ZIP_FILE_NAME} + * @return true if OTA package is compatible with this device + */ + private static boolean verifyPackageCompatibility(File file) { + try { + return RecoverySystem.verifyPackageCompatibility(file); + } catch (IOException e) { + Log.e(TAG, "Failed to verify package compatibility", e); + return false; + } + } + /** * Used by {@link PrepareStreamingService} to pass {@link PayloadSpec} * to {@link UpdateResultCallback#onReceiveResult}. @@ -213,4 +240,10 @@ public class PrepareStreamingService extends IntentService { } } + private static class PreparationFailedException extends Exception { + PreparationFailedException(String message) { + super(message); + } + } + } -- cgit v1.2.3