1 /*
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 
17 package com.example.android.systemupdatersample;
18 
19 import android.content.Context;
20 import android.os.Handler;
21 import android.os.UpdateEngine;
22 import android.os.UpdateEngineCallback;
23 import android.util.Log;
24 
25 import com.example.android.systemupdatersample.services.PrepareUpdateService;
26 import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes;
27 import com.example.android.systemupdatersample.util.UpdateEngineProperties;
28 import com.google.common.base.Preconditions;
29 import com.google.common.collect.ImmutableList;
30 import com.google.common.util.concurrent.AtomicDouble;
31 
32 import java.util.ArrayList;
33 import java.util.Collections;
34 import java.util.List;
35 import java.util.Optional;
36 import java.util.concurrent.atomic.AtomicBoolean;
37 import java.util.concurrent.atomic.AtomicInteger;
38 import java.util.function.DoubleConsumer;
39 import java.util.function.IntConsumer;
40 
41 import javax.annotation.concurrent.GuardedBy;
42 
43 /**
44  * Manages the update flow. It has its own state (in memory), separate from
45  * {@link UpdateEngine}'s state. Asynchronously interacts with the {@link UpdateEngine}.
46  */
47 public 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. */
52     static final String HTTP_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
53             + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36";
54 
55     private final UpdateEngine mUpdateEngine;
56 
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);
61     private UpdaterState mUpdaterState = new UpdaterState(UpdaterState.IDLE);
62 
63     private AtomicBoolean mManualSwitchSlotRequired = new AtomicBoolean(true);
64 
65     /** Synchronize state with engine status only once when app binds to UpdateEngine. */
66     private AtomicBoolean mStateSynchronized = new AtomicBoolean(false);
67 
68     @GuardedBy("mLock")
69     private UpdateData mLastUpdateData = null;
70 
71     @GuardedBy("mLock")
72     private IntConsumer mOnStateChangeCallback = null;
73     @GuardedBy("mLock")
74     private IntConsumer mOnEngineStatusUpdateCallback = null;
75     @GuardedBy("mLock")
76     private DoubleConsumer mOnProgressUpdateCallback = null;
77     @GuardedBy("mLock")
78     private IntConsumer mOnEngineCompleteCallback = null;
79 
80     private final Object mLock = new Object();
81 
82     private final UpdateManager.UpdateEngineCallbackImpl
83             mUpdateEngineCallback = new UpdateManager.UpdateEngineCallbackImpl();
84 
85     private final Handler mHandler;
86 
87     /**
88      * @param updateEngine UpdateEngine instance.
89      * @param handler      Handler for {@link PrepareUpdateService} intent service.
90      */
UpdateManager(UpdateEngine updateEngine, Handler handler)91     public UpdateManager(UpdateEngine updateEngine, Handler handler) {
92         this.mUpdateEngine = updateEngine;
93         this.mHandler = handler;
94     }
95 
96     /**
97      * Binds to {@link UpdateEngine}. Invokes onStateChangeCallback if present.
98      */
bind()99     public void bind() {
100         getOnStateChangeCallback().ifPresent(callback -> callback.accept(mUpdaterState.get()));
101 
102         mStateSynchronized.set(false);
103         this.mUpdateEngine.bind(mUpdateEngineCallback);
104     }
105 
106     /**
107      * Unbinds from {@link UpdateEngine}.
108      */
unbind()109     public void unbind() {
110         this.mUpdateEngine.unbind();
111     }
112 
getUpdaterState()113     public int getUpdaterState() {
114         return mUpdaterState.get();
115     }
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      */
isManualSwitchSlotRequired()121     public boolean isManualSwitchSlotRequired() {
122         return mManualSwitchSlotRequired.get();
123     }
124 
125     /**
126      * Sets SystemUpdaterSample app state change callback. Value of {@code state} will be one
127      * of the values from {@link UpdaterState}.
128      *
129      * @param onStateChangeCallback a callback with parameter {@code state}.
130      */
setOnStateChangeCallback(IntConsumer onStateChangeCallback)131     public void setOnStateChangeCallback(IntConsumer onStateChangeCallback) {
132         synchronized (mLock) {
133             this.mOnStateChangeCallback = onStateChangeCallback;
134         }
135     }
136 
getOnStateChangeCallback()137     private Optional<IntConsumer> getOnStateChangeCallback() {
138         synchronized (mLock) {
139             return mOnStateChangeCallback == null
140                     ? Optional.empty()
141                     : Optional.of(mOnStateChangeCallback);
142         }
143     }
144 
145     /**
146      * 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      */
setOnEngineStatusUpdateCallback(IntConsumer onStatusUpdateCallback)151     public void setOnEngineStatusUpdateCallback(IntConsumer onStatusUpdateCallback) {
152         synchronized (mLock) {
153             this.mOnEngineStatusUpdateCallback = onStatusUpdateCallback;
154         }
155     }
156 
getOnEngineStatusUpdateCallback()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      */
setOnEngineCompleteCallback(IntConsumer onComplete)171     public void setOnEngineCompleteCallback(IntConsumer onComplete) {
172         synchronized (mLock) {
173             this.mOnEngineCompleteCallback = onComplete;
174         }
175     }
176 
getOnEngineCompleteCallback()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      */
setOnProgressUpdateCallback(DoubleConsumer onProgressCallback)190     public void setOnProgressUpdateCallback(DoubleConsumer onProgressCallback) {
191         synchronized (mLock) {
192             this.mOnProgressUpdateCallback = onProgressCallback;
193         }
194     }
195 
getOnProgressUpdateCallback()196     private Optional<DoubleConsumer> getOnProgressUpdateCallback() {
197         synchronized (mLock) {
198             return mOnProgressUpdateCallback == null
199                     ? Optional.empty()
200                     : Optional.of(mOnProgressUpdateCallback);
201         }
202     }
203 
204     /**
205      * Suspend running update.
206      */
suspend()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      */
resume()216     public synchronized void resume() throws UpdaterState.InvalidTransitionException {
217         Log.d(TAG, "resume invoked");
218         setUpdaterState(UpdaterState.RUNNING);
219         updateEngineReApplyPayload();
220     }
221 
222     /**
223      * Updates {@link this.mState} and if state is changed,
224      * it also notifies {@link this.mOnStateChangeCallback}.
225      */
setUpdaterState(int newUpdaterState)226     private void setUpdaterState(int newUpdaterState)
227             throws UpdaterState.InvalidTransitionException {
228         Log.d(TAG, "setUpdaterState invoked newState=" + newUpdaterState);
229         int previousState = mUpdaterState.get();
230         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      */
setUpdaterStateSilent(int newUpdaterState)240     private void setUpdaterStateSilent(int newUpdaterState) {
241         try {
242             setUpdaterState(newUpdaterState);
243         } catch (UpdaterState.InvalidTransitionException e) {
244             // 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);
247         }
248     }
249 
250     /**
251      * Creates new UpdaterState, assigns it to {@link this.mUpdaterState},
252      * and notifies callbacks.
253      */
initializeUpdateState(int state)254     private void initializeUpdateState(int state) {
255         this.mUpdaterState = new UpdaterState(state);
256         getOnStateChangeCallback().ifPresent(callback -> callback.accept(state));
257     }
258 
259     /**
260      * Requests update engine to stop any ongoing update. If an update has been applied,
261      * leave it as is.
262      */
cancelRunningUpdate()263     public synchronized void cancelRunningUpdate() throws UpdaterState.InvalidTransitionException {
264         Log.d(TAG, "cancelRunningUpdate invoked");
265         setUpdaterState(UpdaterState.IDLE);
266         mUpdateEngine.cancel();
267     }
268 
269     /**
270      * Resets update engine to IDLE state. If an update has been applied it reverts it.
271      */
resetUpdate()272     public synchronized void resetUpdate() throws UpdaterState.InvalidTransitionException {
273         Log.d(TAG, "resetUpdate invoked");
274         setUpdaterState(UpdaterState.IDLE);
275         mUpdateEngine.resetStatus();
276     }
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      */
applyUpdate(Context context, UpdateConfig config)284     public synchronized void applyUpdate(Context context, UpdateConfig config)
285             throws UpdaterState.InvalidTransitionException {
286         mEngineErrorCode.set(UpdateEngineErrorCodes.UNKNOWN);
287         setUpdaterState(UpdaterState.RUNNING);
288 
289         synchronized (mLock) {
290             // Cleaning up previous update data.
291             mLastUpdateData = null;
292         }
293 
294         if (!config.getAbConfig().getForceSwitchSlot()) {
295             mManualSwitchSlotRequired.set(true);
296         } else {
297             mManualSwitchSlotRequired.set(false);
298         }
299 
300         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);
304                 setUpdaterStateSilent(UpdaterState.ERROR);
305                 return;
306             }
307             updateEngineApplyPayload(UpdateData.builder()
308                     .setExtraProperties(prepareExtraProperties(config))
309                     .setPayload(payloadSpec)
310                     .build());
311         });
312     }
313 
prepareExtraProperties(UpdateConfig config)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         }
322         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         }
328         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>
340      */
updateEngineApplyPayload(UpdateData update)341     private void updateEngineApplyPayload(UpdateData update) {
342         Log.d(TAG, "updateEngineApplyPayload invoked with url " + update.mPayload.getUrl());
343 
344         synchronized (mLock) {
345             mLastUpdateData = update;
346         }
347 
348         ArrayList<String> properties = new ArrayList<>(update.getPayload().getProperties());
349         properties.addAll(update.getExtraProperties());
350 
351         try {
352             mUpdateEngine.applyPayload(
353                     update.getPayload().getUrl(),
354                     update.getPayload().getOffset(),
355                     update.getPayload().getSize(),
356                     properties.toArray(new String[0]));
357         } catch (Exception e) {
358             Log.e(TAG, "UpdateEngine failed to apply the update", e);
359             setUpdaterStateSilent(UpdaterState.ERROR);
360         }
361     }
362 
363     /**
364      * Re-applies {@link this.mLastUpdateData} to update_engine.
365      */
updateEngineReApplyPayload()366     private void updateEngineReApplyPayload() {
367         Log.d(TAG, "updateEngineReApplyPayload invoked");
368         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 
380     /**
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      */
setSwitchSlotOnReboot()392     public synchronized void setSwitchSlotOnReboot() {
393         Log.d(TAG, "setSwitchSlotOnReboot invoked");
394 
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 
400         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         }
408         // PROPERTY_SKIP_POST_INSTALL should be passed on to skip post-installation hooks.
409         builder.setExtraProperties(
410                 Collections.singletonList(UpdateEngineProperties.PROPERTY_SKIP_POST_INSTALL));
411         // UpdateEngine sets property SWITCH_SLOT_ON_REBOOT=1 by default.
412         // HTTP headers are not required, UpdateEngine is not expected to stream payload.
413         updateEngineApplyPayload(builder.build());
414     }
415 
416     /**
417      * 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.
421      */
synchronizeUpdaterStateWithUpdateEngineStatus()422     private void synchronizeUpdaterStateWithUpdateEngineStatus() {
423         Log.d(TAG, "synchronizeUpdaterStateWithUpdateEngineStatus is invoked.");
424 
425         int state = mUpdaterState.get();
426         int engineStatus = mUpdateEngineStatus.get();
427 
428         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);
432             return;
433         }
434 
435         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.");
472         }
473     }
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      *
482      * @param status   one of {@link UpdateEngine.UpdateStatusConstants}.
483      * @param progress a number from 0.0 to 1.0.
484      */
onStatusUpdate(int status, float progress)485     private void onStatusUpdate(int status, float progress) {
486         Log.d(TAG, String.format(
487                 "onStatusUpdate invoked, status=%s, progress=%.2f",
488                 status,
489                 progress));
490 
491         int previousStatus = mUpdateEngineStatus.get();
492         mUpdateEngineStatus.set(status);
493         mProgress.set(progress);
494 
495         if (!mStateSynchronized.getAndSet(true)) {
496             // We synchronize state with engine status once
497             // only when sample app is bound to UpdateEngine.
498             synchronizeUpdaterStateWithUpdateEngineStatus();
499         }
500 
501         getOnProgressUpdateCallback().ifPresent(callback -> callback.accept(mProgress.get()));
502 
503         if (previousStatus != status) {
504             getOnEngineStatusUpdateCallback().ifPresent(callback -> callback.accept(status));
505         }
506     }
507 
onPayloadApplicationComplete(int errorCode)508     private void onPayloadApplicationComplete(int errorCode) {
509         Log.d(TAG, "onPayloadApplicationComplete invoked, errorCode=" + errorCode);
510         mEngineErrorCode.set(errorCode);
511         if (errorCode == UpdateEngine.ErrorCodeConstants.SUCCESS
512                 || errorCode == UpdateEngineErrorCodes.UPDATED_BUT_NOT_ACTIVE) {
513             setUpdaterStateSilent(isManualSwitchSlotRequired()
514                     ? UpdaterState.SLOT_SWITCH_REQUIRED
515                     : UpdaterState.REBOOT_REQUIRED);
516         } else if (errorCode != UpdateEngineErrorCodes.USER_CANCELLED) {
517             setUpdaterStateSilent(UpdaterState.ERROR);
518         }
519 
520         getOnEngineCompleteCallback()
521                 .ifPresent(callback -> callback.accept(errorCode));
522     }
523 
524     /**
525      * Helper class to delegate {@code update_engine} callback invocations to UpdateManager.
526      */
527     class UpdateEngineCallbackImpl extends UpdateEngineCallback {
528         @Override
onStatusUpdate(int status, float percent)529         public void onStatusUpdate(int status, float percent) {
530             UpdateManager.this.onStatusUpdate(status, percent);
531         }
532 
533         @Override
onPayloadApplicationComplete(int errorCode)534         public void onPayloadApplicationComplete(int errorCode) {
535             UpdateManager.this.onPayloadApplicationComplete(errorCode);
536         }
537     }
538 
539     /**
540      * 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 
builder()550         public static Builder builder() {
551             return new Builder();
552         }
553 
UpdateData(Builder builder)554         UpdateData(Builder builder) {
555             this.mPayload = builder.mPayload;
556             this.mExtraProperties = ImmutableList.copyOf(builder.mExtraProperties);
557         }
558 
getPayload()559         public PayloadSpec getPayload() {
560             return mPayload;
561         }
562 
getExtraProperties()563         public ImmutableList<String> getExtraProperties() {
564             return mExtraProperties;
565         }
566 
toBuilder()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 
setPayload(PayloadSpec payload)577             public Builder setPayload(PayloadSpec payload) {
578                 this.mPayload = payload;
579                 return this;
580             }
581 
setExtraProperties(List<String> extraProperties)582             public Builder setExtraProperties(List<String> extraProperties) {
583                 this.mExtraProperties = new ArrayList<>(extraProperties);
584                 return this;
585             }
586 
addExtraProperty(String property)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 
build()595             public UpdateData build() {
596                 return new UpdateData(this);
597             }
598         }
599     }
600 
601 }
602