blob: 145cc83b1f4a1150bd575e5f786e53cd583da658 [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 Mukhamejanovf6522eb2018-05-24 09:11:47 -070067 @GuardedBy("mLock")
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -070068 private UpdateData mLastUpdateData = null;
69
Zhomart Mukhamejanovf6522eb2018-05-24 09:11:47 -070070 @GuardedBy("mLock")
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -070071 private IntConsumer mOnStateChangeCallback = null;
Zhomart Mukhamejanovf6522eb2018-05-24 09:11:47 -070072 @GuardedBy("mLock")
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070073 private IntConsumer mOnEngineStatusUpdateCallback = null;
Zhomart Mukhamejanovf6522eb2018-05-24 09:11:47 -070074 @GuardedBy("mLock")
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070075 private DoubleConsumer mOnProgressUpdateCallback = null;
Zhomart Mukhamejanovf6522eb2018-05-24 09:11:47 -070076 @GuardedBy("mLock")
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070077 private IntConsumer mOnEngineCompleteCallback = null;
78
79 private final Object mLock = new Object();
80
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -070081 private final UpdateManager.UpdateEngineCallbackImpl
82 mUpdateEngineCallback = new UpdateManager.UpdateEngineCallbackImpl();
83
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070084 public UpdateManager(UpdateEngine updateEngine, PayloadSpecs payloadSpecs) {
85 this.mUpdateEngine = updateEngine;
86 this.mPayloadSpecs = payloadSpecs;
87 }
88
89 /**
90 * Binds to {@link UpdateEngine}.
91 */
92 public void bind() {
93 this.mUpdateEngine.bind(mUpdateEngineCallback);
94 }
95
96 /**
97 * Unbinds from {@link UpdateEngine}.
98 */
99 public void unbind() {
100 this.mUpdateEngine.unbind();
101 }
102
103 /**
104 * @return a number from {@code 0.0} to {@code 1.0}.
105 */
106 public float getProgress() {
107 return (float) this.mProgress.get();
108 }
109
110 /**
111 * Returns true if manual switching slot is required. Value depends on
112 * the update config {@code ab_config.force_switch_slot}.
113 */
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700114 public boolean isManualSwitchSlotRequired() {
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700115 return mManualSwitchSlotRequired.get();
116 }
117
118 /**
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700119 * Sets SystemUpdaterSample app state change callback. Value of {@code state} will be one
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700120 * of the values from {@link UpdaterState}.
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700121 *
122 * @param onStateChangeCallback a callback with parameter {@code state}.
123 */
124 public void setOnStateChangeCallback(IntConsumer onStateChangeCallback) {
125 synchronized (mLock) {
126 this.mOnStateChangeCallback = onStateChangeCallback;
127 }
128 }
129
130 private Optional<IntConsumer> getOnStateChangeCallback() {
131 synchronized (mLock) {
132 return mOnStateChangeCallback == null
133 ? Optional.empty()
134 : Optional.of(mOnStateChangeCallback);
135 }
136 }
137
138 /**
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700139 * Sets update engine status update callback. Value of {@code status} will
140 * be one of the values from {@link UpdateEngine.UpdateStatusConstants}.
141 *
142 * @param onStatusUpdateCallback a callback with parameter {@code status}.
143 */
144 public void setOnEngineStatusUpdateCallback(IntConsumer onStatusUpdateCallback) {
145 synchronized (mLock) {
146 this.mOnEngineStatusUpdateCallback = onStatusUpdateCallback;
147 }
148 }
149
150 private Optional<IntConsumer> getOnEngineStatusUpdateCallback() {
151 synchronized (mLock) {
152 return mOnEngineStatusUpdateCallback == null
153 ? Optional.empty()
154 : Optional.of(mOnEngineStatusUpdateCallback);
155 }
156 }
157
158 /**
159 * Sets update engine payload application complete callback. Value of {@code errorCode} will
160 * be one of the values from {@link UpdateEngine.ErrorCodeConstants}.
161 *
162 * @param onComplete a callback with parameter {@code errorCode}.
163 */
164 public void setOnEngineCompleteCallback(IntConsumer onComplete) {
165 synchronized (mLock) {
166 this.mOnEngineCompleteCallback = onComplete;
167 }
168 }
169
170 private Optional<IntConsumer> getOnEngineCompleteCallback() {
171 synchronized (mLock) {
172 return mOnEngineCompleteCallback == null
173 ? Optional.empty()
174 : Optional.of(mOnEngineCompleteCallback);
175 }
176 }
177
178 /**
179 * Sets progress update callback. Progress is a number from {@code 0.0} to {@code 1.0}.
180 *
181 * @param onProgressCallback a callback with parameter {@code progress}.
182 */
183 public void setOnProgressUpdateCallback(DoubleConsumer onProgressCallback) {
184 synchronized (mLock) {
185 this.mOnProgressUpdateCallback = onProgressCallback;
186 }
187 }
188
189 private Optional<DoubleConsumer> getOnProgressUpdateCallback() {
190 synchronized (mLock) {
191 return mOnProgressUpdateCallback == null
192 ? Optional.empty()
193 : Optional.of(mOnProgressUpdateCallback);
194 }
195 }
196
197 /**
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700198 * Updates {@link this.mState} and if state is changed,
199 * it also notifies {@link this.mOnStateChangeCallback}.
200 */
201 private void setUpdaterState(int updaterState) {
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700202 int previousState = mUpdaterState.get();
203 try {
204 mUpdaterState.set(updaterState);
205 } catch (UpdaterState.InvalidTransitionException e) {
206 // Note: invalid state transitions should be handled properly,
207 // but to make sample app simple, we just throw runtime exception.
208 throw new RuntimeException("Can't set state " + updaterState, e);
209 }
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700210 if (previousState != updaterState) {
211 getOnStateChangeCallback().ifPresent(callback -> callback.accept(updaterState));
212 }
213 }
214
215 /**
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700216 * Requests update engine to stop any ongoing update. If an update has been applied,
217 * leave it as is.
218 *
219 * <p>Sometimes it's possible that the
220 * update engine would throw an error when the method is called, and the only way to
221 * handle it is to catch the exception.</p>
222 */
223 public void cancelRunningUpdate() {
224 try {
225 mUpdateEngine.cancel();
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700226 setUpdaterState(UpdaterState.IDLE);
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700227 } catch (Exception e) {
228 Log.w(TAG, "UpdateEngine failed to stop the ongoing update", e);
229 }
230 }
231
232 /**
233 * Resets update engine to IDLE state. If an update has been applied it reverts it.
234 *
235 * <p>Sometimes it's possible that the
236 * update engine would throw an error when the method is called, and the only way to
237 * handle it is to catch the exception.</p>
238 */
239 public void resetUpdate() {
240 try {
241 mUpdateEngine.resetStatus();
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700242 setUpdaterState(UpdaterState.IDLE);
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700243 } catch (Exception e) {
244 Log.w(TAG, "UpdateEngine failed to reset the update", e);
245 }
246 }
247
248 /**
249 * Applies the given update.
250 *
251 * <p>UpdateEngine works asynchronously. This method doesn't wait until
252 * end of the update.</p>
253 */
254 public void applyUpdate(Context context, UpdateConfig config) {
255 mEngineErrorCode.set(UpdateEngineErrorCodes.UNKNOWN);
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700256 setUpdaterState(UpdaterState.RUNNING);
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700257
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700258 synchronized (mLock) {
259 // Cleaning up previous update data.
260 mLastUpdateData = null;
261 }
262
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700263 if (!config.getAbConfig().getForceSwitchSlot()) {
264 mManualSwitchSlotRequired.set(true);
265 } else {
266 mManualSwitchSlotRequired.set(false);
267 }
268
269 if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING) {
270 applyAbNonStreamingUpdate(config);
271 } else {
272 applyAbStreamingUpdate(context, config);
273 }
274 }
275
276 private void applyAbNonStreamingUpdate(UpdateConfig config) {
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700277 UpdateData.Builder builder = UpdateData.builder()
278 .setExtraProperties(prepareExtraProperties(config));
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700279
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700280 try {
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700281 builder.setPayload(mPayloadSpecs.forNonStreaming(config.getUpdatePackageFile()));
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700282 } catch (IOException e) {
283 Log.e(TAG, "Error creating payload spec", e);
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700284 setUpdaterState(UpdaterState.ERROR);
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700285 return;
286 }
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700287 updateEngineApplyPayload(builder.build());
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700288 }
289
290 private void applyAbStreamingUpdate(Context context, UpdateConfig config) {
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700291 UpdateData.Builder builder = UpdateData.builder()
292 .setExtraProperties(prepareExtraProperties(config));
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700293
294 Log.d(TAG, "Starting PrepareStreamingService");
295 PrepareStreamingService.startService(context, config, (code, payloadSpec) -> {
296 if (code == PrepareStreamingService.RESULT_CODE_SUCCESS) {
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700297 builder.setPayload(payloadSpec);
298 builder.addExtraProperty("USER_AGENT=" + HTTP_USER_AGENT);
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700299 config.getStreamingMetadata()
300 .getAuthorization()
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700301 .ifPresent(s -> builder.addExtraProperty("AUTHORIZATION=" + s));
302 updateEngineApplyPayload(builder.build());
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700303 } else {
304 Log.e(TAG, "PrepareStreamingService failed, result code is " + code);
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700305 setUpdaterState(UpdaterState.ERROR);
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700306 }
307 });
308 }
309
310 private List<String> prepareExtraProperties(UpdateConfig config) {
311 List<String> extraProperties = new ArrayList<>();
312
313 if (!config.getAbConfig().getForceSwitchSlot()) {
314 // Disable switch slot on reboot, which is enabled by default.
315 // User will enable it manually by clicking "Switch Slot" button on the screen.
316 extraProperties.add(UpdateEngineProperties.PROPERTY_DISABLE_SWITCH_SLOT_ON_REBOOT);
317 }
318 return extraProperties;
319 }
320
321 /**
322 * Applies given payload.
323 *
324 * <p>UpdateEngine works asynchronously. This method doesn't wait until
325 * end of the update.</p>
326 *
327 * <p>It's possible that the update engine throws a generic error, such as upon seeing invalid
328 * payload properties (which come from OTA packages), or failing to set up the network
329 * with the given id.</p>
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700330 */
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700331 private void updateEngineApplyPayload(UpdateData update) {
332 synchronized (mLock) {
333 mLastUpdateData = update;
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700334 }
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700335
336 ArrayList<String> properties = new ArrayList<>(update.getPayload().getProperties());
337 properties.addAll(update.getExtraProperties());
338
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700339 try {
340 mUpdateEngine.applyPayload(
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700341 update.getPayload().getUrl(),
342 update.getPayload().getOffset(),
343 update.getPayload().getSize(),
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700344 properties.toArray(new String[0]));
345 } catch (Exception e) {
346 Log.e(TAG, "UpdateEngine failed to apply the update", e);
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700347 setUpdaterState(UpdaterState.ERROR);
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700348 }
349 }
350
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700351 private void updateEngineReApplyPayload() {
352 UpdateData lastUpdate;
353 synchronized (mLock) {
354 // mLastPayloadSpec might be empty in some cases.
355 // But to make this sample app simple, we will not handle it.
356 Preconditions.checkArgument(
357 mLastUpdateData != null,
358 "mLastUpdateData must be present.");
359 lastUpdate = mLastUpdateData;
360 }
361 updateEngineApplyPayload(lastUpdate);
362 }
363
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700364 /**
365 * Sets the new slot that has the updated partitions as the active slot,
366 * which device will boot into next time.
367 * This method is only supposed to be called after the payload is applied.
368 *
369 * Invoking {@link UpdateEngine#applyPayload} with the same payload url, offset, size
370 * and payload metadata headers doesn't trigger new update. It can be used to just switch
371 * active A/B slot.
372 *
373 * {@link UpdateEngine#applyPayload} might take several seconds to finish, and it will
374 * invoke callbacks {@link this#onStatusUpdate} and {@link this#onPayloadApplicationComplete)}.
375 */
376 public void setSwitchSlotOnReboot() {
377 Log.d(TAG, "setSwitchSlotOnReboot invoked");
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700378 UpdateData.Builder builder;
379 synchronized (mLock) {
380 // To make sample app simple, we don't handle it.
381 Preconditions.checkArgument(
382 mLastUpdateData != null,
383 "mLastUpdateData must be present.");
384 builder = mLastUpdateData.toBuilder();
385 }
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700386 // PROPERTY_SKIP_POST_INSTALL should be passed on to skip post-installation hooks.
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700387 builder.setExtraProperties(
388 Collections.singletonList(UpdateEngineProperties.PROPERTY_SKIP_POST_INSTALL));
389 // UpdateEngine sets property SWITCH_SLOT_ON_REBOOT=1 by default.
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700390 // HTTP headers are not required, UpdateEngine is not expected to stream payload.
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700391 updateEngineApplyPayload(builder.build());
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700392 }
393
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700394 /**
395 * Verifies if mUpdaterState matches mUpdateEngineStatus.
396 * If they don't match, runs applyPayload to trigger onPayloadApplicationComplete
397 * callback, which updates mUpdaterState.
398 */
399 private void ensureCorrectUpdaterState() {
400 // When mUpdaterState is one of IDLE, PAUSED, ERROR, SLOT_SWITCH_REQUIRED
401 // then mUpdateEngineStatus must be IDLE.
402 // When mUpdaterState is RUNNING,
403 // then mUpdateEngineStatus must not be IDLE or UPDATED_NEED_REBOOT.
404 // When mUpdaterState is REBOOT_REQUIRED,
405 // then mUpdateEngineStatus must be UPDATED_NEED_REBOOT.
406 int state = mUpdaterState.get();
407 int updateEngineStatus = mUpdateEngineStatus.get();
408 if (state == UpdaterState.IDLE
409 || state == UpdaterState.ERROR
410 || state == UpdaterState.PAUSED
411 || state == UpdaterState.SLOT_SWITCH_REQUIRED) {
412 ensureUpdateEngineStatusIdle(state, updateEngineStatus);
413 } else if (state == UpdaterState.RUNNING) {
414 ensureUpdateEngineStatusRunning(state, updateEngineStatus);
415 } else if (state == UpdaterState.REBOOT_REQUIRED) {
416 ensureUpdateEngineStatusReboot(state, updateEngineStatus);
417 }
418 }
419
420 private void ensureUpdateEngineStatusIdle(int state, int updateEngineStatus) {
421 if (updateEngineStatus == UpdateEngine.UpdateStatusConstants.IDLE) {
422 return;
423 }
424 // It might happen when update is started not from the sample app.
425 // To make the sample app simple, we won't handle this case.
426 throw new RuntimeException("When mUpdaterState is " + state
427 + " mUpdateEngineStatus expected to be "
428 + UpdateEngine.UpdateStatusConstants.IDLE
429 + ", but it is " + updateEngineStatus);
430 }
431
432 private void ensureUpdateEngineStatusRunning(int state, int updateEngineStatus) {
433 if (updateEngineStatus != UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT
434 && updateEngineStatus != UpdateEngine.UpdateStatusConstants.IDLE) {
435 return;
436 }
437 // Re-apply latest update. It makes update_engine to invoke
438 // onPayloadApplicationComplete callback. The callback notifies
439 // if update was successful or not.
440 updateEngineReApplyPayload();
441 }
442
443 private void ensureUpdateEngineStatusReboot(int state, int updateEngineStatus) {
444 if (updateEngineStatus == UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT) {
445 return;
446 }
447 // This might happen when update is installed by other means,
448 // and sample app is not aware of it. To make the sample app simple,
449 // we won't handle this case.
450 throw new RuntimeException("When mUpdaterState is " + state
451 + " mUpdateEngineStatus expected to be "
452 + UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT
453 + ", but it is " + updateEngineStatus);
454 }
455
456 /**
457 * Invoked by update_engine whenever update status or progress changes.
458 * It's also guaranteed to be invoked when app binds to the update_engine, except
459 * when update_engine fails to initialize (as defined in
460 * system/update_engine/binder_service_android.cc in
461 * function BinderUpdateEngineAndroidService::bind).
462 *
463 * @param status one of {@link UpdateEngine.UpdateStatusConstants}.
464 * @param progress a number from 0.0 to 1.0.
465 */
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700466 private void onStatusUpdate(int status, float progress) {
467 int previousStatus = mUpdateEngineStatus.get();
468 mUpdateEngineStatus.set(status);
469 mProgress.set(progress);
470
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700471 ensureCorrectUpdaterState();
472
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700473 getOnProgressUpdateCallback().ifPresent(callback -> callback.accept(progress));
474
475 if (previousStatus != status) {
476 getOnEngineStatusUpdateCallback().ifPresent(callback -> callback.accept(status));
477 }
478 }
479
480 private void onPayloadApplicationComplete(int errorCode) {
481 Log.d(TAG, "onPayloadApplicationComplete invoked, errorCode=" + errorCode);
482 mEngineErrorCode.set(errorCode);
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700483 if (errorCode == UpdateEngine.ErrorCodeConstants.SUCCESS
484 || errorCode == UpdateEngineErrorCodes.UPDATED_BUT_NOT_ACTIVE) {
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700485 setUpdaterState(isManualSwitchSlotRequired()
486 ? UpdaterState.SLOT_SWITCH_REQUIRED
487 : UpdaterState.REBOOT_REQUIRED);
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700488 } else if (errorCode != UpdateEngineErrorCodes.USER_CANCELLED) {
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700489 setUpdaterState(UpdaterState.ERROR);
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700490 }
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700491
492 getOnEngineCompleteCallback()
493 .ifPresent(callback -> callback.accept(errorCode));
494 }
495
496 /**
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700497 * Helper class to delegate {@code update_engine} callback invocations to UpdateManager.
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700498 */
499 class UpdateEngineCallbackImpl extends UpdateEngineCallback {
500 @Override
501 public void onStatusUpdate(int status, float percent) {
502 UpdateManager.this.onStatusUpdate(status, percent);
503 }
504
505 @Override
506 public void onPayloadApplicationComplete(int errorCode) {
507 UpdateManager.this.onPayloadApplicationComplete(errorCode);
508 }
509 }
510
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700511 /**
512 *
513 * Contains update data - PayloadSpec and extra properties list.
514 *
515 * <p>{@code mPayload} contains url, offset and size to {@code PAYLOAD_BINARY_FILE_NAME}.
516 * {@code mExtraProperties} is a list of additional properties to pass to
517 * {@link UpdateEngine#applyPayload}.</p>
518 */
519 private static class UpdateData {
520 private final PayloadSpec mPayload;
521 private final ImmutableList<String> mExtraProperties;
522
523 public static Builder builder() {
524 return new Builder();
525 }
526
527 UpdateData(Builder builder) {
528 this.mPayload = builder.mPayload;
529 this.mExtraProperties = ImmutableList.copyOf(builder.mExtraProperties);
530 }
531
532 public PayloadSpec getPayload() {
533 return mPayload;
534 }
535
536 public ImmutableList<String> getExtraProperties() {
537 return mExtraProperties;
538 }
539
540 public Builder toBuilder() {
541 return builder()
542 .setPayload(mPayload)
543 .setExtraProperties(mExtraProperties);
544 }
545
546 static class Builder {
547 private PayloadSpec mPayload;
548 private List<String> mExtraProperties;
549
550 public Builder setPayload(PayloadSpec payload) {
551 this.mPayload = payload;
552 return this;
553 }
554
555 public Builder setExtraProperties(List<String> extraProperties) {
556 this.mExtraProperties = new ArrayList<>(extraProperties);
557 return this;
558 }
559
560 public Builder addExtraProperty(String property) {
561 if (this.mExtraProperties == null) {
562 this.mExtraProperties = new ArrayList<>();
563 }
564 this.mExtraProperties.add(property);
565 return this;
566 }
567
568 public UpdateData build() {
569 return new UpdateData(this);
570 }
571 }
572 }
573
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700574}