blob: 840a6d6070fc4b822fcc6cedb89690f07c2d3ae0 [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
19import static com.example.android.systemupdatersample.util.PackageFiles.OTA_PACKAGE_DIR;
20import static com.example.android.systemupdatersample.util.PackageFiles.PAYLOAD_BINARY_FILE_NAME;
21import static com.example.android.systemupdatersample.util.PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME;
22
23import android.app.IntentService;
24import android.content.Context;
25import android.content.Intent;
26import android.os.Bundle;
27import android.os.Handler;
28import android.os.ResultReceiver;
29import android.util.Log;
30
31import com.example.android.systemupdatersample.PayloadSpec;
32import com.example.android.systemupdatersample.UpdateConfig;
33import com.example.android.systemupdatersample.util.FileDownloader;
34import com.example.android.systemupdatersample.util.PackageFiles;
35import com.example.android.systemupdatersample.util.PayloadSpecs;
36import com.example.android.systemupdatersample.util.UpdateConfigs;
37import com.google.common.collect.ImmutableSet;
38
39import java.io.IOException;
40import java.nio.file.Paths;
41import java.util.Optional;
42
43/**
44 * This IntentService will download/extract the necessary files from the package zip
45 * without downloading the whole package. And it constructs {@link PayloadSpec}.
46 * All this work required to install streaming A/B updates.
47 *
48 * PrepareStreamingService runs on it's own thread. It will notify activity
49 * using interface {@link UpdateResultCallback} when update is ready to install.
50 */
51public class PrepareStreamingService extends IntentService {
52
53 /**
54 * UpdateResultCallback result codes.
55 */
56 public static final int RESULT_CODE_SUCCESS = 0;
57 public static final int RESULT_CODE_ERROR = 1;
58
59 /**
60 * This interface is used to send results from {@link PrepareStreamingService} to
61 * {@code MainActivity}.
62 */
63 public interface UpdateResultCallback {
64
65 /**
66 * Invoked when files are downloaded and payload spec is constructed.
67 *
68 * @param resultCode result code, values are defined in {@link PrepareStreamingService}
69 * @param payloadSpec prepared payload spec for streaming update
70 */
71 void onReceiveResult(int resultCode, PayloadSpec payloadSpec);
72 }
73
74 /**
75 * Starts PrepareStreamingService.
76 *
77 * @param context application context
78 * @param config update config
79 * @param resultCallback callback that will be called when the update is ready to be installed
80 */
81 public static void startService(Context context,
82 UpdateConfig config,
83 UpdateResultCallback resultCallback) {
84 Log.d(TAG, "Starting PrepareStreamingService");
85 ResultReceiver receiver = new CallbackResultReceiver(new Handler(), resultCallback);
86 Intent intent = new Intent(context, PrepareStreamingService.class);
87 intent.putExtra(EXTRA_PARAM_CONFIG, config);
88 intent.putExtra(EXTRA_PARAM_RESULT_RECEIVER, receiver);
89 context.startService(intent);
90 }
91
92 public PrepareStreamingService() {
93 super(TAG);
94 }
95
96 private static final String TAG = "PrepareStreamingService";
97
98 /**
99 * Extra params that will be sent from Activity to IntentService.
100 */
101 private static final String EXTRA_PARAM_CONFIG = "config";
102 private static final String EXTRA_PARAM_RESULT_RECEIVER = "result-receiver";
103
104 /**
105 * The files that should be downloaded before streaming.
106 */
107 private static final ImmutableSet<String> PRE_STREAMING_FILES_SET =
108 ImmutableSet.of(
109 PackageFiles.CARE_MAP_FILE_NAME,
110 PackageFiles.COMPATIBILITY_ZIP_FILE_NAME,
111 PackageFiles.METADATA_FILE_NAME,
112 PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME
113 );
114
115 @Override
116 protected void onHandleIntent(Intent intent) {
117 Log.d(TAG, "On handle intent is called");
118 UpdateConfig config = intent.getParcelableExtra(EXTRA_PARAM_CONFIG);
119 ResultReceiver resultReceiver = intent.getParcelableExtra(EXTRA_PARAM_RESULT_RECEIVER);
120
121 try {
122 downloadPreStreamingFiles(config, OTA_PACKAGE_DIR);
123 } catch (IOException e) {
124 Log.e(TAG, "Failed to download pre-streaming files", e);
125 resultReceiver.send(RESULT_CODE_ERROR, null);
126 return;
127 }
128
129 Optional<UpdateConfig.PackageFile> payloadBinary =
130 UpdateConfigs.getPropertyFile(PAYLOAD_BINARY_FILE_NAME, config);
131
132 if (!payloadBinary.isPresent()) {
133 Log.e(TAG, "Failed to find " + PAYLOAD_BINARY_FILE_NAME + " in config");
134 resultReceiver.send(RESULT_CODE_ERROR, null);
135 return;
136 }
137
138 Optional<UpdateConfig.PackageFile> properties =
139 UpdateConfigs.getPropertyFile(PAYLOAD_PROPERTIES_FILE_NAME, config);
140
141 if (!properties.isPresent()) {
142 Log.e(TAG, "Failed to find " + PAYLOAD_PROPERTIES_FILE_NAME + " in config");
143 resultReceiver.send(RESULT_CODE_ERROR, null);
144 return;
145 }
146
147 PayloadSpec spec;
148 try {
149 spec = PayloadSpecs.forStreaming(config.getUrl(),
150 payloadBinary.get().getOffset(),
151 payloadBinary.get().getSize(),
152 Paths.get(OTA_PACKAGE_DIR, properties.get().getFilename()).toFile()
153 );
154 } catch (IOException e) {
155 Log.e(TAG, "PayloadSpecs failed to create PayloadSpec for streaming", e);
156 resultReceiver.send(RESULT_CODE_ERROR, null);
157 return;
158 }
159
160 resultReceiver.send(RESULT_CODE_SUCCESS, CallbackResultReceiver.createBundle(spec));
161 }
162
163 /**
164 * Downloads files defined in {@link UpdateConfig#getStreamingMetadata()}
165 * and exists in {@code PRE_STREAMING_FILES_SET}, and put them
166 * in directory {@code dir}.
167 * @throws IOException when can't download a file
168 */
169 private static void downloadPreStreamingFiles(UpdateConfig config, String dir)
170 throws IOException {
171 Log.d(TAG, "Downloading files to " + dir);
172 for (UpdateConfig.PackageFile file : config.getStreamingMetadata().getPropertyFiles()) {
173 if (PRE_STREAMING_FILES_SET.contains(file.getFilename())) {
174 Log.d(TAG, "Downloading file " + file.getFilename());
175 FileDownloader downloader = new FileDownloader(
176 config.getUrl(),
177 file.getOffset(),
178 file.getSize(),
179 Paths.get(dir, file.getFilename()).toFile());
180 downloader.download();
181 }
182 }
183 }
184
185 /**
186 * Used by {@link PrepareStreamingService} to pass {@link PayloadSpec}
187 * to {@link UpdateResultCallback#onReceiveResult}.
188 */
189 private static class CallbackResultReceiver extends ResultReceiver {
190
191 static Bundle createBundle(PayloadSpec payloadSpec) {
192 Bundle b = new Bundle();
193 b.putSerializable(BUNDLE_PARAM_PAYLOAD_SPEC, payloadSpec);
194 return b;
195 }
196
197 private static final String BUNDLE_PARAM_PAYLOAD_SPEC = "payload-spec";
198
199 private UpdateResultCallback mUpdateResultCallback;
200
201 CallbackResultReceiver(Handler handler, UpdateResultCallback updateResultCallback) {
202 super(handler);
203 this.mUpdateResultCallback = updateResultCallback;
204 }
205
206 @Override
207 protected void onReceiveResult(int resultCode, Bundle resultData) {
208 PayloadSpec payloadSpec = null;
209 if (resultCode == RESULT_CODE_SUCCESS) {
210 payloadSpec = (PayloadSpec) resultData.getSerializable(BUNDLE_PARAM_PAYLOAD_SPEC);
211 }
212 mUpdateResultCallback.onReceiveResult(resultCode, payloadSpec);
213 }
214 }
215
216}