blob: d6a6ce3f5171faa31aa7e0fc943a6d478d801c24 [file] [log] [blame]
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -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.ui;
18
19import android.app.Activity;
20import android.app.AlertDialog;
21import android.os.Build;
22import android.os.Bundle;
23import android.os.UpdateEngine;
24import android.os.UpdateEngineCallback;
25import android.util.Log;
26import android.view.View;
27import android.widget.ArrayAdapter;
28import android.widget.Button;
29import android.widget.ProgressBar;
30import android.widget.Spinner;
31import android.widget.TextView;
32import android.widget.Toast;
33
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -070034import com.example.android.systemupdatersample.PayloadSpec;
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -070035import com.example.android.systemupdatersample.R;
36import com.example.android.systemupdatersample.UpdateConfig;
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -070037import com.example.android.systemupdatersample.util.PayloadSpecs;
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -070038import com.example.android.systemupdatersample.util.UpdateConfigs;
39import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes;
40import com.example.android.systemupdatersample.util.UpdateEngineStatuses;
41
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -070042import java.io.IOException;
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -070043import java.util.List;
44import java.util.concurrent.atomic.AtomicInteger;
45
46/**
47 * UI for SystemUpdaterSample app.
48 */
49public class MainActivity extends Activity {
50
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -070051 private static final String TAG = "MainActivity";
52
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -070053 private TextView mTextViewBuild;
54 private Spinner mSpinnerConfigs;
55 private TextView mTextViewConfigsDirHint;
56 private Button mButtonReload;
57 private Button mButtonApplyConfig;
58 private Button mButtonStop;
59 private Button mButtonReset;
60 private ProgressBar mProgressBar;
61 private TextView mTextViewStatus;
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -070062 private TextView mTextViewCompletion;
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -070063
64 private List<UpdateConfig> mConfigs;
65 private AtomicInteger mUpdateEngineStatus =
66 new AtomicInteger(UpdateEngine.UpdateStatusConstants.IDLE);
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -070067
68 /**
69 * Listen to {@code update_engine} events.
70 */
71 private UpdateEngineCallbackImpl mUpdateEngineCallback = new UpdateEngineCallbackImpl();
72
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -070073 private final UpdateEngine mUpdateEngine = new UpdateEngine();
74
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -070075 @Override
76 protected void onCreate(Bundle savedInstanceState) {
77 super.onCreate(savedInstanceState);
78 setContentView(R.layout.activity_main);
79
80 this.mTextViewBuild = findViewById(R.id.textViewBuild);
81 this.mSpinnerConfigs = findViewById(R.id.spinnerConfigs);
82 this.mTextViewConfigsDirHint = findViewById(R.id.textViewConfigsDirHint);
83 this.mButtonReload = findViewById(R.id.buttonReload);
84 this.mButtonApplyConfig = findViewById(R.id.buttonApplyConfig);
85 this.mButtonStop = findViewById(R.id.buttonStop);
86 this.mButtonReset = findViewById(R.id.buttonReset);
87 this.mProgressBar = findViewById(R.id.progressBar);
88 this.mTextViewStatus = findViewById(R.id.textViewStatus);
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -070089 this.mTextViewCompletion = findViewById(R.id.textViewCompletion);
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -070090
91 this.mTextViewConfigsDirHint.setText(UpdateConfigs.getConfigsRoot(this));
92
93 uiReset();
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -070094 loadUpdateConfigs();
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -070095
96 this.mUpdateEngine.bind(mUpdateEngineCallback);
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -070097 }
98
99 @Override
100 protected void onDestroy() {
101 this.mUpdateEngine.unbind();
102 super.onDestroy();
103 }
104
105 /**
106 * reload button is clicked
107 */
108 public void onReloadClick(View view) {
109 loadUpdateConfigs();
110 }
111
112 /**
113 * view config button is clicked
114 */
115 public void onViewConfigClick(View view) {
116 UpdateConfig config = mConfigs.get(mSpinnerConfigs.getSelectedItemPosition());
117 new AlertDialog.Builder(this)
118 .setTitle(config.getName())
119 .setMessage(config.getRawJson())
120 .setPositiveButton(R.string.close, (dialog, id) -> dialog.dismiss())
121 .show();
122 }
123
124 /**
125 * apply config button is clicked
126 */
127 public void onApplyConfigClick(View view) {
128 new AlertDialog.Builder(this)
129 .setTitle("Apply Update")
130 .setMessage("Do you really want to apply this update?")
131 .setIcon(android.R.drawable.ic_dialog_alert)
132 .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> {
133 uiSetUpdating();
134 applyUpdate(getSelectedConfig());
135 })
136 .setNegativeButton(android.R.string.cancel, null)
137 .show();
138 }
139
140 /**
141 * stop button clicked
142 */
143 public void onStopClick(View view) {
144 new AlertDialog.Builder(this)
145 .setTitle("Stop Update")
146 .setMessage("Do you really want to cancel running update?")
147 .setIcon(android.R.drawable.ic_dialog_alert)
148 .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> {
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700149 stopRunningUpdate();
150 })
151 .setNegativeButton(android.R.string.cancel, null).show();
152 }
153
154 /**
155 * reset button clicked
156 */
157 public void onResetClick(View view) {
158 new AlertDialog.Builder(this)
159 .setTitle("Reset Update")
160 .setMessage("Do you really want to cancel running update"
161 + " and restore old version?")
162 .setIcon(android.R.drawable.ic_dialog_alert)
163 .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> {
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700164 resetUpdate();
165 })
166 .setNegativeButton(android.R.string.cancel, null).show();
167 }
168
169 /**
170 * Invoked when anything changes. The value of {@code status} will
171 * be one of the values from {@link UpdateEngine.UpdateStatusConstants},
172 * and {@code percent} will be from {@code 0.0} to {@code 1.0}.
173 */
174 private void onStatusUpdate(int status, float percent) {
175 mProgressBar.setProgress((int) (100 * percent));
176 if (mUpdateEngineStatus.get() != status) {
177 mUpdateEngineStatus.set(status);
178 runOnUiThread(() -> {
179 Log.e("UpdateEngine", "StatusUpdate - status="
180 + UpdateEngineStatuses.getStatusText(status)
181 + "/" + status);
182 setUiStatus(status);
183 Toast.makeText(this, "Update Status changed", Toast.LENGTH_LONG)
184 .show();
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700185 if (status != UpdateEngine.UpdateStatusConstants.IDLE) {
186 Log.d(TAG, "status changed, setting ui to updating mode");
187 uiSetUpdating();
188 } else {
189 Log.d(TAG, "status changed, resetting ui");
190 uiReset();
191 }
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700192 });
193 }
194 }
195
196 /**
197 * Invoked when the payload has been applied, whether successfully or
198 * unsuccessfully. The value of {@code errorCode} will be one of the
199 * values from {@link UpdateEngine.ErrorCodeConstants}.
200 */
201 private void onPayloadApplicationComplete(int errorCode) {
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700202 final String state = UpdateEngineErrorCodes.isUpdateSucceeded(errorCode)
203 ? "SUCCESS"
204 : "FAILURE";
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700205 runOnUiThread(() -> {
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700206 Log.i("UpdateEngine",
207 "Completed - errorCode="
208 + UpdateEngineErrorCodes.getCodeName(errorCode) + "/" + errorCode
209 + " " + state);
210 Toast.makeText(this, "Update completed", Toast.LENGTH_LONG).show();
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700211 setUiCompletion(errorCode);
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700212 });
213 }
214
215 /** resets ui */
216 private void uiReset() {
217 mTextViewBuild.setText(Build.DISPLAY);
218 mSpinnerConfigs.setEnabled(true);
219 mButtonReload.setEnabled(true);
220 mButtonApplyConfig.setEnabled(true);
221 mButtonStop.setEnabled(false);
222 mButtonReset.setEnabled(false);
223 mProgressBar.setProgress(0);
224 mProgressBar.setEnabled(false);
225 mProgressBar.setVisibility(ProgressBar.INVISIBLE);
226 mTextViewStatus.setText(R.string.unknown);
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700227 mTextViewCompletion.setText(R.string.unknown);
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700228 }
229
230 /** sets ui updating mode */
231 private void uiSetUpdating() {
232 mTextViewBuild.setText(Build.DISPLAY);
233 mSpinnerConfigs.setEnabled(false);
234 mButtonReload.setEnabled(false);
235 mButtonApplyConfig.setEnabled(false);
236 mButtonStop.setEnabled(true);
237 mProgressBar.setEnabled(true);
238 mButtonReset.setEnabled(true);
239 mProgressBar.setVisibility(ProgressBar.VISIBLE);
240 }
241
242 /**
243 * loads json configurations from configs dir that is defined in {@link UpdateConfigs}.
244 */
245 private void loadUpdateConfigs() {
246 mConfigs = UpdateConfigs.getUpdateConfigs(this);
247 loadConfigsToSpinner(mConfigs);
248 }
249
250 /**
251 * @param status update engine status code
252 */
253 private void setUiStatus(int status) {
254 String statusText = UpdateEngineStatuses.getStatusText(status);
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700255 mTextViewStatus.setText(statusText + "/" + status);
256 }
257
258 /**
259 * @param errorCode update engine error code
260 */
261 private void setUiCompletion(int errorCode) {
262 final String state = UpdateEngineErrorCodes.isUpdateSucceeded(errorCode)
263 ? "SUCCESS"
264 : "FAILURE";
265 String errorText = UpdateEngineErrorCodes.getCodeName(errorCode);
266 mTextViewCompletion.setText(state + " " + errorText + "/" + errorCode);
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700267 }
268
269 private void loadConfigsToSpinner(List<UpdateConfig> configs) {
270 String[] spinnerArray = UpdateConfigs.configsToNames(configs);
271 ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<>(this,
272 android.R.layout.simple_spinner_item,
273 spinnerArray);
274 spinnerArrayAdapter.setDropDownViewResource(android.R.layout
275 .simple_spinner_dropdown_item);
276 mSpinnerConfigs.setAdapter(spinnerArrayAdapter);
277 }
278
279 private UpdateConfig getSelectedConfig() {
280 return mConfigs.get(mSpinnerConfigs.getSelectedItemPosition());
281 }
282
283 /**
284 * Applies the given update
285 */
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700286 private void applyUpdate(final UpdateConfig config) {
Zhomart Mukhamejanov963e3ee2018-04-26 21:07:05 -0700287 if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING) {
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700288 PayloadSpec payload;
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700289 try {
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700290 payload = PayloadSpecs.forNonStreaming(config.getUpdatePackageFile());
291 } catch (IOException e) {
292 Log.e(TAG, "Error creating payload spec", e);
293 Toast.makeText(this, "Error creating payload spec", Toast.LENGTH_LONG)
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700294 .show();
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700295 return;
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700296 }
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700297 updateEngineApplyPayload(payload);
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700298 } else {
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700299 Log.d(TAG, "Starting PrepareStreamingService");
300 }
301 }
302
303 /**
304 * Applies given payload.
305 *
306 * UpdateEngine works asynchronously. This method doesn't wait until
307 * end of the update.
308 */
309 private void updateEngineApplyPayload(PayloadSpec payloadSpec) {
310 try {
311 mUpdateEngine.applyPayload(
312 payloadSpec.getUrl(),
313 payloadSpec.getOffset(),
314 payloadSpec.getSize(),
315 payloadSpec.getProperties().toArray(new String[0]));
316 } catch (Exception e) {
317 Log.e(TAG, "UpdateEngine failed to apply the update", e);
318 Toast.makeText(
319 this,
320 "UpdateEngine failed to apply the update",
321 Toast.LENGTH_LONG).show();
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700322 }
323 }
324
325 /**
326 * Requests update engine to stop any ongoing update. If an update has been applied,
327 * leave it as is.
328 */
329 private void stopRunningUpdate() {
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700330 try {
331 mUpdateEngine.cancel();
332 } catch (Exception e) {
333 Log.w(TAG, "UpdateEngine failed to stop the ongoing update", e);
334 }
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700335 }
336
337 /**
338 * Resets update engine to IDLE state. Requests to cancel any onging update, or to revert if an
339 * update has been applied.
340 */
341 private void resetUpdate() {
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700342 try {
343 mUpdateEngine.resetStatus();
344 } catch (Exception e) {
345 Log.w(TAG, "UpdateEngine failed to reset the update", e);
346 }
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700347 }
348
349 /**
Zhomart Mukhamejanovda7e2372018-05-01 12:50:55 -0700350 * Helper class to delegate {@code update_engine} callbacks to MainActivity
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700351 */
352 class UpdateEngineCallbackImpl extends UpdateEngineCallback {
353 @Override
354 public void onStatusUpdate(int status, float percent) {
355 MainActivity.this.onStatusUpdate(status, percent);
356 }
357
358 @Override
359 public void onPayloadApplicationComplete(int errorCode) {
360 MainActivity.this.onPayloadApplicationComplete(errorCode);
361 }
362 }
363
364}