Add PrepareUpdateService.
It's moved from PrepareStreamingService intent service.
Now PrepareUpdateService takes an UpdateConfig and
builds PayloadSpec for UpdateEngine for both streaming
and non-streaming update.
It allows us to do all preparations in intent service's
thread, without blocking UI.
We will also add checksum verification to
PrepareUpdateService.
Test: device, junit
Bug: 77150191
Change-Id: I15c0bc58e3238bea6ea1c4f13063575e2def89c1
Merged-In: Iea69acd9aa41e17538c26aff60f7598093ca7744
diff --git a/updater_sample/AndroidManifest.xml b/updater_sample/AndroidManifest.xml
index 18d8425..0a25116 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 f9c3fb8..5894cf8 100644
--- a/updater_sample/README.md
+++ b/updater_sample/README.md
@@ -220,7 +220,7 @@
- [x] Add Sample app update state (separate from update_engine status)
- [x] Add smart update completion detection using onStatusUpdate
- [x] Add pause/resume demo
-- [x] Verify system partition checksum for package
+- [-] Verify system partition checksum for package
## Running tests
@@ -235,8 +235,8 @@
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 12a8f3f..c02e608 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 @@
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 @@
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 @@
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 @@
// 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 @@
* 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 @@
}
/**
- *
* 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
similarity index 85%
rename from updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java
rename to updater_sample/src/com/example/android/systemupdatersample/services/PrepareUpdateService.java
index 9314048..06581be 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.Handler;
import android.os.RecoverySystem;
import android.os.ResultReceiver;
+import android.os.UpdateEngine;
import android.util.Log;
import com.example.android.systemupdatersample.PayloadSpec;
@@ -49,10 +50,10 @@
* 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 +62,63 @@
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 +144,10 @@
private PayloadSpec execute(UpdateConfig config)
throws IOException, PreparationFailedException {
+ if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING) {
+ return mPayloadSpecs.forNonStreaming(config.getUpdatePackageFile());
+ }
+
downloadPreStreamingFiles(config, OTA_PACKAGE_DIR);
Optional<UpdateConfig.PackageFile> payloadBinary =
@@ -176,6 +182,7 @@
* 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 +219,7 @@
}
/**
- * 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 fc9fddd..6d1e4c3 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.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.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 @@
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 ddd0919..0f9083d 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 @@
* 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 e05ad29..5ad16d4 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 @@
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.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -60,49 +64,39 @@
@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 @@
});
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 @@
private String readResource(int id) throws IOException {
return CharStreams.toString(new InputStreamReader(
- mContext.getResources().openRawResource(id)));
+ mTestContext.getResources().openRawResource(id)));
}
}