blob: e4c09346b5c5a9a0628422ba1d1ef378bf9cfa43 [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 Mukhamejanov8f4059d2018-05-18 10:15:31 -0700201 * Updates {@link this.mState} and if state is changed,
202 * it also notifies {@link this.mOnStateChangeCallback}.
203 */
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700204 private void setUpdaterState(int newUpdaterState)
205 throws UpdaterState.InvalidTransitionException {
206 Log.d(TAG, "setUpdaterState invoked newState=" + newUpdaterState);
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700207 int previousState = mUpdaterState.get();
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700208 mUpdaterState.set(newUpdaterState);
209 if (previousState != newUpdaterState) {
210 getOnStateChangeCallback().ifPresent(callback -> callback.accept(newUpdaterState));
211 }
212 }
213
214 /**
215 * Same as {@link this.setUpdaterState}. Logs the error if new state
216 * cannot be set.
217 */
218 private void setUpdaterStateSilent(int newUpdaterState) {
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700219 try {
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700220 setUpdaterState(newUpdaterState);
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700221 } catch (UpdaterState.InvalidTransitionException e) {
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700222 // Most likely UpdateEngine status and UpdaterSample state got de-synchronized.
223 // To make sample app simple, we don't handle it properly.
224 Log.e(TAG, "Failed to set updater state", e);
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700225 }
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700226 }
227
228 /**
229 * Creates new UpdaterState, assigns it to {@link this.mUpdaterState},
230 * and notifies callbacks.
231 */
232 private void initializeUpdateState(int state) {
233 this.mUpdaterState = new UpdaterState(state);
234 getOnStateChangeCallback().ifPresent(callback -> callback.accept(state));
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700235 }
236
237 /**
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700238 * Requests update engine to stop any ongoing update. If an update has been applied,
239 * leave it as is.
240 *
241 * <p>Sometimes it's possible that the
242 * update engine would throw an error when the method is called, and the only way to
243 * handle it is to catch the exception.</p>
244 */
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700245 public synchronized void cancelRunningUpdate() throws UpdaterState.InvalidTransitionException {
246 Log.d(TAG, "cancelRunningUpdate invoked");
247 setUpdaterState(UpdaterState.IDLE);
248 mUpdateEngine.cancel();
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700249 }
250
251 /**
252 * Resets update engine to IDLE state. If an update has been applied it reverts it.
253 *
254 * <p>Sometimes it's possible that the
255 * update engine would throw an error when the method is called, and the only way to
256 * handle it is to catch the exception.</p>
257 */
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700258 public synchronized void resetUpdate() throws UpdaterState.InvalidTransitionException {
259 Log.d(TAG, "resetUpdate invoked");
260 setUpdaterState(UpdaterState.IDLE);
261 mUpdateEngine.resetStatus();
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700262 }
263
264 /**
265 * Applies the given update.
266 *
267 * <p>UpdateEngine works asynchronously. This method doesn't wait until
268 * end of the update.</p>
269 */
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700270 public synchronized void applyUpdate(Context context, UpdateConfig config)
271 throws UpdaterState.InvalidTransitionException {
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700272 mEngineErrorCode.set(UpdateEngineErrorCodes.UNKNOWN);
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700273 setUpdaterState(UpdaterState.RUNNING);
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700274
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700275 synchronized (mLock) {
276 // Cleaning up previous update data.
277 mLastUpdateData = null;
278 }
279
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700280 if (!config.getAbConfig().getForceSwitchSlot()) {
281 mManualSwitchSlotRequired.set(true);
282 } else {
283 mManualSwitchSlotRequired.set(false);
284 }
285
286 if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING) {
287 applyAbNonStreamingUpdate(config);
288 } else {
289 applyAbStreamingUpdate(context, config);
290 }
291 }
292
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700293 private void applyAbNonStreamingUpdate(UpdateConfig config)
294 throws UpdaterState.InvalidTransitionException {
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700295 UpdateData.Builder builder = UpdateData.builder()
296 .setExtraProperties(prepareExtraProperties(config));
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700297
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700298 try {
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700299 builder.setPayload(mPayloadSpecs.forNonStreaming(config.getUpdatePackageFile()));
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700300 } catch (IOException e) {
301 Log.e(TAG, "Error creating payload spec", e);
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700302 setUpdaterState(UpdaterState.ERROR);
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700303 return;
304 }
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700305 updateEngineApplyPayload(builder.build());
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700306 }
307
308 private void applyAbStreamingUpdate(Context context, UpdateConfig config) {
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700309 UpdateData.Builder builder = UpdateData.builder()
310 .setExtraProperties(prepareExtraProperties(config));
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700311
312 Log.d(TAG, "Starting PrepareStreamingService");
313 PrepareStreamingService.startService(context, config, (code, payloadSpec) -> {
314 if (code == PrepareStreamingService.RESULT_CODE_SUCCESS) {
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700315 builder.setPayload(payloadSpec);
316 builder.addExtraProperty("USER_AGENT=" + HTTP_USER_AGENT);
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700317 config.getStreamingMetadata()
318 .getAuthorization()
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700319 .ifPresent(s -> builder.addExtraProperty("AUTHORIZATION=" + s));
320 updateEngineApplyPayload(builder.build());
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700321 } else {
322 Log.e(TAG, "PrepareStreamingService failed, result code is " + code);
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700323 setUpdaterStateSilent(UpdaterState.ERROR);
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700324 }
325 });
326 }
327
328 private List<String> prepareExtraProperties(UpdateConfig config) {
329 List<String> extraProperties = new ArrayList<>();
330
331 if (!config.getAbConfig().getForceSwitchSlot()) {
332 // Disable switch slot on reboot, which is enabled by default.
333 // User will enable it manually by clicking "Switch Slot" button on the screen.
334 extraProperties.add(UpdateEngineProperties.PROPERTY_DISABLE_SWITCH_SLOT_ON_REBOOT);
335 }
336 return extraProperties;
337 }
338
339 /**
340 * Applies given payload.
341 *
342 * <p>UpdateEngine works asynchronously. This method doesn't wait until
343 * end of the update.</p>
344 *
345 * <p>It's possible that the update engine throws a generic error, such as upon seeing invalid
346 * payload properties (which come from OTA packages), or failing to set up the network
347 * with the given id.</p>
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700348 */
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700349 private void updateEngineApplyPayload(UpdateData update) {
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700350 Log.d(TAG, "updateEngineApplyPayload invoked with url " + update.mPayload.getUrl());
351
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700352 synchronized (mLock) {
353 mLastUpdateData = update;
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700354 }
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700355
356 ArrayList<String> properties = new ArrayList<>(update.getPayload().getProperties());
357 properties.addAll(update.getExtraProperties());
358
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700359 try {
360 mUpdateEngine.applyPayload(
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700361 update.getPayload().getUrl(),
362 update.getPayload().getOffset(),
363 update.getPayload().getSize(),
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700364 properties.toArray(new String[0]));
365 } catch (Exception e) {
366 Log.e(TAG, "UpdateEngine failed to apply the update", e);
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700367 setUpdaterStateSilent(UpdaterState.ERROR);
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700368 }
369 }
370
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700371 /**
372 * Re-applies {@link this.mLastUpdateData} to update_engine.
373 */
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700374 private void updateEngineReApplyPayload() {
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700375 Log.d(TAG, "updateEngineReApplyPayload invoked");
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700376 UpdateData lastUpdate;
377 synchronized (mLock) {
378 // mLastPayloadSpec might be empty in some cases.
379 // But to make this sample app simple, we will not handle it.
380 Preconditions.checkArgument(
381 mLastUpdateData != null,
382 "mLastUpdateData must be present.");
383 lastUpdate = mLastUpdateData;
384 }
385 updateEngineApplyPayload(lastUpdate);
386 }
387
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700388 /**
389 * Sets the new slot that has the updated partitions as the active slot,
390 * which device will boot into next time.
391 * This method is only supposed to be called after the payload is applied.
392 *
393 * Invoking {@link UpdateEngine#applyPayload} with the same payload url, offset, size
394 * and payload metadata headers doesn't trigger new update. It can be used to just switch
395 * active A/B slot.
396 *
397 * {@link UpdateEngine#applyPayload} might take several seconds to finish, and it will
398 * invoke callbacks {@link this#onStatusUpdate} and {@link this#onPayloadApplicationComplete)}.
399 */
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700400 public synchronized void setSwitchSlotOnReboot() {
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700401 Log.d(TAG, "setSwitchSlotOnReboot invoked");
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700402
403 // When mManualSwitchSlotRequired set false, next time
404 // onApplicationPayloadComplete is called,
405 // it will set updater state to REBOOT_REQUIRED.
406 mManualSwitchSlotRequired.set(false);
407
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700408 UpdateData.Builder builder;
409 synchronized (mLock) {
410 // To make sample app simple, we don't handle it.
411 Preconditions.checkArgument(
412 mLastUpdateData != null,
413 "mLastUpdateData must be present.");
414 builder = mLastUpdateData.toBuilder();
415 }
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700416 // PROPERTY_SKIP_POST_INSTALL should be passed on to skip post-installation hooks.
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700417 builder.setExtraProperties(
418 Collections.singletonList(UpdateEngineProperties.PROPERTY_SKIP_POST_INSTALL));
419 // UpdateEngine sets property SWITCH_SLOT_ON_REBOOT=1 by default.
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700420 // HTTP headers are not required, UpdateEngine is not expected to stream payload.
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700421 updateEngineApplyPayload(builder.build());
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700422 }
423
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700424 /**
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700425 * Synchronize UpdaterState with UpdateEngine status.
426 * Apply necessary UpdateEngine operation if status are out of sync.
427 *
428 * It's expected to be called once when sample app binds itself to UpdateEngine.
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700429 */
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700430 private void synchronizeUpdaterStateWithUpdateEngineStatus() {
431 Log.d(TAG, "synchronizeUpdaterStateWithUpdateEngineStatus is invoked.");
432
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700433 int state = mUpdaterState.get();
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700434 int engineStatus = mUpdateEngineStatus.get();
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700435
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700436 if (engineStatus == UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT) {
437 // If update has been installed before running the sample app,
438 // set state to REBOOT_REQUIRED.
439 initializeUpdateState(UpdaterState.REBOOT_REQUIRED);
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700440 return;
441 }
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700442
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700443 switch (state) {
444 case UpdaterState.IDLE:
445 case UpdaterState.ERROR:
446 case UpdaterState.PAUSED:
447 case UpdaterState.SLOT_SWITCH_REQUIRED:
448 // It might happen when update is started not from the sample app.
449 // To make the sample app simple, we won't handle this case.
450 Preconditions.checkState(
451 engineStatus == UpdateEngine.UpdateStatusConstants.IDLE,
452 "When mUpdaterState is %s, mUpdateEngineStatus "
453 + "must be 0/IDLE, but it is %s",
454 state,
455 engineStatus);
456 break;
457 case UpdaterState.RUNNING:
458 if (engineStatus == UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT
459 || engineStatus == UpdateEngine.UpdateStatusConstants.IDLE) {
460 Log.i(TAG, "ensureUpdateEngineStatusIsRunning - re-applying last payload");
461 // Re-apply latest update. It makes update_engine to invoke
462 // onPayloadApplicationComplete callback. The callback notifies
463 // if update was successful or not.
464 updateEngineReApplyPayload();
465 }
466 break;
467 case UpdaterState.REBOOT_REQUIRED:
468 // This might happen when update is installed by other means,
469 // and sample app is not aware of it.
470 // To make the sample app simple, we won't handle this case.
471 Preconditions.checkState(
472 engineStatus == UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT,
473 "When mUpdaterState is %s, mUpdateEngineStatus "
474 + "must be 6/UPDATED_NEED_REBOOT, but it is %s",
475 state,
476 engineStatus);
477 break;
478 default:
479 throw new IllegalStateException("This block should not be reached.");
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700480 }
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700481 }
482
483 /**
484 * Invoked by update_engine whenever update status or progress changes.
485 * It's also guaranteed to be invoked when app binds to the update_engine, except
486 * when update_engine fails to initialize (as defined in
487 * system/update_engine/binder_service_android.cc in
488 * function BinderUpdateEngineAndroidService::bind).
489 *
490 * @param status one of {@link UpdateEngine.UpdateStatusConstants}.
491 * @param progress a number from 0.0 to 1.0.
492 */
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700493 private void onStatusUpdate(int status, float progress) {
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700494 Log.d(TAG, String.format(
495 "onStatusUpdate invoked, status=%s, progress=%.2f",
496 status,
497 progress));
498
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700499 int previousStatus = mUpdateEngineStatus.get();
500 mUpdateEngineStatus.set(status);
501 mProgress.set(progress);
502
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700503 if (!mStateSynchronized.getAndSet(true)) {
504 // We synchronize state with engine status once
505 // only when sample app is bound to UpdateEngine.
506 synchronizeUpdaterStateWithUpdateEngineStatus();
Zhomart Mukhamejanov51cde1e2018-05-31 12:19:41 -0700507 }
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700508
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700509 getOnProgressUpdateCallback().ifPresent(callback -> callback.accept(progress));
510
511 if (previousStatus != status) {
512 getOnEngineStatusUpdateCallback().ifPresent(callback -> callback.accept(status));
513 }
514 }
515
516 private void onPayloadApplicationComplete(int errorCode) {
517 Log.d(TAG, "onPayloadApplicationComplete invoked, errorCode=" + errorCode);
518 mEngineErrorCode.set(errorCode);
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700519 if (errorCode == UpdateEngine.ErrorCodeConstants.SUCCESS
520 || errorCode == UpdateEngineErrorCodes.UPDATED_BUT_NOT_ACTIVE) {
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700521 setUpdaterStateSilent(isManualSwitchSlotRequired()
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700522 ? UpdaterState.SLOT_SWITCH_REQUIRED
523 : UpdaterState.REBOOT_REQUIRED);
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700524 } else if (errorCode != UpdateEngineErrorCodes.USER_CANCELLED) {
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700525 setUpdaterStateSilent(UpdaterState.ERROR);
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700526 }
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700527
528 getOnEngineCompleteCallback()
529 .ifPresent(callback -> callback.accept(errorCode));
530 }
531
532 /**
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700533 * Helper class to delegate {@code update_engine} callback invocations to UpdateManager.
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700534 */
535 class UpdateEngineCallbackImpl extends UpdateEngineCallback {
536 @Override
537 public void onStatusUpdate(int status, float percent) {
538 UpdateManager.this.onStatusUpdate(status, percent);
539 }
540
541 @Override
542 public void onPayloadApplicationComplete(int errorCode) {
543 UpdateManager.this.onPayloadApplicationComplete(errorCode);
544 }
545 }
546
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700547 /**
548 *
549 * Contains update data - PayloadSpec and extra properties list.
550 *
551 * <p>{@code mPayload} contains url, offset and size to {@code PAYLOAD_BINARY_FILE_NAME}.
552 * {@code mExtraProperties} is a list of additional properties to pass to
553 * {@link UpdateEngine#applyPayload}.</p>
554 */
555 private static class UpdateData {
556 private final PayloadSpec mPayload;
557 private final ImmutableList<String> mExtraProperties;
558
559 public static Builder builder() {
560 return new Builder();
561 }
562
563 UpdateData(Builder builder) {
564 this.mPayload = builder.mPayload;
565 this.mExtraProperties = ImmutableList.copyOf(builder.mExtraProperties);
566 }
567
568 public PayloadSpec getPayload() {
569 return mPayload;
570 }
571
572 public ImmutableList<String> getExtraProperties() {
573 return mExtraProperties;
574 }
575
576 public Builder toBuilder() {
577 return builder()
578 .setPayload(mPayload)
579 .setExtraProperties(mExtraProperties);
580 }
581
582 static class Builder {
583 private PayloadSpec mPayload;
584 private List<String> mExtraProperties;
585
586 public Builder setPayload(PayloadSpec payload) {
587 this.mPayload = payload;
588 return this;
589 }
590
591 public Builder setExtraProperties(List<String> extraProperties) {
592 this.mExtraProperties = new ArrayList<>(extraProperties);
593 return this;
594 }
595
596 public Builder addExtraProperty(String property) {
597 if (this.mExtraProperties == null) {
598 this.mExtraProperties = new ArrayList<>();
599 }
600 this.mExtraProperties.add(property);
601 return this;
602 }
603
604 public UpdateData build() {
605 return new UpdateData(this);
606 }
607 }
608 }
609
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700610}