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.ui;
18 
19 import android.app.Activity;
20 import android.app.AlertDialog;
21 import android.graphics.Color;
22 import android.os.Build;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.os.UpdateEngine;
26 import android.util.Log;
27 import android.view.View;
28 import android.widget.ArrayAdapter;
29 import android.widget.Button;
30 import android.widget.ProgressBar;
31 import android.widget.Spinner;
32 import android.widget.TextView;
33 
34 import com.example.android.systemupdatersample.R;
35 import com.example.android.systemupdatersample.UpdateConfig;
36 import com.example.android.systemupdatersample.UpdateManager;
37 import com.example.android.systemupdatersample.UpdaterState;
38 import com.example.android.systemupdatersample.util.UpdateConfigs;
39 import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes;
40 import com.example.android.systemupdatersample.util.UpdateEngineStatuses;
41 
42 import java.util.List;
43 
44 /**
45  * UI for SystemUpdaterSample app.
46  */
47 public class MainActivity extends Activity {
48 
49     private static final String TAG = "MainActivity";
50 
51     private TextView mTextViewBuild;
52     private Spinner mSpinnerConfigs;
53     private TextView mTextViewConfigsDirHint;
54     private Button mButtonReload;
55     private Button mButtonApplyConfig;
56     private Button mButtonStop;
57     private Button mButtonReset;
58     private Button mButtonSuspend;
59     private Button mButtonResume;
60     private ProgressBar mProgressBar;
61     private TextView mTextViewUpdaterState;
62     private TextView mTextViewEngineStatus;
63     private TextView mTextViewEngineErrorCode;
64     private TextView mTextViewUpdateInfo;
65     private Button mButtonSwitchSlot;
66 
67     private List<UpdateConfig> mConfigs;
68 
69     private final UpdateManager mUpdateManager =
70             new UpdateManager(new UpdateEngine(), new Handler());
71 
72     @Override
onCreate(Bundle savedInstanceState)73     protected void onCreate(Bundle savedInstanceState) {
74         super.onCreate(savedInstanceState);
75         setContentView(R.layout.activity_main);
76 
77         this.mTextViewBuild = findViewById(R.id.textViewBuild);
78         this.mSpinnerConfigs = findViewById(R.id.spinnerConfigs);
79         this.mTextViewConfigsDirHint = findViewById(R.id.textViewConfigsDirHint);
80         this.mButtonReload = findViewById(R.id.buttonReload);
81         this.mButtonApplyConfig = findViewById(R.id.buttonApplyConfig);
82         this.mButtonStop = findViewById(R.id.buttonStop);
83         this.mButtonReset = findViewById(R.id.buttonReset);
84         this.mButtonSuspend = findViewById(R.id.buttonSuspend);
85         this.mButtonResume = findViewById(R.id.buttonResume);
86         this.mProgressBar = findViewById(R.id.progressBar);
87         this.mTextViewUpdaterState = findViewById(R.id.textViewUpdaterState);
88         this.mTextViewEngineStatus = findViewById(R.id.textViewEngineStatus);
89         this.mTextViewEngineErrorCode = findViewById(R.id.textViewEngineErrorCode);
90         this.mTextViewUpdateInfo = findViewById(R.id.textViewUpdateInfo);
91         this.mButtonSwitchSlot = findViewById(R.id.buttonSwitchSlot);
92 
93         this.mTextViewConfigsDirHint.setText(UpdateConfigs.getConfigsRoot(this));
94 
95         uiResetWidgets();
96         loadUpdateConfigs();
97 
98         this.mUpdateManager.setOnStateChangeCallback(this::onUpdaterStateChange);
99         this.mUpdateManager.setOnEngineStatusUpdateCallback(this::onEngineStatusUpdate);
100         this.mUpdateManager.setOnEngineCompleteCallback(this::onEnginePayloadApplicationComplete);
101         this.mUpdateManager.setOnProgressUpdateCallback(this::onProgressUpdate);
102     }
103 
104     @Override
onDestroy()105     protected void onDestroy() {
106         this.mUpdateManager.setOnEngineStatusUpdateCallback(null);
107         this.mUpdateManager.setOnProgressUpdateCallback(null);
108         this.mUpdateManager.setOnEngineCompleteCallback(null);
109         super.onDestroy();
110     }
111 
112     @Override
onResume()113     protected void onResume() {
114         super.onResume();
115         // Binding to UpdateEngine invokes onStatusUpdate callback,
116         // persisted updater state has to be loaded and prepared beforehand.
117         this.mUpdateManager.bind();
118     }
119 
120     @Override
onPause()121     protected void onPause() {
122         this.mUpdateManager.unbind();
123         super.onPause();
124     }
125 
126     /**
127      * reload button is clicked
128      */
onReloadClick(View view)129     public void onReloadClick(View view) {
130         loadUpdateConfigs();
131     }
132 
133     /**
134      * view config button is clicked
135      */
onViewConfigClick(View view)136     public void onViewConfigClick(View view) {
137         UpdateConfig config = mConfigs.get(mSpinnerConfigs.getSelectedItemPosition());
138         new AlertDialog.Builder(this)
139                 .setTitle(config.getName())
140                 .setMessage(config.getRawJson())
141                 .setPositiveButton(R.string.close, (dialog, id) -> dialog.dismiss())
142                 .show();
143     }
144 
145     /**
146      * apply config button is clicked
147      */
onApplyConfigClick(View view)148     public void onApplyConfigClick(View view) {
149         new AlertDialog.Builder(this)
150                 .setTitle("Apply Update")
151                 .setMessage("Do you really want to apply this update?")
152                 .setIcon(android.R.drawable.ic_dialog_alert)
153                 .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> {
154                     uiResetWidgets();
155                     uiResetEngineText();
156                     applyUpdate(getSelectedConfig());
157                 })
158                 .setNegativeButton(android.R.string.cancel, null)
159                 .show();
160     }
161 
applyUpdate(UpdateConfig config)162     private void applyUpdate(UpdateConfig config) {
163         try {
164             mUpdateManager.applyUpdate(this, config);
165         } catch (UpdaterState.InvalidTransitionException e) {
166             Log.e(TAG, "Failed to apply update " + config.getName(), e);
167         }
168     }
169 
170     /**
171      * stop button clicked
172      */
onStopClick(View view)173     public void onStopClick(View view) {
174         new AlertDialog.Builder(this)
175                 .setTitle("Stop Update")
176                 .setMessage("Do you really want to cancel running update?")
177                 .setIcon(android.R.drawable.ic_dialog_alert)
178                 .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> {
179                     cancelRunningUpdate();
180                 })
181                 .setNegativeButton(android.R.string.cancel, null).show();
182     }
183 
cancelRunningUpdate()184     private void cancelRunningUpdate() {
185         try {
186             mUpdateManager.cancelRunningUpdate();
187         } catch (UpdaterState.InvalidTransitionException e) {
188             Log.e(TAG, "Failed to cancel running update", e);
189         }
190     }
191 
192     /**
193      * reset button clicked
194      */
onResetClick(View view)195     public void onResetClick(View view) {
196         new AlertDialog.Builder(this)
197                 .setTitle("Reset Update")
198                 .setMessage("Do you really want to cancel running update"
199                         + " and restore old version?")
200                 .setIcon(android.R.drawable.ic_dialog_alert)
201                 .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> {
202                     resetUpdate();
203                 })
204                 .setNegativeButton(android.R.string.cancel, null).show();
205     }
206 
resetUpdate()207     private void resetUpdate() {
208         try {
209             mUpdateManager.resetUpdate();
210         } catch (UpdaterState.InvalidTransitionException e) {
211             Log.e(TAG, "Failed to reset update", e);
212         }
213     }
214 
215     /**
216      * suspend button clicked
217      */
onSuspendClick(View view)218     public void onSuspendClick(View view) {
219         try {
220             mUpdateManager.suspend();
221         } catch (UpdaterState.InvalidTransitionException e) {
222             Log.e(TAG, "Failed to suspend running update", e);
223         }
224     }
225 
226     /**
227      * resume button clicked
228      */
onResumeClick(View view)229     public void onResumeClick(View view) {
230         try {
231             uiResetWidgets();
232             uiResetEngineText();
233             mUpdateManager.resume();
234         } catch (UpdaterState.InvalidTransitionException e) {
235             Log.e(TAG, "Failed to resume running update", e);
236         }
237     }
238 
239     /**
240      * switch slot button clicked
241      */
onSwitchSlotClick(View view)242     public void onSwitchSlotClick(View view) {
243         uiResetWidgets();
244         mUpdateManager.setSwitchSlotOnReboot();
245     }
246 
247     /**
248      * Invoked when SystemUpdaterSample app state changes.
249      * Value of {@code state} will be one of the
250      * values from {@link UpdaterState}.
251      */
onUpdaterStateChange(int state)252     private void onUpdaterStateChange(int state) {
253         Log.i(TAG, "UpdaterStateChange state="
254                 + UpdaterState.getStateText(state)
255                 + "/" + state);
256         runOnUiThread(() -> {
257             setUiUpdaterState(state);
258 
259             if (state == UpdaterState.IDLE) {
260                 uiStateIdle();
261             } else if (state == UpdaterState.RUNNING) {
262                 uiStateRunning();
263             } else if (state == UpdaterState.PAUSED) {
264                 uiStatePaused();
265             } else if (state == UpdaterState.ERROR) {
266                 uiStateError();
267             } else if (state == UpdaterState.SLOT_SWITCH_REQUIRED) {
268                 uiStateSlotSwitchRequired();
269             } else if (state == UpdaterState.REBOOT_REQUIRED) {
270                 uiStateRebootRequired();
271             }
272         });
273     }
274 
275     /**
276      * Invoked when {@link UpdateEngine} status changes. Value of {@code status} will
277      * be one of the values from {@link UpdateEngine.UpdateStatusConstants}.
278      */
onEngineStatusUpdate(int status)279     private void onEngineStatusUpdate(int status) {
280         Log.i(TAG, "StatusUpdate - status="
281                 + UpdateEngineStatuses.getStatusText(status)
282                 + "/" + status);
283         runOnUiThread(() -> {
284             setUiEngineStatus(status);
285         });
286     }
287 
288     /**
289      * Invoked when the payload has been applied, whether successfully or
290      * unsuccessfully. The value of {@code errorCode} will be one of the
291      * values from {@link UpdateEngine.ErrorCodeConstants}.
292      */
onEnginePayloadApplicationComplete(int errorCode)293     private void onEnginePayloadApplicationComplete(int errorCode) {
294         final String completionState = UpdateEngineErrorCodes.isUpdateSucceeded(errorCode)
295                 ? "SUCCESS"
296                 : "FAILURE";
297         Log.i(TAG,
298                 "PayloadApplicationCompleted - errorCode="
299                         + UpdateEngineErrorCodes.getCodeName(errorCode) + "/" + errorCode
300                         + " " + completionState);
301         runOnUiThread(() -> {
302             setUiEngineErrorCode(errorCode);
303         });
304     }
305 
306     /**
307      * Invoked when update progress changes.
308      */
onProgressUpdate(double progress)309     private void onProgressUpdate(double progress) {
310         mProgressBar.setProgress((int) (100 * progress));
311     }
312 
313     /** resets ui */
uiResetWidgets()314     private void uiResetWidgets() {
315         mTextViewBuild.setText(Build.DISPLAY);
316         mSpinnerConfigs.setEnabled(false);
317         mButtonReload.setEnabled(false);
318         mButtonApplyConfig.setEnabled(false);
319         mButtonStop.setEnabled(false);
320         mButtonReset.setEnabled(false);
321         mButtonSuspend.setEnabled(false);
322         mButtonResume.setEnabled(false);
323         mProgressBar.setEnabled(false);
324         mProgressBar.setVisibility(ProgressBar.INVISIBLE);
325         mButtonSwitchSlot.setEnabled(false);
326         mTextViewUpdateInfo.setTextColor(Color.parseColor("#aaaaaa"));
327     }
328 
uiResetEngineText()329     private void uiResetEngineText() {
330         mTextViewEngineStatus.setText(R.string.unknown);
331         mTextViewEngineErrorCode.setText(R.string.unknown);
332         // Note: Do not reset mTextViewUpdaterState; UpdateManager notifies updater state properly.
333     }
334 
uiStateIdle()335     private void uiStateIdle() {
336         uiResetWidgets();
337         mButtonReset.setEnabled(true);
338         mSpinnerConfigs.setEnabled(true);
339         mButtonReload.setEnabled(true);
340         mButtonApplyConfig.setEnabled(true);
341         mProgressBar.setProgress(0);
342     }
343 
uiStateRunning()344     private void uiStateRunning() {
345         uiResetWidgets();
346         mProgressBar.setEnabled(true);
347         mProgressBar.setVisibility(ProgressBar.VISIBLE);
348         mButtonStop.setEnabled(true);
349         mButtonSuspend.setEnabled(true);
350     }
351 
uiStatePaused()352     private void uiStatePaused() {
353         uiResetWidgets();
354         mButtonReset.setEnabled(true);
355         mProgressBar.setEnabled(true);
356         mProgressBar.setVisibility(ProgressBar.VISIBLE);
357         mButtonResume.setEnabled(true);
358     }
359 
uiStateSlotSwitchRequired()360     private void uiStateSlotSwitchRequired() {
361         uiResetWidgets();
362         mButtonReset.setEnabled(true);
363         mProgressBar.setEnabled(true);
364         mProgressBar.setVisibility(ProgressBar.VISIBLE);
365         mButtonSwitchSlot.setEnabled(true);
366         mTextViewUpdateInfo.setTextColor(Color.parseColor("#777777"));
367     }
368 
uiStateError()369     private void uiStateError() {
370         uiResetWidgets();
371         mButtonReset.setEnabled(true);
372         mProgressBar.setEnabled(true);
373         mProgressBar.setVisibility(ProgressBar.VISIBLE);
374     }
375 
uiStateRebootRequired()376     private void uiStateRebootRequired() {
377         uiResetWidgets();
378         mButtonReset.setEnabled(true);
379     }
380 
381     /**
382      * loads json configurations from configs dir that is defined in {@link UpdateConfigs}.
383      */
loadUpdateConfigs()384     private void loadUpdateConfigs() {
385         mConfigs = UpdateConfigs.getUpdateConfigs(this);
386         loadConfigsToSpinner(mConfigs);
387     }
388 
389     /**
390      * @param status update engine status code
391      */
setUiEngineStatus(int status)392     private void setUiEngineStatus(int status) {
393         String statusText = UpdateEngineStatuses.getStatusText(status);
394         mTextViewEngineStatus.setText(statusText + "/" + status);
395     }
396 
397     /**
398      * @param errorCode update engine error code
399      */
setUiEngineErrorCode(int errorCode)400     private void setUiEngineErrorCode(int errorCode) {
401         String errorText = UpdateEngineErrorCodes.getCodeName(errorCode);
402         mTextViewEngineErrorCode.setText(errorText + "/" + errorCode);
403     }
404 
405     /**
406      * @param state updater sample state
407      */
setUiUpdaterState(int state)408     private void setUiUpdaterState(int state) {
409         String stateText = UpdaterState.getStateText(state);
410         mTextViewUpdaterState.setText(stateText + "/" + state);
411     }
412 
loadConfigsToSpinner(List<UpdateConfig> configs)413     private void loadConfigsToSpinner(List<UpdateConfig> configs) {
414         String[] spinnerArray = UpdateConfigs.configsToNames(configs);
415         ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<>(this,
416                 android.R.layout.simple_spinner_item,
417                 spinnerArray);
418         spinnerArrayAdapter.setDropDownViewResource(android.R.layout
419                 .simple_spinner_dropdown_item);
420         mSpinnerConfigs.setAdapter(spinnerArrayAdapter);
421     }
422 
getSelectedConfig()423     private UpdateConfig getSelectedConfig() {
424         return mConfigs.get(mSpinnerConfigs.getSelectedItemPosition());
425     }
426 
427 }
428