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