blob: 06581bee3de01c48016d815a962ade66c6e0dc36 [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;
Zhomart Mukhamejanovbc077752018-12-14 09:36:32 -080031import android.os.UpdateEngine;
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -070032import android.util.Log;
33
34import com.example.android.systemupdatersample.PayloadSpec;
35import com.example.android.systemupdatersample.UpdateConfig;
36import com.example.android.systemupdatersample.util.FileDownloader;
37import com.example.android.systemupdatersample.util.PackageFiles;
38import com.example.android.systemupdatersample.util.PayloadSpecs;
39import com.example.android.systemupdatersample.util.UpdateConfigs;
40import com.google.common.collect.ImmutableSet;
41
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -070042import java.io.File;
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -070043import java.io.IOException;
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -070044import java.nio.file.Files;
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -070045import java.nio.file.Paths;
46import java.util.Optional;
47
48/**
49 * This IntentService will download/extract the necessary files from the package zip
50 * without downloading the whole package. And it constructs {@link PayloadSpec}.
51 * All this work required to install streaming A/B updates.
52 *
Zhomart Mukhamejanovbc077752018-12-14 09:36:32 -080053 * PrepareUpdateService runs on it's own thread. It will notify activity
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -070054 * using interface {@link UpdateResultCallback} when update is ready to install.
55 */
Zhomart Mukhamejanovbc077752018-12-14 09:36:32 -080056public class PrepareUpdateService extends IntentService {
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -070057
58 /**
59 * UpdateResultCallback result codes.
60 */
61 public static final int RESULT_CODE_SUCCESS = 0;
62 public static final int RESULT_CODE_ERROR = 1;
63
64 /**
Zhomart Mukhamejanovbc077752018-12-14 09:36:32 -080065 * Extra params that will be sent to IntentService.
66 */
67 public static final String EXTRA_PARAM_CONFIG = "config";
68 public static final String EXTRA_PARAM_RESULT_RECEIVER = "result-receiver";
69
70 /**
71 * This interface is used to send results from {@link PrepareUpdateService} to
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -070072 * {@code MainActivity}.
73 */
74 public interface UpdateResultCallback {
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -070075 /**
76 * Invoked when files are downloaded and payload spec is constructed.
77 *
Zhomart Mukhamejanovbc077752018-12-14 09:36:32 -080078 * @param resultCode result code, values are defined in {@link PrepareUpdateService}
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -070079 * @param payloadSpec prepared payload spec for streaming update
80 */
81 void onReceiveResult(int resultCode, PayloadSpec payloadSpec);
82 }
83
84 /**
Zhomart Mukhamejanovbc077752018-12-14 09:36:32 -080085 * Starts PrepareUpdateService.
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -070086 *
Zhomart Mukhamejanovbc077752018-12-14 09:36:32 -080087 * @param context application context
88 * @param config update config
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -070089 * @param resultCallback callback that will be called when the update is ready to be installed
90 */
91 public static void startService(Context context,
92 UpdateConfig config,
Zhomart Mukhamejanovbc077752018-12-14 09:36:32 -080093 Handler handler,
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -070094 UpdateResultCallback resultCallback) {
Zhomart Mukhamejanovbc077752018-12-14 09:36:32 -080095 Log.d(TAG, "Starting PrepareUpdateService");
96 ResultReceiver receiver = new CallbackResultReceiver(handler, resultCallback);
97 Intent intent = new Intent(context, PrepareUpdateService.class);
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -070098 intent.putExtra(EXTRA_PARAM_CONFIG, config);
99 intent.putExtra(EXTRA_PARAM_RESULT_RECEIVER, receiver);
100 context.startService(intent);
101 }
102
Zhomart Mukhamejanovbc077752018-12-14 09:36:32 -0800103 public PrepareUpdateService() {
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700104 super(TAG);
105 }
106
Zhomart Mukhamejanovbc077752018-12-14 09:36:32 -0800107 private static final String TAG = "PrepareUpdateService";
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700108
109 /**
110 * The files that should be downloaded before streaming.
111 */
112 private static final ImmutableSet<String> PRE_STREAMING_FILES_SET =
113 ImmutableSet.of(
Zhomart Mukhamejanovbc077752018-12-14 09:36:32 -0800114 PackageFiles.CARE_MAP_FILE_NAME,
115 PackageFiles.COMPATIBILITY_ZIP_FILE_NAME,
116 PackageFiles.METADATA_FILE_NAME,
117 PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700118 );
119
Zhomart Mukhamejanovb0361ff2018-05-18 10:22:13 -0700120 private final PayloadSpecs mPayloadSpecs = new PayloadSpecs();
Zhomart Mukhamejanovbc077752018-12-14 09:36:32 -0800121 private final UpdateEngine mUpdateEngine = new UpdateEngine();
Zhomart Mukhamejanovb0361ff2018-05-18 10:22:13 -0700122
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700123 @Override
124 protected void onHandleIntent(Intent intent) {
125 Log.d(TAG, "On handle intent is called");
126 UpdateConfig config = intent.getParcelableExtra(EXTRA_PARAM_CONFIG);
127 ResultReceiver resultReceiver = intent.getParcelableExtra(EXTRA_PARAM_RESULT_RECEIVER);
128
129 try {
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -0700130 PayloadSpec spec = execute(config);
131 resultReceiver.send(RESULT_CODE_SUCCESS, CallbackResultReceiver.createBundle(spec));
132 } catch (Exception e) {
133 Log.e(TAG, "Failed to prepare streaming update", e);
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700134 resultReceiver.send(RESULT_CODE_ERROR, null);
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700135 }
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -0700136 }
137
138 /**
139 * 1. Downloads files for streaming updates.
140 * 2. Makes sure required files are present.
141 * 3. Checks OTA package compatibility with the device.
142 * 4. Constructs {@link PayloadSpec} for streaming update.
143 */
Zhomart Mukhamejanovb0361ff2018-05-18 10:22:13 -0700144 private PayloadSpec execute(UpdateConfig config)
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -0700145 throws IOException, PreparationFailedException {
146
Zhomart Mukhamejanovbc077752018-12-14 09:36:32 -0800147 if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING) {
148 return mPayloadSpecs.forNonStreaming(config.getUpdatePackageFile());
149 }
150
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -0700151 downloadPreStreamingFiles(config, OTA_PACKAGE_DIR);
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700152
153 Optional<UpdateConfig.PackageFile> payloadBinary =
154 UpdateConfigs.getPropertyFile(PAYLOAD_BINARY_FILE_NAME, config);
155
156 if (!payloadBinary.isPresent()) {
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -0700157 throw new PreparationFailedException(
158 "Failed to find " + PAYLOAD_BINARY_FILE_NAME + " in config");
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700159 }
160
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -0700161 if (!UpdateConfigs.getPropertyFile(PAYLOAD_PROPERTIES_FILE_NAME, config).isPresent()
162 || !Paths.get(OTA_PACKAGE_DIR, PAYLOAD_PROPERTIES_FILE_NAME).toFile().exists()) {
163 throw new IOException(PAYLOAD_PROPERTIES_FILE_NAME + " not found");
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700164 }
165
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -0700166 File compatibilityFile = Paths.get(OTA_PACKAGE_DIR, COMPATIBILITY_ZIP_FILE_NAME).toFile();
167 if (compatibilityFile.isFile()) {
168 Log.i(TAG, "Verifying OTA package for compatibility with the device");
169 if (!verifyPackageCompatibility(compatibilityFile)) {
170 throw new PreparationFailedException(
171 "OTA package is not compatible with this device");
172 }
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700173 }
174
Zhomart Mukhamejanovb0361ff2018-05-18 10:22:13 -0700175 return mPayloadSpecs.forStreaming(config.getUrl(),
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -0700176 payloadBinary.get().getOffset(),
177 payloadBinary.get().getSize(),
178 Paths.get(OTA_PACKAGE_DIR, PAYLOAD_PROPERTIES_FILE_NAME).toFile());
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700179 }
180
181 /**
Zhomart Mukhamejanov88712f72018-08-21 12:19:02 -0700182 * Downloads files defined in {@link UpdateConfig#getAbConfig()}
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700183 * and exists in {@code PRE_STREAMING_FILES_SET}, and put them
184 * in directory {@code dir}.
Zhomart Mukhamejanovbc077752018-12-14 09:36:32 -0800185 *
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700186 * @throws IOException when can't download a file
187 */
Zhomart Mukhamejanovb0361ff2018-05-18 10:22:13 -0700188 private void downloadPreStreamingFiles(UpdateConfig config, String dir)
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700189 throws IOException {
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -0700190 Log.d(TAG, "Deleting existing files from " + dir);
191 for (String file : PRE_STREAMING_FILES_SET) {
192 Files.deleteIfExists(Paths.get(OTA_PACKAGE_DIR, file));
193 }
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700194 Log.d(TAG, "Downloading files to " + dir);
Zhomart Mukhamejanov88712f72018-08-21 12:19:02 -0700195 for (UpdateConfig.PackageFile file : config.getAbConfig().getPropertyFiles()) {
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700196 if (PRE_STREAMING_FILES_SET.contains(file.getFilename())) {
197 Log.d(TAG, "Downloading file " + file.getFilename());
198 FileDownloader downloader = new FileDownloader(
199 config.getUrl(),
200 file.getOffset(),
201 file.getSize(),
202 Paths.get(dir, file.getFilename()).toFile());
203 downloader.download();
204 }
205 }
206 }
207
208 /**
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -0700209 * @param file physical location of {@link PackageFiles#COMPATIBILITY_ZIP_FILE_NAME}
210 * @return true if OTA package is compatible with this device
211 */
Zhomart Mukhamejanovb0361ff2018-05-18 10:22:13 -0700212 private boolean verifyPackageCompatibility(File file) {
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -0700213 try {
214 return RecoverySystem.verifyPackageCompatibility(file);
215 } catch (IOException e) {
216 Log.e(TAG, "Failed to verify package compatibility", e);
217 return false;
218 }
219 }
220
221 /**
Zhomart Mukhamejanovbc077752018-12-14 09:36:32 -0800222 * Used by {@link PrepareUpdateService} to pass {@link PayloadSpec}
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700223 * to {@link UpdateResultCallback#onReceiveResult}.
224 */
225 private static class CallbackResultReceiver extends ResultReceiver {
226
227 static Bundle createBundle(PayloadSpec payloadSpec) {
228 Bundle b = new Bundle();
229 b.putSerializable(BUNDLE_PARAM_PAYLOAD_SPEC, payloadSpec);
230 return b;
231 }
232
233 private static final String BUNDLE_PARAM_PAYLOAD_SPEC = "payload-spec";
234
235 private UpdateResultCallback mUpdateResultCallback;
236
237 CallbackResultReceiver(Handler handler, UpdateResultCallback updateResultCallback) {
238 super(handler);
239 this.mUpdateResultCallback = updateResultCallback;
240 }
241
242 @Override
243 protected void onReceiveResult(int resultCode, Bundle resultData) {
244 PayloadSpec payloadSpec = null;
245 if (resultCode == RESULT_CODE_SUCCESS) {
246 payloadSpec = (PayloadSpec) resultData.getSerializable(BUNDLE_PARAM_PAYLOAD_SPEC);
247 }
248 mUpdateResultCallback.onReceiveResult(resultCode, payloadSpec);
249 }
250 }
251
Zhomart Mukhamejanov46a51ac2018-05-09 09:53:45 -0700252 private static class PreparationFailedException extends Exception {
253 PreparationFailedException(String message) {
254 super(message);
255 }
256 }
257
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700258}