blob: 931404857b96eadaaa52e8db2c0281c0ad42d14b [file] [log] [blame]
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -07001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.example.android.systemupdatersample.services;
18
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -070019import static com.example.android.systemupdatersample.util.PackageFiles.COMPATIBILITY_ZIP_FILE_NAME;
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -070020import static com.example.android.systemupdatersample.util.PackageFiles.OTA_PACKAGE_DIR;
21import static com.example.android.systemupdatersample.util.PackageFiles.PAYLOAD_BINARY_FILE_NAME;
22import static com.example.android.systemupdatersample.util.PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME;
23
24import android.app.IntentService;
25import android.content.Context;
26import android.content.Intent;
27import android.os.Bundle;
28import android.os.Handler;
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -070029import android.os.RecoverySystem;
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -070030import android.os.ResultReceiver;
31import android.util.Log;
32
33import com.example.android.systemupdatersample.PayloadSpec;
34import com.example.android.systemupdatersample.UpdateConfig;
35import com.example.android.systemupdatersample.util.FileDownloader;
36import com.example.android.systemupdatersample.util.PackageFiles;
37import com.example.android.systemupdatersample.util.PayloadSpecs;
38import com.example.android.systemupdatersample.util.UpdateConfigs;
39import com.google.common.collect.ImmutableSet;
40
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -070041import java.io.File;
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -070042import java.io.IOException;
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -070043import java.nio.file.Files;
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -070044import java.nio.file.Paths;
45import java.util.Optional;
46
47/**
48 * This IntentService will download/extract the necessary files from the package zip
49 * without downloading the whole package. And it constructs {@link PayloadSpec}.
50 * All this work required to install streaming A/B updates.
51 *
52 * PrepareStreamingService runs on it's own thread. It will notify activity
53 * using interface {@link UpdateResultCallback} when update is ready to install.
54 */
55public class PrepareStreamingService extends IntentService {
56
57 /**
58 * UpdateResultCallback result codes.
59 */
60 public static final int RESULT_CODE_SUCCESS = 0;
61 public static final int RESULT_CODE_ERROR = 1;
62
63 /**
64 * This interface is used to send results from {@link PrepareStreamingService} to
65 * {@code MainActivity}.
66 */
67 public interface UpdateResultCallback {
68
69 /**
70 * Invoked when files are downloaded and payload spec is constructed.
71 *
72 * @param resultCode result code, values are defined in {@link PrepareStreamingService}
73 * @param payloadSpec prepared payload spec for streaming update
74 */
75 void onReceiveResult(int resultCode, PayloadSpec payloadSpec);
76 }
77
78 /**
79 * Starts PrepareStreamingService.
80 *
81 * @param context application context
82 * @param config update config
83 * @param resultCallback callback that will be called when the update is ready to be installed
84 */
85 public static void startService(Context context,
86 UpdateConfig config,
87 UpdateResultCallback resultCallback) {
88 Log.d(TAG, "Starting PrepareStreamingService");
89 ResultReceiver receiver = new CallbackResultReceiver(new Handler(), resultCallback);
90 Intent intent = new Intent(context, PrepareStreamingService.class);
91 intent.putExtra(EXTRA_PARAM_CONFIG, config);
92 intent.putExtra(EXTRA_PARAM_RESULT_RECEIVER, receiver);
93 context.startService(intent);
94 }
95
96 public PrepareStreamingService() {
97 super(TAG);
98 }
99
100 private static final String TAG = "PrepareStreamingService";
101
102 /**
103 * Extra params that will be sent from Activity to IntentService.
104 */
105 private static final String EXTRA_PARAM_CONFIG = "config";
106 private static final String EXTRA_PARAM_RESULT_RECEIVER = "result-receiver";
107
108 /**
109 * The files that should be downloaded before streaming.
110 */
111 private static final ImmutableSet<String> PRE_STREAMING_FILES_SET =
112 ImmutableSet.of(
113 PackageFiles.CARE_MAP_FILE_NAME,
114 PackageFiles.COMPATIBILITY_ZIP_FILE_NAME,
115 PackageFiles.METADATA_FILE_NAME,
116 PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME
117 );
118
Zhomart Mukhamejanovb0361ff2018-05-18 10:22:13 -0700119 private final PayloadSpecs mPayloadSpecs = new PayloadSpecs();
120
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700121 @Override
122 protected void onHandleIntent(Intent intent) {
123 Log.d(TAG, "On handle intent is called");
124 UpdateConfig config = intent.getParcelableExtra(EXTRA_PARAM_CONFIG);
125 ResultReceiver resultReceiver = intent.getParcelableExtra(EXTRA_PARAM_RESULT_RECEIVER);
126
127 try {
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -0700128 PayloadSpec spec = execute(config);
129 resultReceiver.send(RESULT_CODE_SUCCESS, CallbackResultReceiver.createBundle(spec));
130 } catch (Exception e) {
131 Log.e(TAG, "Failed to prepare streaming update", e);
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700132 resultReceiver.send(RESULT_CODE_ERROR, null);
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700133 }
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -0700134 }
135
136 /**
137 * 1. Downloads files for streaming updates.
138 * 2. Makes sure required files are present.
139 * 3. Checks OTA package compatibility with the device.
140 * 4. Constructs {@link PayloadSpec} for streaming update.
141 */
Zhomart Mukhamejanovb0361ff2018-05-18 10:22:13 -0700142 private PayloadSpec execute(UpdateConfig config)
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -0700143 throws IOException, PreparationFailedException {
144
145 downloadPreStreamingFiles(config, OTA_PACKAGE_DIR);
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700146
147 Optional<UpdateConfig.PackageFile> payloadBinary =
148 UpdateConfigs.getPropertyFile(PAYLOAD_BINARY_FILE_NAME, config);
149
150 if (!payloadBinary.isPresent()) {
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -0700151 throw new PreparationFailedException(
152 "Failed to find " + PAYLOAD_BINARY_FILE_NAME + " in config");
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700153 }
154
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -0700155 if (!UpdateConfigs.getPropertyFile(PAYLOAD_PROPERTIES_FILE_NAME, config).isPresent()
156 || !Paths.get(OTA_PACKAGE_DIR, PAYLOAD_PROPERTIES_FILE_NAME).toFile().exists()) {
157 throw new IOException(PAYLOAD_PROPERTIES_FILE_NAME + " not found");
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700158 }
159
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -0700160 File compatibilityFile = Paths.get(OTA_PACKAGE_DIR, COMPATIBILITY_ZIP_FILE_NAME).toFile();
161 if (compatibilityFile.isFile()) {
162 Log.i(TAG, "Verifying OTA package for compatibility with the device");
163 if (!verifyPackageCompatibility(compatibilityFile)) {
164 throw new PreparationFailedException(
165 "OTA package is not compatible with this device");
166 }
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700167 }
168
Zhomart Mukhamejanovb0361ff2018-05-18 10:22:13 -0700169 return mPayloadSpecs.forStreaming(config.getUrl(),
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -0700170 payloadBinary.get().getOffset(),
171 payloadBinary.get().getSize(),
172 Paths.get(OTA_PACKAGE_DIR, PAYLOAD_PROPERTIES_FILE_NAME).toFile());
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700173 }
174
175 /**
Zhomart Mukhamejanov88712f72018-08-21 12:19:02 -0700176 * Downloads files defined in {@link UpdateConfig#getAbConfig()}
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700177 * and exists in {@code PRE_STREAMING_FILES_SET}, and put them
178 * in directory {@code dir}.
179 * @throws IOException when can't download a file
180 */
Zhomart Mukhamejanovb0361ff2018-05-18 10:22:13 -0700181 private void downloadPreStreamingFiles(UpdateConfig config, String dir)
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700182 throws IOException {
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -0700183 Log.d(TAG, "Deleting existing files from " + dir);
184 for (String file : PRE_STREAMING_FILES_SET) {
185 Files.deleteIfExists(Paths.get(OTA_PACKAGE_DIR, file));
186 }
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700187 Log.d(TAG, "Downloading files to " + dir);
Zhomart Mukhamejanov88712f72018-08-21 12:19:02 -0700188 for (UpdateConfig.PackageFile file : config.getAbConfig().getPropertyFiles()) {
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700189 if (PRE_STREAMING_FILES_SET.contains(file.getFilename())) {
190 Log.d(TAG, "Downloading file " + file.getFilename());
191 FileDownloader downloader = new FileDownloader(
192 config.getUrl(),
193 file.getOffset(),
194 file.getSize(),
195 Paths.get(dir, file.getFilename()).toFile());
196 downloader.download();
197 }
198 }
199 }
200
201 /**
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -0700202 * @param file physical location of {@link PackageFiles#COMPATIBILITY_ZIP_FILE_NAME}
203 * @return true if OTA package is compatible with this device
204 */
Zhomart Mukhamejanovb0361ff2018-05-18 10:22:13 -0700205 private boolean verifyPackageCompatibility(File file) {
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -0700206 try {
207 return RecoverySystem.verifyPackageCompatibility(file);
208 } catch (IOException e) {
209 Log.e(TAG, "Failed to verify package compatibility", e);
210 return false;
211 }
212 }
213
214 /**
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700215 * Used by {@link PrepareStreamingService} to pass {@link PayloadSpec}
216 * to {@link UpdateResultCallback#onReceiveResult}.
217 */
218 private static class CallbackResultReceiver extends ResultReceiver {
219
220 static Bundle createBundle(PayloadSpec payloadSpec) {
221 Bundle b = new Bundle();
222 b.putSerializable(BUNDLE_PARAM_PAYLOAD_SPEC, payloadSpec);
223 return b;
224 }
225
226 private static final String BUNDLE_PARAM_PAYLOAD_SPEC = "payload-spec";
227
228 private UpdateResultCallback mUpdateResultCallback;
229
230 CallbackResultReceiver(Handler handler, UpdateResultCallback updateResultCallback) {
231 super(handler);
232 this.mUpdateResultCallback = updateResultCallback;
233 }
234
235 @Override
236 protected void onReceiveResult(int resultCode, Bundle resultData) {
237 PayloadSpec payloadSpec = null;
238 if (resultCode == RESULT_CODE_SUCCESS) {
239 payloadSpec = (PayloadSpec) resultData.getSerializable(BUNDLE_PARAM_PAYLOAD_SPEC);
240 }
241 mUpdateResultCallback.onReceiveResult(resultCode, payloadSpec);
242 }
243 }
244
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -0700245 private static class PreparationFailedException extends Exception {
246 PreparationFailedException(String message) {
247 super(message);
248 }
249 }
250
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700251}