blob: a9783e70a47ec1910c73832cfce682f295dde50e [file] [log] [blame]
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -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;
18
19import android.content.Context;
20import android.os.UpdateEngine;
21import android.os.UpdateEngineCallback;
22import android.util.Log;
23
24import com.example.android.systemupdatersample.services.PrepareStreamingService;
25import com.example.android.systemupdatersample.util.PayloadSpecs;
26import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes;
27import com.example.android.systemupdatersample.util.UpdateEngineProperties;
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -070028import com.google.common.base.Preconditions;
29import com.google.common.collect.ImmutableList;
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070030import com.google.common.util.concurrent.AtomicDouble;
31
32import java.io.IOException;
33import java.util.ArrayList;
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -070034import java.util.Collections;
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070035import java.util.List;
36import java.util.Optional;
37import java.util.concurrent.atomic.AtomicBoolean;
38import java.util.concurrent.atomic.AtomicInteger;
39import java.util.function.DoubleConsumer;
40import java.util.function.IntConsumer;
41
Zhomart Mukhamejanovf6522eb2018-05-24 09:11:47 -070042import javax.annotation.concurrent.GuardedBy;
43
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070044/**
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -070045 * Manages the update flow. It has its own state (in memory), separate from
46 * {@link UpdateEngine}'s state. Asynchronously interacts with the {@link UpdateEngine}.
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070047 */
48public class UpdateManager {
49
50 private static final String TAG = "UpdateManager";
51
52 /** HTTP Header: User-Agent; it will be sent to the server when streaming the payload. */
53 private static final String HTTP_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
54 + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36";
55
56 private final UpdateEngine mUpdateEngine;
57 private final PayloadSpecs mPayloadSpecs;
58
59 private AtomicInteger mUpdateEngineStatus =
60 new AtomicInteger(UpdateEngine.UpdateStatusConstants.IDLE);
61 private AtomicInteger mEngineErrorCode = new AtomicInteger(UpdateEngineErrorCodes.UNKNOWN);
62 private AtomicDouble mProgress = new AtomicDouble(0);
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -070063 private UpdaterState mUpdaterState = new UpdaterState(UpdaterState.IDLE);
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -070064
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070065 private AtomicBoolean mManualSwitchSlotRequired = new AtomicBoolean(true);
66
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -070067 /** Synchronize state with engine status only once when app binds to UpdateEngine. */
68 private AtomicBoolean mStateSynchronized = new AtomicBoolean(false);
Zhomart Mukhamejanov51cde1e2018-05-31 12:19:41 -070069
Zhomart Mukhamejanovf6522eb2018-05-24 09:11:47 -070070 @GuardedBy("mLock")
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -070071 private UpdateData mLastUpdateData = null;
72
Zhomart Mukhamejanovf6522eb2018-05-24 09:11:47 -070073 @GuardedBy("mLock")
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -070074 private IntConsumer mOnStateChangeCallback = null;
Zhomart Mukhamejanovf6522eb2018-05-24 09:11:47 -070075 @GuardedBy("mLock")
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070076 private IntConsumer mOnEngineStatusUpdateCallback = null;
Zhomart Mukhamejanovf6522eb2018-05-24 09:11:47 -070077 @GuardedBy("mLock")
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070078 private DoubleConsumer mOnProgressUpdateCallback = null;
Zhomart Mukhamejanovf6522eb2018-05-24 09:11:47 -070079 @GuardedBy("mLock")
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070080 private IntConsumer mOnEngineCompleteCallback = null;
81
82 private final Object mLock = new Object();
83
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -070084 private final UpdateManager.UpdateEngineCallbackImpl
85 mUpdateEngineCallback = new UpdateManager.UpdateEngineCallbackImpl();
86
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070087 public UpdateManager(UpdateEngine updateEngine, PayloadSpecs payloadSpecs) {
88 this.mUpdateEngine = updateEngine;
89 this.mPayloadSpecs = payloadSpecs;
90 }
91
92 /**
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -070093 * Binds to {@link UpdateEngine}. Invokes onStateChangeCallback if present.
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070094 */
95 public void bind() {
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -070096 getOnStateChangeCallback().ifPresent(callback -> callback.accept(mUpdaterState.get()));
97
98 mStateSynchronized.set(false);
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070099 this.mUpdateEngine.bind(mUpdateEngineCallback);
100 }
101
102 /**
103 * Unbinds from {@link UpdateEngine}.
104 */
105 public void unbind() {
106 this.mUpdateEngine.unbind();
107 }
108
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700109 public int getUpdaterState() {
110 return mUpdaterState.get();
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700111 }
112
113 /**
114 * Returns true if manual switching slot is required. Value depends on
115 * the update config {@code ab_config.force_switch_slot}.
116 */
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700117 public boolean isManualSwitchSlotRequired() {
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700118 return mManualSwitchSlotRequired.get();
119 }
120
121 /**
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700122 * Sets SystemUpdaterSample app state change callback. Value of {@code state} will be one
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700123 * of the values from {@link UpdaterState}.
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700124 *
125 * @param onStateChangeCallback a callback with parameter {@code state}.
126 */
127 public void setOnStateChangeCallback(IntConsumer onStateChangeCallback) {
128 synchronized (mLock) {
129 this.mOnStateChangeCallback = onStateChangeCallback;
130 }
131 }
132
133 private Optional<IntConsumer> getOnStateChangeCallback() {
134 synchronized (mLock) {
135 return mOnStateChangeCallback == null
136 ? Optional.empty()
137 : Optional.of(mOnStateChangeCallback);
138 }
139 }
140
141 /**
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700142 * Sets update engine status update callback. Value of {@code status} will
143 * be one of the values from {@link UpdateEngine.UpdateStatusConstants}.
144 *
145 * @param onStatusUpdateCallback a callback with parameter {@code status}.
146 */
147 public void setOnEngineStatusUpdateCallback(IntConsumer onStatusUpdateCallback) {
148 synchronized (mLock) {
149 this.mOnEngineStatusUpdateCallback = onStatusUpdateCallback;
150 }
151 }
152
153 private Optional<IntConsumer> getOnEngineStatusUpdateCallback() {
154 synchronized (mLock) {
155 return mOnEngineStatusUpdateCallback == null
156 ? Optional.empty()
157 : Optional.of(mOnEngineStatusUpdateCallback);
158 }
159 }
160
161 /**
162 * Sets update engine payload application complete callback. Value of {@code errorCode} will
163 * be one of the values from {@link UpdateEngine.ErrorCodeConstants}.
164 *
165 * @param onComplete a callback with parameter {@code errorCode}.
166 */
167 public void setOnEngineCompleteCallback(IntConsumer onComplete) {
168 synchronized (mLock) {
169 this.mOnEngineCompleteCallback = onComplete;
170 }
171 }
172
173 private Optional<IntConsumer> getOnEngineCompleteCallback() {
174 synchronized (mLock) {
175 return mOnEngineCompleteCallback == null
176 ? Optional.empty()
177 : Optional.of(mOnEngineCompleteCallback);
178 }
179 }
180
181 /**
182 * Sets progress update callback. Progress is a number from {@code 0.0} to {@code 1.0}.
183 *
184 * @param onProgressCallback a callback with parameter {@code progress}.
185 */
186 public void setOnProgressUpdateCallback(DoubleConsumer onProgressCallback) {
187 synchronized (mLock) {
188 this.mOnProgressUpdateCallback = onProgressCallback;
189 }
190 }
191
192 private Optional<DoubleConsumer> getOnProgressUpdateCallback() {
193 synchronized (mLock) {
194 return mOnProgressUpdateCallback == null
195 ? Optional.empty()
196 : Optional.of(mOnProgressUpdateCallback);
197 }
198 }
199
200 /**
Zhomart Mukhamejanov16db9942018-05-31 10:47:09 -0700201 * Suspend running update.
202 */
203 public synchronized void suspend() throws UpdaterState.InvalidTransitionException {
204 Log.d(TAG, "suspend invoked");
205 setUpdaterState(UpdaterState.PAUSED);
206 mUpdateEngine.cancel();
207 }
208
209 /**
210 * Resume suspended update.
211 */
212 public synchronized void resume() throws UpdaterState.InvalidTransitionException {
213 Log.d(TAG, "resume invoked");
214 setUpdaterState(UpdaterState.RUNNING);
215 updateEngineReApplyPayload();
216 }
217
218 /**
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700219 * Updates {@link this.mState} and if state is changed,
220 * it also notifies {@link this.mOnStateChangeCallback}.
221 */
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700222 private void setUpdaterState(int newUpdaterState)
223 throws UpdaterState.InvalidTransitionException {
224 Log.d(TAG, "setUpdaterState invoked newState=" + newUpdaterState);
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700225 int previousState = mUpdaterState.get();
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700226 mUpdaterState.set(newUpdaterState);
227 if (previousState != newUpdaterState) {
228 getOnStateChangeCallback().ifPresent(callback -> callback.accept(newUpdaterState));
229 }
230 }
231
232 /**
233 * Same as {@link this.setUpdaterState}. Logs the error if new state
234 * cannot be set.
235 */
236 private void setUpdaterStateSilent(int newUpdaterState) {
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700237 try {
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700238 setUpdaterState(newUpdaterState);
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700239 } catch (UpdaterState.InvalidTransitionException e) {
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700240 // Most likely UpdateEngine status and UpdaterSample state got de-synchronized.
241 // To make sample app simple, we don't handle it properly.
242 Log.e(TAG, "Failed to set updater state", e);
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700243 }
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700244 }
245
246 /**
247 * Creates new UpdaterState, assigns it to {@link this.mUpdaterState},
248 * and notifies callbacks.
249 */
250 private void initializeUpdateState(int state) {
251 this.mUpdaterState = new UpdaterState(state);
252 getOnStateChangeCallback().ifPresent(callback -> callback.accept(state));
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700253 }
254
255 /**
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700256 * Requests update engine to stop any ongoing update. If an update has been applied,
257 * leave it as is.
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700258 */
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700259 public synchronized void cancelRunningUpdate() throws UpdaterState.InvalidTransitionException {
260 Log.d(TAG, "cancelRunningUpdate invoked");
261 setUpdaterState(UpdaterState.IDLE);
262 mUpdateEngine.cancel();
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700263 }
264
265 /**
266 * Resets update engine to IDLE state. If an update has been applied it reverts it.
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700267 */
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700268 public synchronized void resetUpdate() throws UpdaterState.InvalidTransitionException {
269 Log.d(TAG, "resetUpdate invoked");
270 setUpdaterState(UpdaterState.IDLE);
271 mUpdateEngine.resetStatus();
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700272 }
273
274 /**
275 * Applies the given update.
276 *
277 * <p>UpdateEngine works asynchronously. This method doesn't wait until
278 * end of the update.</p>
279 */
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700280 public synchronized void applyUpdate(Context context, UpdateConfig config)
281 throws UpdaterState.InvalidTransitionException {
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700282 mEngineErrorCode.set(UpdateEngineErrorCodes.UNKNOWN);
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700283 setUpdaterState(UpdaterState.RUNNING);
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700284
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700285 synchronized (mLock) {
286 // Cleaning up previous update data.
287 mLastUpdateData = null;
288 }
289
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700290 if (!config.getAbConfig().getForceSwitchSlot()) {
291 mManualSwitchSlotRequired.set(true);
292 } else {
293 mManualSwitchSlotRequired.set(false);
294 }
295
296 if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING) {
297 applyAbNonStreamingUpdate(config);
298 } else {
299 applyAbStreamingUpdate(context, config);
300 }
301 }
302
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700303 private void applyAbNonStreamingUpdate(UpdateConfig config)
304 throws UpdaterState.InvalidTransitionException {
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700305 UpdateData.Builder builder = UpdateData.builder()
306 .setExtraProperties(prepareExtraProperties(config));
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700307
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700308 try {
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700309 builder.setPayload(mPayloadSpecs.forNonStreaming(config.getUpdatePackageFile()));
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700310 } catch (IOException e) {
311 Log.e(TAG, "Error creating payload spec", e);
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700312 setUpdaterState(UpdaterState.ERROR);
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700313 return;
314 }
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700315 updateEngineApplyPayload(builder.build());
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700316 }
317
318 private void applyAbStreamingUpdate(Context context, UpdateConfig config) {
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700319 UpdateData.Builder builder = UpdateData.builder()
320 .setExtraProperties(prepareExtraProperties(config));
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700321
322 Log.d(TAG, "Starting PrepareStreamingService");
323 PrepareStreamingService.startService(context, config, (code, payloadSpec) -> {
324 if (code == PrepareStreamingService.RESULT_CODE_SUCCESS) {
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700325 builder.setPayload(payloadSpec);
326 builder.addExtraProperty("USER_AGENT=" + HTTP_USER_AGENT);
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700327 config.getStreamingMetadata()
328 .getAuthorization()
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700329 .ifPresent(s -> builder.addExtraProperty("AUTHORIZATION=" + s));
330 updateEngineApplyPayload(builder.build());
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700331 } else {
332 Log.e(TAG, "PrepareStreamingService failed, result code is " + code);
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700333 setUpdaterStateSilent(UpdaterState.ERROR);
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700334 }
335 });
336 }
337
338 private List<String> prepareExtraProperties(UpdateConfig config) {
339 List<String> extraProperties = new ArrayList<>();
340
341 if (!config.getAbConfig().getForceSwitchSlot()) {
342 // Disable switch slot on reboot, which is enabled by default.
343 // User will enable it manually by clicking "Switch Slot" button on the screen.
344 extraProperties.add(UpdateEngineProperties.PROPERTY_DISABLE_SWITCH_SLOT_ON_REBOOT);
345 }
346 return extraProperties;
347 }
348
349 /**
350 * Applies given payload.
351 *
352 * <p>UpdateEngine works asynchronously. This method doesn't wait until
353 * end of the update.</p>
354 *
355 * <p>It's possible that the update engine throws a generic error, such as upon seeing invalid
356 * payload properties (which come from OTA packages), or failing to set up the network
357 * with the given id.</p>
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700358 */
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700359 private void updateEngineApplyPayload(UpdateData update) {
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700360 Log.d(TAG, "updateEngineApplyPayload invoked with url " + update.mPayload.getUrl());
361
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700362 synchronized (mLock) {
363 mLastUpdateData = update;
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700364 }
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700365
366 ArrayList<String> properties = new ArrayList<>(update.getPayload().getProperties());
367 properties.addAll(update.getExtraProperties());
368
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700369 try {
370 mUpdateEngine.applyPayload(
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700371 update.getPayload().getUrl(),
372 update.getPayload().getOffset(),
373 update.getPayload().getSize(),
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700374 properties.toArray(new String[0]));
375 } catch (Exception e) {
376 Log.e(TAG, "UpdateEngine failed to apply the update", e);
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700377 setUpdaterStateSilent(UpdaterState.ERROR);
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700378 }
379 }
380
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700381 /**
382 * Re-applies {@link this.mLastUpdateData} to update_engine.
383 */
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700384 private void updateEngineReApplyPayload() {
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700385 Log.d(TAG, "updateEngineReApplyPayload invoked");
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700386 UpdateData lastUpdate;
387 synchronized (mLock) {
388 // mLastPayloadSpec might be empty in some cases.
389 // But to make this sample app simple, we will not handle it.
390 Preconditions.checkArgument(
391 mLastUpdateData != null,
392 "mLastUpdateData must be present.");
393 lastUpdate = mLastUpdateData;
394 }
395 updateEngineApplyPayload(lastUpdate);
396 }
397
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700398 /**
399 * Sets the new slot that has the updated partitions as the active slot,
400 * which device will boot into next time.
401 * This method is only supposed to be called after the payload is applied.
402 *
403 * Invoking {@link UpdateEngine#applyPayload} with the same payload url, offset, size
404 * and payload metadata headers doesn't trigger new update. It can be used to just switch
405 * active A/B slot.
406 *
407 * {@link UpdateEngine#applyPayload} might take several seconds to finish, and it will
408 * invoke callbacks {@link this#onStatusUpdate} and {@link this#onPayloadApplicationComplete)}.
409 */
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700410 public synchronized void setSwitchSlotOnReboot() {
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700411 Log.d(TAG, "setSwitchSlotOnReboot invoked");
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700412
413 // When mManualSwitchSlotRequired set false, next time
414 // onApplicationPayloadComplete is called,
415 // it will set updater state to REBOOT_REQUIRED.
416 mManualSwitchSlotRequired.set(false);
417
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700418 UpdateData.Builder builder;
419 synchronized (mLock) {
420 // To make sample app simple, we don't handle it.
421 Preconditions.checkArgument(
422 mLastUpdateData != null,
423 "mLastUpdateData must be present.");
424 builder = mLastUpdateData.toBuilder();
425 }
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700426 // PROPERTY_SKIP_POST_INSTALL should be passed on to skip post-installation hooks.
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700427 builder.setExtraProperties(
428 Collections.singletonList(UpdateEngineProperties.PROPERTY_SKIP_POST_INSTALL));
429 // UpdateEngine sets property SWITCH_SLOT_ON_REBOOT=1 by default.
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700430 // HTTP headers are not required, UpdateEngine is not expected to stream payload.
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700431 updateEngineApplyPayload(builder.build());
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700432 }
433
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700434 /**
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700435 * Synchronize UpdaterState with UpdateEngine status.
436 * Apply necessary UpdateEngine operation if status are out of sync.
437 *
438 * It's expected to be called once when sample app binds itself to UpdateEngine.
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700439 */
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700440 private void synchronizeUpdaterStateWithUpdateEngineStatus() {
441 Log.d(TAG, "synchronizeUpdaterStateWithUpdateEngineStatus is invoked.");
442
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700443 int state = mUpdaterState.get();
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700444 int engineStatus = mUpdateEngineStatus.get();
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700445
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700446 if (engineStatus == UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT) {
447 // If update has been installed before running the sample app,
448 // set state to REBOOT_REQUIRED.
449 initializeUpdateState(UpdaterState.REBOOT_REQUIRED);
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700450 return;
451 }
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700452
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700453 switch (state) {
454 case UpdaterState.IDLE:
455 case UpdaterState.ERROR:
456 case UpdaterState.PAUSED:
457 case UpdaterState.SLOT_SWITCH_REQUIRED:
458 // It might happen when update is started not from the sample app.
459 // To make the sample app simple, we won't handle this case.
460 Preconditions.checkState(
461 engineStatus == UpdateEngine.UpdateStatusConstants.IDLE,
462 "When mUpdaterState is %s, mUpdateEngineStatus "
463 + "must be 0/IDLE, but it is %s",
464 state,
465 engineStatus);
466 break;
467 case UpdaterState.RUNNING:
468 if (engineStatus == UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT
469 || engineStatus == UpdateEngine.UpdateStatusConstants.IDLE) {
470 Log.i(TAG, "ensureUpdateEngineStatusIsRunning - re-applying last payload");
471 // Re-apply latest update. It makes update_engine to invoke
472 // onPayloadApplicationComplete callback. The callback notifies
473 // if update was successful or not.
474 updateEngineReApplyPayload();
475 }
476 break;
477 case UpdaterState.REBOOT_REQUIRED:
478 // This might happen when update is installed by other means,
479 // and sample app is not aware of it.
480 // To make the sample app simple, we won't handle this case.
481 Preconditions.checkState(
482 engineStatus == UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT,
483 "When mUpdaterState is %s, mUpdateEngineStatus "
484 + "must be 6/UPDATED_NEED_REBOOT, but it is %s",
485 state,
486 engineStatus);
487 break;
488 default:
489 throw new IllegalStateException("This block should not be reached.");
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700490 }
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700491 }
492
493 /**
494 * Invoked by update_engine whenever update status or progress changes.
495 * It's also guaranteed to be invoked when app binds to the update_engine, except
496 * when update_engine fails to initialize (as defined in
497 * system/update_engine/binder_service_android.cc in
498 * function BinderUpdateEngineAndroidService::bind).
499 *
500 * @param status one of {@link UpdateEngine.UpdateStatusConstants}.
501 * @param progress a number from 0.0 to 1.0.
502 */
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700503 private void onStatusUpdate(int status, float progress) {
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700504 Log.d(TAG, String.format(
505 "onStatusUpdate invoked, status=%s, progress=%.2f",
506 status,
507 progress));
508
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700509 int previousStatus = mUpdateEngineStatus.get();
510 mUpdateEngineStatus.set(status);
511 mProgress.set(progress);
512
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700513 if (!mStateSynchronized.getAndSet(true)) {
514 // We synchronize state with engine status once
515 // only when sample app is bound to UpdateEngine.
516 synchronizeUpdaterStateWithUpdateEngineStatus();
Zhomart Mukhamejanov51cde1e2018-05-31 12:19:41 -0700517 }
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700518
Zhomart Mukhamejanov16db9942018-05-31 10:47:09 -0700519 getOnProgressUpdateCallback().ifPresent(callback -> callback.accept(mProgress.get()));
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700520
521 if (previousStatus != status) {
522 getOnEngineStatusUpdateCallback().ifPresent(callback -> callback.accept(status));
523 }
524 }
525
526 private void onPayloadApplicationComplete(int errorCode) {
527 Log.d(TAG, "onPayloadApplicationComplete invoked, errorCode=" + errorCode);
528 mEngineErrorCode.set(errorCode);
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700529 if (errorCode == UpdateEngine.ErrorCodeConstants.SUCCESS
530 || errorCode == UpdateEngineErrorCodes.UPDATED_BUT_NOT_ACTIVE) {
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700531 setUpdaterStateSilent(isManualSwitchSlotRequired()
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700532 ? UpdaterState.SLOT_SWITCH_REQUIRED
533 : UpdaterState.REBOOT_REQUIRED);
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700534 } else if (errorCode != UpdateEngineErrorCodes.USER_CANCELLED) {
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700535 setUpdaterStateSilent(UpdaterState.ERROR);
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700536 }
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700537
538 getOnEngineCompleteCallback()
539 .ifPresent(callback -> callback.accept(errorCode));
540 }
541
542 /**
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700543 * Helper class to delegate {@code update_engine} callback invocations to UpdateManager.
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700544 */
545 class UpdateEngineCallbackImpl extends UpdateEngineCallback {
546 @Override
547 public void onStatusUpdate(int status, float percent) {
548 UpdateManager.this.onStatusUpdate(status, percent);
549 }
550
551 @Override
552 public void onPayloadApplicationComplete(int errorCode) {
553 UpdateManager.this.onPayloadApplicationComplete(errorCode);
554 }
555 }
556
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700557 /**
558 *
559 * Contains update data - PayloadSpec and extra properties list.
560 *
561 * <p>{@code mPayload} contains url, offset and size to {@code PAYLOAD_BINARY_FILE_NAME}.
562 * {@code mExtraProperties} is a list of additional properties to pass to
563 * {@link UpdateEngine#applyPayload}.</p>
564 */
565 private static class UpdateData {
566 private final PayloadSpec mPayload;
567 private final ImmutableList<String> mExtraProperties;
568
569 public static Builder builder() {
570 return new Builder();
571 }
572
573 UpdateData(Builder builder) {
574 this.mPayload = builder.mPayload;
575 this.mExtraProperties = ImmutableList.copyOf(builder.mExtraProperties);
576 }
577
578 public PayloadSpec getPayload() {
579 return mPayload;
580 }
581
582 public ImmutableList<String> getExtraProperties() {
583 return mExtraProperties;
584 }
585
586 public Builder toBuilder() {
587 return builder()
588 .setPayload(mPayload)
589 .setExtraProperties(mExtraProperties);
590 }
591
592 static class Builder {
593 private PayloadSpec mPayload;
594 private List<String> mExtraProperties;
595
596 public Builder setPayload(PayloadSpec payload) {
597 this.mPayload = payload;
598 return this;
599 }
600
601 public Builder setExtraProperties(List<String> extraProperties) {
602 this.mExtraProperties = new ArrayList<>(extraProperties);
603 return this;
604 }
605
606 public Builder addExtraProperty(String property) {
607 if (this.mExtraProperties == null) {
608 this.mExtraProperties = new ArrayList<>();
609 }
610 this.mExtraProperties.add(property);
611 return this;
612 }
613
614 public UpdateData build() {
615 return new UpdateData(this);
616 }
617 }
618 }
619
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700620}