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: Iea69acd9aa41e17538c26aff60f7598093ca7744
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 {