blob: c02e6084685428d55ddf96aac200a09b07acfb75 [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;
Zhomart Mukhamejanov75f40732018-12-14 09:36:32 -080020import android.os.Handler;
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070021import android.os.UpdateEngine;
22import android.os.UpdateEngineCallback;
23import android.util.Log;
24
Zhomart Mukhamejanov75f40732018-12-14 09:36:32 -080025import com.example.android.systemupdatersample.services.PrepareUpdateService;
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070026import 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
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070032import java.util.ArrayList;
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -070033import java.util.Collections;
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070034import java.util.List;
35import java.util.Optional;
36import java.util.concurrent.atomic.AtomicBoolean;
37import java.util.concurrent.atomic.AtomicInteger;
38import java.util.function.DoubleConsumer;
39import java.util.function.IntConsumer;
40
Zhomart Mukhamejanovf6522eb2018-05-24 09:11:47 -070041import javax.annotation.concurrent.GuardedBy;
42
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070043/**
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -070044 * Manages the update flow. It has its own state (in memory), separate from
45 * {@link UpdateEngine}'s state. Asynchronously interacts with the {@link UpdateEngine}.
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070046 */
47public class UpdateManager {
48
49 private static final String TAG = "UpdateManager";
50
51 /** HTTP Header: User-Agent; it will be sent to the server when streaming the payload. */
Zhomart Mukhamejanov75f40732018-12-14 09:36:32 -080052 static final String HTTP_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070053 + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36";
54
55 private final UpdateEngine mUpdateEngine;
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070056
57 private AtomicInteger mUpdateEngineStatus =
58 new AtomicInteger(UpdateEngine.UpdateStatusConstants.IDLE);
59 private AtomicInteger mEngineErrorCode = new AtomicInteger(UpdateEngineErrorCodes.UNKNOWN);
60 private AtomicDouble mProgress = new AtomicDouble(0);
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -070061 private UpdaterState mUpdaterState = new UpdaterState(UpdaterState.IDLE);
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -070062
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070063 private AtomicBoolean mManualSwitchSlotRequired = new AtomicBoolean(true);
64
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -070065 /** Synchronize state with engine status only once when app binds to UpdateEngine. */
66 private AtomicBoolean mStateSynchronized = new AtomicBoolean(false);
Zhomart Mukhamejanov51cde1e2018-05-31 12:19:41 -070067
Zhomart Mukhamejanovf6522eb2018-05-24 09:11:47 -070068 @GuardedBy("mLock")
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -070069 private UpdateData mLastUpdateData = null;
70
Zhomart Mukhamejanovf6522eb2018-05-24 09:11:47 -070071 @GuardedBy("mLock")
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -070072 private IntConsumer mOnStateChangeCallback = null;
Zhomart Mukhamejanovf6522eb2018-05-24 09:11:47 -070073 @GuardedBy("mLock")
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070074 private IntConsumer mOnEngineStatusUpdateCallback = null;
Zhomart Mukhamejanovf6522eb2018-05-24 09:11:47 -070075 @GuardedBy("mLock")
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070076 private DoubleConsumer mOnProgressUpdateCallback = null;
Zhomart Mukhamejanovf6522eb2018-05-24 09:11:47 -070077 @GuardedBy("mLock")
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070078 private IntConsumer mOnEngineCompleteCallback = null;
79
80 private final Object mLock = new Object();
81
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -070082 private final UpdateManager.UpdateEngineCallbackImpl
83 mUpdateEngineCallback = new UpdateManager.UpdateEngineCallbackImpl();
84
Zhomart Mukhamejanov75f40732018-12-14 09:36:32 -080085 private final Handler mHandler;
86
87 /**
88 * @param updateEngine UpdateEngine instance.
89 * @param handler Handler for {@link PrepareUpdateService} intent service.
90 */
91 public UpdateManager(UpdateEngine updateEngine, Handler handler) {
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070092 this.mUpdateEngine = updateEngine;
Zhomart Mukhamejanov75f40732018-12-14 09:36:32 -080093 this.mHandler = handler;
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070094 }
95
96 /**
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -070097 * Binds to {@link UpdateEngine}. Invokes onStateChangeCallback if present.
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -070098 */
99 public void bind() {
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700100 getOnStateChangeCallback().ifPresent(callback -> callback.accept(mUpdaterState.get()));
101
102 mStateSynchronized.set(false);
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700103 this.mUpdateEngine.bind(mUpdateEngineCallback);
104 }
105
106 /**
107 * Unbinds from {@link UpdateEngine}.
108 */
109 public void unbind() {
110 this.mUpdateEngine.unbind();
111 }
112
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700113 public int getUpdaterState() {
114 return mUpdaterState.get();
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700115 }
116
117 /**
118 * Returns true if manual switching slot is required. Value depends on
119 * the update config {@code ab_config.force_switch_slot}.
120 */
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700121 public boolean isManualSwitchSlotRequired() {
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700122 return mManualSwitchSlotRequired.get();
123 }
124
125 /**
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700126 * Sets SystemUpdaterSample app state change callback. Value of {@code state} will be one
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700127 * of the values from {@link UpdaterState}.
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700128 *
129 * @param onStateChangeCallback a callback with parameter {@code state}.
130 */
131 public void setOnStateChangeCallback(IntConsumer onStateChangeCallback) {
132 synchronized (mLock) {
133 this.mOnStateChangeCallback = onStateChangeCallback;
134 }
135 }
136
137 private Optional<IntConsumer> getOnStateChangeCallback() {
138 synchronized (mLock) {
139 return mOnStateChangeCallback == null
140 ? Optional.empty()
141 : Optional.of(mOnStateChangeCallback);
142 }
143 }
144
145 /**
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700146 * Sets update engine status update callback. Value of {@code status} will
147 * be one of the values from {@link UpdateEngine.UpdateStatusConstants}.
148 *
149 * @param onStatusUpdateCallback a callback with parameter {@code status}.
150 */
151 public void setOnEngineStatusUpdateCallback(IntConsumer onStatusUpdateCallback) {
152 synchronized (mLock) {
153 this.mOnEngineStatusUpdateCallback = onStatusUpdateCallback;
154 }
155 }
156
157 private Optional<IntConsumer> getOnEngineStatusUpdateCallback() {
158 synchronized (mLock) {
159 return mOnEngineStatusUpdateCallback == null
160 ? Optional.empty()
161 : Optional.of(mOnEngineStatusUpdateCallback);
162 }
163 }
164
165 /**
166 * Sets update engine payload application complete callback. Value of {@code errorCode} will
167 * be one of the values from {@link UpdateEngine.ErrorCodeConstants}.
168 *
169 * @param onComplete a callback with parameter {@code errorCode}.
170 */
171 public void setOnEngineCompleteCallback(IntConsumer onComplete) {
172 synchronized (mLock) {
173 this.mOnEngineCompleteCallback = onComplete;
174 }
175 }
176
177 private Optional<IntConsumer> getOnEngineCompleteCallback() {
178 synchronized (mLock) {
179 return mOnEngineCompleteCallback == null
180 ? Optional.empty()
181 : Optional.of(mOnEngineCompleteCallback);
182 }
183 }
184
185 /**
186 * Sets progress update callback. Progress is a number from {@code 0.0} to {@code 1.0}.
187 *
188 * @param onProgressCallback a callback with parameter {@code progress}.
189 */
190 public void setOnProgressUpdateCallback(DoubleConsumer onProgressCallback) {
191 synchronized (mLock) {
192 this.mOnProgressUpdateCallback = onProgressCallback;
193 }
194 }
195
196 private Optional<DoubleConsumer> getOnProgressUpdateCallback() {
197 synchronized (mLock) {
198 return mOnProgressUpdateCallback == null
199 ? Optional.empty()
200 : Optional.of(mOnProgressUpdateCallback);
201 }
202 }
203
204 /**
Zhomart Mukhamejanov16db9942018-05-31 10:47:09 -0700205 * Suspend running update.
206 */
207 public synchronized void suspend() throws UpdaterState.InvalidTransitionException {
208 Log.d(TAG, "suspend invoked");
209 setUpdaterState(UpdaterState.PAUSED);
210 mUpdateEngine.cancel();
211 }
212
213 /**
214 * Resume suspended update.
215 */
216 public synchronized void resume() throws UpdaterState.InvalidTransitionException {
217 Log.d(TAG, "resume invoked");
218 setUpdaterState(UpdaterState.RUNNING);
219 updateEngineReApplyPayload();
220 }
221
222 /**
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700223 * Updates {@link this.mState} and if state is changed,
224 * it also notifies {@link this.mOnStateChangeCallback}.
225 */
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700226 private void setUpdaterState(int newUpdaterState)
227 throws UpdaterState.InvalidTransitionException {
228 Log.d(TAG, "setUpdaterState invoked newState=" + newUpdaterState);
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700229 int previousState = mUpdaterState.get();
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700230 mUpdaterState.set(newUpdaterState);
231 if (previousState != newUpdaterState) {
232 getOnStateChangeCallback().ifPresent(callback -> callback.accept(newUpdaterState));
233 }
234 }
235
236 /**
237 * Same as {@link this.setUpdaterState}. Logs the error if new state
238 * cannot be set.
239 */
240 private void setUpdaterStateSilent(int newUpdaterState) {
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700241 try {
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700242 setUpdaterState(newUpdaterState);
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700243 } catch (UpdaterState.InvalidTransitionException e) {
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700244 // Most likely UpdateEngine status and UpdaterSample state got de-synchronized.
245 // To make sample app simple, we don't handle it properly.
246 Log.e(TAG, "Failed to set updater state", e);
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700247 }
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700248 }
249
250 /**
251 * Creates new UpdaterState, assigns it to {@link this.mUpdaterState},
252 * and notifies callbacks.
253 */
254 private void initializeUpdateState(int state) {
255 this.mUpdaterState = new UpdaterState(state);
256 getOnStateChangeCallback().ifPresent(callback -> callback.accept(state));
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700257 }
258
259 /**
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700260 * Requests update engine to stop any ongoing update. If an update has been applied,
261 * leave it as is.
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700262 */
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700263 public synchronized void cancelRunningUpdate() throws UpdaterState.InvalidTransitionException {
264 Log.d(TAG, "cancelRunningUpdate invoked");
265 setUpdaterState(UpdaterState.IDLE);
266 mUpdateEngine.cancel();
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700267 }
268
269 /**
270 * Resets update engine to IDLE state. If an update has been applied it reverts it.
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700271 */
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700272 public synchronized void resetUpdate() throws UpdaterState.InvalidTransitionException {
273 Log.d(TAG, "resetUpdate invoked");
274 setUpdaterState(UpdaterState.IDLE);
275 mUpdateEngine.resetStatus();
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700276 }
277
278 /**
279 * Applies the given update.
280 *
281 * <p>UpdateEngine works asynchronously. This method doesn't wait until
282 * end of the update.</p>
283 */
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700284 public synchronized void applyUpdate(Context context, UpdateConfig config)
285 throws UpdaterState.InvalidTransitionException {
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700286 mEngineErrorCode.set(UpdateEngineErrorCodes.UNKNOWN);
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700287 setUpdaterState(UpdaterState.RUNNING);
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700288
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700289 synchronized (mLock) {
290 // Cleaning up previous update data.
291 mLastUpdateData = null;
292 }
293
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700294 if (!config.getAbConfig().getForceSwitchSlot()) {
295 mManualSwitchSlotRequired.set(true);
296 } else {
297 mManualSwitchSlotRequired.set(false);
298 }
299
Zhomart Mukhamejanov75f40732018-12-14 09:36:32 -0800300 Log.d(TAG, "Starting PrepareUpdateService");
301 PrepareUpdateService.startService(context, config, mHandler, (code, payloadSpec) -> {
302 if (code != PrepareUpdateService.RESULT_CODE_SUCCESS) {
303 Log.e(TAG, "PrepareUpdateService failed, result code is " + code);
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700304 setUpdaterStateSilent(UpdaterState.ERROR);
Zhomart Mukhamejanov75f40732018-12-14 09:36:32 -0800305 return;
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700306 }
Zhomart Mukhamejanov75f40732018-12-14 09:36:32 -0800307 updateEngineApplyPayload(UpdateData.builder()
308 .setExtraProperties(prepareExtraProperties(config))
309 .setPayload(payloadSpec)
310 .build());
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700311 });
312 }
313
314 private List<String> prepareExtraProperties(UpdateConfig config) {
315 List<String> extraProperties = new ArrayList<>();
316
317 if (!config.getAbConfig().getForceSwitchSlot()) {
318 // Disable switch slot on reboot, which is enabled by default.
319 // User will enable it manually by clicking "Switch Slot" button on the screen.
320 extraProperties.add(UpdateEngineProperties.PROPERTY_DISABLE_SWITCH_SLOT_ON_REBOOT);
321 }
Zhomart Mukhamejanov75f40732018-12-14 09:36:32 -0800322 if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_STREAMING) {
323 extraProperties.add("USER_AGENT=" + HTTP_USER_AGENT);
324 config.getAbConfig()
325 .getAuthorization()
326 .ifPresent(s -> extraProperties.add("AUTHORIZATION=" + s));
327 }
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700328 return extraProperties;
329 }
330
331 /**
332 * Applies given payload.
333 *
334 * <p>UpdateEngine works asynchronously. This method doesn't wait until
335 * end of the update.</p>
336 *
337 * <p>It's possible that the update engine throws a generic error, such as upon seeing invalid
338 * payload properties (which come from OTA packages), or failing to set up the network
339 * with the given id.</p>
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700340 */
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700341 private void updateEngineApplyPayload(UpdateData update) {
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700342 Log.d(TAG, "updateEngineApplyPayload invoked with url " + update.mPayload.getUrl());
343
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700344 synchronized (mLock) {
345 mLastUpdateData = update;
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700346 }
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700347
348 ArrayList<String> properties = new ArrayList<>(update.getPayload().getProperties());
349 properties.addAll(update.getExtraProperties());
350
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700351 try {
352 mUpdateEngine.applyPayload(
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700353 update.getPayload().getUrl(),
354 update.getPayload().getOffset(),
355 update.getPayload().getSize(),
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700356 properties.toArray(new String[0]));
357 } catch (Exception e) {
358 Log.e(TAG, "UpdateEngine failed to apply the update", e);
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700359 setUpdaterStateSilent(UpdaterState.ERROR);
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700360 }
361 }
362
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700363 /**
364 * Re-applies {@link this.mLastUpdateData} to update_engine.
365 */
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700366 private void updateEngineReApplyPayload() {
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700367 Log.d(TAG, "updateEngineReApplyPayload invoked");
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700368 UpdateData lastUpdate;
369 synchronized (mLock) {
370 // mLastPayloadSpec might be empty in some cases.
371 // But to make this sample app simple, we will not handle it.
372 Preconditions.checkArgument(
373 mLastUpdateData != null,
374 "mLastUpdateData must be present.");
375 lastUpdate = mLastUpdateData;
376 }
377 updateEngineApplyPayload(lastUpdate);
378 }
379
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700380 /**
381 * Sets the new slot that has the updated partitions as the active slot,
382 * which device will boot into next time.
383 * This method is only supposed to be called after the payload is applied.
384 *
385 * Invoking {@link UpdateEngine#applyPayload} with the same payload url, offset, size
386 * and payload metadata headers doesn't trigger new update. It can be used to just switch
387 * active A/B slot.
388 *
389 * {@link UpdateEngine#applyPayload} might take several seconds to finish, and it will
390 * invoke callbacks {@link this#onStatusUpdate} and {@link this#onPayloadApplicationComplete)}.
391 */
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700392 public synchronized void setSwitchSlotOnReboot() {
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700393 Log.d(TAG, "setSwitchSlotOnReboot invoked");
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700394
395 // When mManualSwitchSlotRequired set false, next time
396 // onApplicationPayloadComplete is called,
397 // it will set updater state to REBOOT_REQUIRED.
398 mManualSwitchSlotRequired.set(false);
399
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700400 UpdateData.Builder builder;
401 synchronized (mLock) {
402 // To make sample app simple, we don't handle it.
403 Preconditions.checkArgument(
404 mLastUpdateData != null,
405 "mLastUpdateData must be present.");
406 builder = mLastUpdateData.toBuilder();
407 }
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700408 // PROPERTY_SKIP_POST_INSTALL should be passed on to skip post-installation hooks.
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700409 builder.setExtraProperties(
410 Collections.singletonList(UpdateEngineProperties.PROPERTY_SKIP_POST_INSTALL));
411 // UpdateEngine sets property SWITCH_SLOT_ON_REBOOT=1 by default.
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700412 // HTTP headers are not required, UpdateEngine is not expected to stream payload.
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700413 updateEngineApplyPayload(builder.build());
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700414 }
415
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700416 /**
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700417 * Synchronize UpdaterState with UpdateEngine status.
418 * Apply necessary UpdateEngine operation if status are out of sync.
419 *
420 * It's expected to be called once when sample app binds itself to UpdateEngine.
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700421 */
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700422 private void synchronizeUpdaterStateWithUpdateEngineStatus() {
423 Log.d(TAG, "synchronizeUpdaterStateWithUpdateEngineStatus is invoked.");
424
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700425 int state = mUpdaterState.get();
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700426 int engineStatus = mUpdateEngineStatus.get();
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700427
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700428 if (engineStatus == UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT) {
429 // If update has been installed before running the sample app,
430 // set state to REBOOT_REQUIRED.
431 initializeUpdateState(UpdaterState.REBOOT_REQUIRED);
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700432 return;
433 }
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700434
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700435 switch (state) {
436 case UpdaterState.IDLE:
437 case UpdaterState.ERROR:
438 case UpdaterState.PAUSED:
439 case UpdaterState.SLOT_SWITCH_REQUIRED:
440 // It might happen when update is started not from the sample app.
441 // To make the sample app simple, we won't handle this case.
442 Preconditions.checkState(
443 engineStatus == UpdateEngine.UpdateStatusConstants.IDLE,
444 "When mUpdaterState is %s, mUpdateEngineStatus "
445 + "must be 0/IDLE, but it is %s",
446 state,
447 engineStatus);
448 break;
449 case UpdaterState.RUNNING:
450 if (engineStatus == UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT
451 || engineStatus == UpdateEngine.UpdateStatusConstants.IDLE) {
452 Log.i(TAG, "ensureUpdateEngineStatusIsRunning - re-applying last payload");
453 // Re-apply latest update. It makes update_engine to invoke
454 // onPayloadApplicationComplete callback. The callback notifies
455 // if update was successful or not.
456 updateEngineReApplyPayload();
457 }
458 break;
459 case UpdaterState.REBOOT_REQUIRED:
460 // This might happen when update is installed by other means,
461 // and sample app is not aware of it.
462 // To make the sample app simple, we won't handle this case.
463 Preconditions.checkState(
464 engineStatus == UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT,
465 "When mUpdaterState is %s, mUpdateEngineStatus "
466 + "must be 6/UPDATED_NEED_REBOOT, but it is %s",
467 state,
468 engineStatus);
469 break;
470 default:
471 throw new IllegalStateException("This block should not be reached.");
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700472 }
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700473 }
474
475 /**
476 * Invoked by update_engine whenever update status or progress changes.
477 * It's also guaranteed to be invoked when app binds to the update_engine, except
478 * when update_engine fails to initialize (as defined in
479 * system/update_engine/binder_service_android.cc in
480 * function BinderUpdateEngineAndroidService::bind).
481 *
Zhomart Mukhamejanov75f40732018-12-14 09:36:32 -0800482 * @param status one of {@link UpdateEngine.UpdateStatusConstants}.
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700483 * @param progress a number from 0.0 to 1.0.
484 */
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700485 private void onStatusUpdate(int status, float progress) {
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700486 Log.d(TAG, String.format(
Zhomart Mukhamejanov75f40732018-12-14 09:36:32 -0800487 "onStatusUpdate invoked, status=%s, progress=%.2f",
488 status,
489 progress));
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700490
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700491 int previousStatus = mUpdateEngineStatus.get();
492 mUpdateEngineStatus.set(status);
493 mProgress.set(progress);
494
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700495 if (!mStateSynchronized.getAndSet(true)) {
496 // We synchronize state with engine status once
497 // only when sample app is bound to UpdateEngine.
498 synchronizeUpdaterStateWithUpdateEngineStatus();
Zhomart Mukhamejanov51cde1e2018-05-31 12:19:41 -0700499 }
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700500
Zhomart Mukhamejanov16db9942018-05-31 10:47:09 -0700501 getOnProgressUpdateCallback().ifPresent(callback -> callback.accept(mProgress.get()));
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700502
503 if (previousStatus != status) {
504 getOnEngineStatusUpdateCallback().ifPresent(callback -> callback.accept(status));
505 }
506 }
507
508 private void onPayloadApplicationComplete(int errorCode) {
509 Log.d(TAG, "onPayloadApplicationComplete invoked, errorCode=" + errorCode);
510 mEngineErrorCode.set(errorCode);
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700511 if (errorCode == UpdateEngine.ErrorCodeConstants.SUCCESS
512 || errorCode == UpdateEngineErrorCodes.UPDATED_BUT_NOT_ACTIVE) {
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700513 setUpdaterStateSilent(isManualSwitchSlotRequired()
Zhomart Mukhamejanov674aa6c2018-05-25 17:00:11 -0700514 ? UpdaterState.SLOT_SWITCH_REQUIRED
515 : UpdaterState.REBOOT_REQUIRED);
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700516 } else if (errorCode != UpdateEngineErrorCodes.USER_CANCELLED) {
Zhomart Mukhamejanov469b35a2018-06-01 12:41:20 -0700517 setUpdaterStateSilent(UpdaterState.ERROR);
Zhomart Mukhamejanov8f4059d2018-05-18 10:15:31 -0700518 }
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700519
520 getOnEngineCompleteCallback()
521 .ifPresent(callback -> callback.accept(errorCode));
522 }
523
524 /**
Zhomart Mukhamejanov7671f682018-05-24 09:11:47 -0700525 * Helper class to delegate {@code update_engine} callback invocations to UpdateManager.
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700526 */
527 class UpdateEngineCallbackImpl extends UpdateEngineCallback {
528 @Override
529 public void onStatusUpdate(int status, float percent) {
530 UpdateManager.this.onStatusUpdate(status, percent);
531 }
532
533 @Override
534 public void onPayloadApplicationComplete(int errorCode) {
535 UpdateManager.this.onPayloadApplicationComplete(errorCode);
536 }
537 }
538
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700539 /**
Zhomart Mukhamejanovb34f7ea2018-05-25 17:00:11 -0700540 * Contains update data - PayloadSpec and extra properties list.
541 *
542 * <p>{@code mPayload} contains url, offset and size to {@code PAYLOAD_BINARY_FILE_NAME}.
543 * {@code mExtraProperties} is a list of additional properties to pass to
544 * {@link UpdateEngine#applyPayload}.</p>
545 */
546 private static class UpdateData {
547 private final PayloadSpec mPayload;
548 private final ImmutableList<String> mExtraProperties;
549
550 public static Builder builder() {
551 return new Builder();
552 }
553
554 UpdateData(Builder builder) {
555 this.mPayload = builder.mPayload;
556 this.mExtraProperties = ImmutableList.copyOf(builder.mExtraProperties);
557 }
558
559 public PayloadSpec getPayload() {
560 return mPayload;
561 }
562
563 public ImmutableList<String> getExtraProperties() {
564 return mExtraProperties;
565 }
566
567 public Builder toBuilder() {
568 return builder()
569 .setPayload(mPayload)
570 .setExtraProperties(mExtraProperties);
571 }
572
573 static class Builder {
574 private PayloadSpec mPayload;
575 private List<String> mExtraProperties;
576
577 public Builder setPayload(PayloadSpec payload) {
578 this.mPayload = payload;
579 return this;
580 }
581
582 public Builder setExtraProperties(List<String> extraProperties) {
583 this.mExtraProperties = new ArrayList<>(extraProperties);
584 return this;
585 }
586
587 public Builder addExtraProperty(String property) {
588 if (this.mExtraProperties == null) {
589 this.mExtraProperties = new ArrayList<>();
590 }
591 this.mExtraProperties.add(property);
592 return this;
593 }
594
595 public UpdateData build() {
596 return new UpdateData(this);
597 }
598 }
599 }
600
Zhomart Mukhamejanov6f26e712018-05-18 10:15:31 -0700601}