blob: 170825635c94c0cec2a5f0561913a56340ccc704 [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 Mukhamejanov0dd5a832018-04-23 11:38:54 -070037import com.example.android.systemupdatersample.services.PrepareStreamingService;
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -070038import com.example.android.systemupdatersample.util.PayloadSpecs;
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -070039import com.example.android.systemupdatersample.util.UpdateConfigs;
40import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes;
41import com.example.android.systemupdatersample.util.UpdateEngineStatuses;
42
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -070043import java.io.IOException;
Zhomart Mukhamejanov6aa5fb02018-05-09 14:28:49 -070044import java.util.ArrayList;
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -070045import java.util.List;
46import java.util.concurrent.atomic.AtomicInteger;
47
48/**
49 * UI for SystemUpdaterSample app.
50 */
51public class MainActivity extends Activity {
52
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -070053 private static final String TAG = "MainActivity";
54
Zhomart Mukhamejanov6aa5fb02018-05-09 14:28:49 -070055 /** HTTP Header: User-Agent; it will be sent to the server when streaming the payload. */
56 private static final String HTTP_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
57 + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36";
58
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -070059 private TextView mTextViewBuild;
60 private Spinner mSpinnerConfigs;
61 private TextView mTextViewConfigsDirHint;
62 private Button mButtonReload;
63 private Button mButtonApplyConfig;
64 private Button mButtonStop;
65 private Button mButtonReset;
66 private ProgressBar mProgressBar;
67 private TextView mTextViewStatus;
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -070068 private TextView mTextViewCompletion;
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -070069
70 private List<UpdateConfig> mConfigs;
71 private AtomicInteger mUpdateEngineStatus =
72 new AtomicInteger(UpdateEngine.UpdateStatusConstants.IDLE);
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -070073
74 /**
75 * Listen to {@code update_engine} events.
76 */
77 private UpdateEngineCallbackImpl mUpdateEngineCallback = new UpdateEngineCallbackImpl();
78
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -070079 private final UpdateEngine mUpdateEngine = new UpdateEngine();
80
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -070081 @Override
82 protected void onCreate(Bundle savedInstanceState) {
83 super.onCreate(savedInstanceState);
84 setContentView(R.layout.activity_main);
85
86 this.mTextViewBuild = findViewById(R.id.textViewBuild);
87 this.mSpinnerConfigs = findViewById(R.id.spinnerConfigs);
88 this.mTextViewConfigsDirHint = findViewById(R.id.textViewConfigsDirHint);
89 this.mButtonReload = findViewById(R.id.buttonReload);
90 this.mButtonApplyConfig = findViewById(R.id.buttonApplyConfig);
91 this.mButtonStop = findViewById(R.id.buttonStop);
92 this.mButtonReset = findViewById(R.id.buttonReset);
93 this.mProgressBar = findViewById(R.id.progressBar);
94 this.mTextViewStatus = findViewById(R.id.textViewStatus);
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -070095 this.mTextViewCompletion = findViewById(R.id.textViewCompletion);
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -070096
97 this.mTextViewConfigsDirHint.setText(UpdateConfigs.getConfigsRoot(this));
98
99 uiReset();
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700100 loadUpdateConfigs();
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700101
102 this.mUpdateEngine.bind(mUpdateEngineCallback);
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700103 }
104
105 @Override
106 protected void onDestroy() {
107 this.mUpdateEngine.unbind();
108 super.onDestroy();
109 }
110
111 /**
112 * reload button is clicked
113 */
114 public void onReloadClick(View view) {
115 loadUpdateConfigs();
116 }
117
118 /**
119 * view config button is clicked
120 */
121 public void onViewConfigClick(View view) {
122 UpdateConfig config = mConfigs.get(mSpinnerConfigs.getSelectedItemPosition());
123 new AlertDialog.Builder(this)
124 .setTitle(config.getName())
125 .setMessage(config.getRawJson())
126 .setPositiveButton(R.string.close, (dialog, id) -> dialog.dismiss())
127 .show();
128 }
129
130 /**
131 * apply config button is clicked
132 */
133 public void onApplyConfigClick(View view) {
134 new AlertDialog.Builder(this)
135 .setTitle("Apply Update")
136 .setMessage("Do you really want to apply this update?")
137 .setIcon(android.R.drawable.ic_dialog_alert)
138 .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> {
139 uiSetUpdating();
140 applyUpdate(getSelectedConfig());
141 })
142 .setNegativeButton(android.R.string.cancel, null)
143 .show();
144 }
145
146 /**
147 * stop button clicked
148 */
149 public void onStopClick(View view) {
150 new AlertDialog.Builder(this)
151 .setTitle("Stop Update")
152 .setMessage("Do you really want to cancel running update?")
153 .setIcon(android.R.drawable.ic_dialog_alert)
154 .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> {
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700155 stopRunningUpdate();
156 })
157 .setNegativeButton(android.R.string.cancel, null).show();
158 }
159
160 /**
161 * reset button clicked
162 */
163 public void onResetClick(View view) {
164 new AlertDialog.Builder(this)
165 .setTitle("Reset Update")
166 .setMessage("Do you really want to cancel running update"
167 + " and restore old version?")
168 .setIcon(android.R.drawable.ic_dialog_alert)
169 .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> {
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700170 resetUpdate();
171 })
172 .setNegativeButton(android.R.string.cancel, null).show();
173 }
174
175 /**
176 * Invoked when anything changes. The value of {@code status} will
177 * be one of the values from {@link UpdateEngine.UpdateStatusConstants},
178 * and {@code percent} will be from {@code 0.0} to {@code 1.0}.
179 */
180 private void onStatusUpdate(int status, float percent) {
181 mProgressBar.setProgress((int) (100 * percent));
182 if (mUpdateEngineStatus.get() != status) {
183 mUpdateEngineStatus.set(status);
184 runOnUiThread(() -> {
185 Log.e("UpdateEngine", "StatusUpdate - status="
186 + UpdateEngineStatuses.getStatusText(status)
187 + "/" + status);
188 setUiStatus(status);
189 Toast.makeText(this, "Update Status changed", Toast.LENGTH_LONG)
190 .show();
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700191 if (status != UpdateEngine.UpdateStatusConstants.IDLE) {
192 Log.d(TAG, "status changed, setting ui to updating mode");
193 uiSetUpdating();
194 } else {
195 Log.d(TAG, "status changed, resetting ui");
196 uiReset();
197 }
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700198 });
199 }
200 }
201
202 /**
203 * Invoked when the payload has been applied, whether successfully or
204 * unsuccessfully. The value of {@code errorCode} will be one of the
205 * values from {@link UpdateEngine.ErrorCodeConstants}.
206 */
207 private void onPayloadApplicationComplete(int errorCode) {
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700208 final String state = UpdateEngineErrorCodes.isUpdateSucceeded(errorCode)
209 ? "SUCCESS"
210 : "FAILURE";
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700211 runOnUiThread(() -> {
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700212 Log.i("UpdateEngine",
213 "Completed - errorCode="
214 + UpdateEngineErrorCodes.getCodeName(errorCode) + "/" + errorCode
215 + " " + state);
216 Toast.makeText(this, "Update completed", Toast.LENGTH_LONG).show();
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700217 setUiCompletion(errorCode);
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700218 });
219 }
220
221 /** resets ui */
222 private void uiReset() {
223 mTextViewBuild.setText(Build.DISPLAY);
224 mSpinnerConfigs.setEnabled(true);
225 mButtonReload.setEnabled(true);
226 mButtonApplyConfig.setEnabled(true);
227 mButtonStop.setEnabled(false);
228 mButtonReset.setEnabled(false);
229 mProgressBar.setProgress(0);
230 mProgressBar.setEnabled(false);
231 mProgressBar.setVisibility(ProgressBar.INVISIBLE);
232 mTextViewStatus.setText(R.string.unknown);
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700233 mTextViewCompletion.setText(R.string.unknown);
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700234 }
235
236 /** sets ui updating mode */
237 private void uiSetUpdating() {
238 mTextViewBuild.setText(Build.DISPLAY);
239 mSpinnerConfigs.setEnabled(false);
240 mButtonReload.setEnabled(false);
241 mButtonApplyConfig.setEnabled(false);
242 mButtonStop.setEnabled(true);
243 mProgressBar.setEnabled(true);
244 mButtonReset.setEnabled(true);
245 mProgressBar.setVisibility(ProgressBar.VISIBLE);
246 }
247
248 /**
249 * loads json configurations from configs dir that is defined in {@link UpdateConfigs}.
250 */
251 private void loadUpdateConfigs() {
252 mConfigs = UpdateConfigs.getUpdateConfigs(this);
253 loadConfigsToSpinner(mConfigs);
254 }
255
256 /**
257 * @param status update engine status code
258 */
259 private void setUiStatus(int status) {
260 String statusText = UpdateEngineStatuses.getStatusText(status);
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700261 mTextViewStatus.setText(statusText + "/" + status);
262 }
263
264 /**
265 * @param errorCode update engine error code
266 */
267 private void setUiCompletion(int errorCode) {
268 final String state = UpdateEngineErrorCodes.isUpdateSucceeded(errorCode)
269 ? "SUCCESS"
270 : "FAILURE";
271 String errorText = UpdateEngineErrorCodes.getCodeName(errorCode);
272 mTextViewCompletion.setText(state + " " + errorText + "/" + errorCode);
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700273 }
274
275 private void loadConfigsToSpinner(List<UpdateConfig> configs) {
276 String[] spinnerArray = UpdateConfigs.configsToNames(configs);
277 ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<>(this,
278 android.R.layout.simple_spinner_item,
279 spinnerArray);
280 spinnerArrayAdapter.setDropDownViewResource(android.R.layout
281 .simple_spinner_dropdown_item);
282 mSpinnerConfigs.setAdapter(spinnerArrayAdapter);
283 }
284
285 private UpdateConfig getSelectedConfig() {
286 return mConfigs.get(mSpinnerConfigs.getSelectedItemPosition());
287 }
288
289 /**
290 * Applies the given update
291 */
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700292 private void applyUpdate(final UpdateConfig config) {
Zhomart Mukhamejanov963e3ee2018-04-26 21:07:05 -0700293 if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING) {
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700294 PayloadSpec payload;
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700295 try {
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700296 payload = PayloadSpecs.forNonStreaming(config.getUpdatePackageFile());
297 } catch (IOException e) {
298 Log.e(TAG, "Error creating payload spec", e);
299 Toast.makeText(this, "Error creating payload spec", Toast.LENGTH_LONG)
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700300 .show();
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700301 return;
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700302 }
Zhomart Mukhamejanov6aa5fb02018-05-09 14:28:49 -0700303 updateEngineApplyPayload(payload, null);
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700304 } else {
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700305 Log.d(TAG, "Starting PrepareStreamingService");
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700306 PrepareStreamingService.startService(this, config, (code, payloadSpec) -> {
307 if (code == PrepareStreamingService.RESULT_CODE_SUCCESS) {
Zhomart Mukhamejanov6aa5fb02018-05-09 14:28:49 -0700308 List<String> extraProperties = new ArrayList<>();
309 extraProperties.add("USER_AGENT=" + HTTP_USER_AGENT);
310 config.getStreamingMetadata()
311 .getAuthorization()
312 .ifPresent(s -> extraProperties.add("AUTHORIZATION=" + s));
313 updateEngineApplyPayload(payloadSpec, extraProperties);
Zhomart Mukhamejanov0dd5a832018-04-23 11:38:54 -0700314 } else {
315 Log.e(TAG, "PrepareStreamingService failed, result code is " + code);
316 Toast.makeText(
317 MainActivity.this,
318 "PrepareStreamingService failed, result code is " + code,
319 Toast.LENGTH_LONG).show();
320 }
321 });
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700322 }
323 }
324
325 /**
326 * Applies given payload.
327 *
328 * UpdateEngine works asynchronously. This method doesn't wait until
329 * end of the update.
Zhomart Mukhamejanov6aa5fb02018-05-09 14:28:49 -0700330 *
331 * @param payloadSpec contains url, offset and size to {@code PAYLOAD_BINARY_FILE_NAME}
332 * @param extraProperties additional properties to pass to {@link UpdateEngine#applyPayload}
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700333 */
Zhomart Mukhamejanov6aa5fb02018-05-09 14:28:49 -0700334 private void updateEngineApplyPayload(PayloadSpec payloadSpec, List<String> extraProperties) {
335 ArrayList<String> properties = new ArrayList<>(payloadSpec.getProperties());
336 if (extraProperties != null) {
337 properties.addAll(extraProperties);
338 }
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700339 try {
340 mUpdateEngine.applyPayload(
341 payloadSpec.getUrl(),
342 payloadSpec.getOffset(),
343 payloadSpec.getSize(),
Zhomart Mukhamejanov6aa5fb02018-05-09 14:28:49 -0700344 properties.toArray(new String[0]));
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700345 } catch (Exception e) {
346 Log.e(TAG, "UpdateEngine failed to apply the update", e);
347 Toast.makeText(
348 this,
349 "UpdateEngine failed to apply the update",
350 Toast.LENGTH_LONG).show();
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700351 }
352 }
353
354 /**
355 * Requests update engine to stop any ongoing update. If an update has been applied,
356 * leave it as is.
357 */
358 private void stopRunningUpdate() {
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700359 try {
360 mUpdateEngine.cancel();
361 } catch (Exception e) {
362 Log.w(TAG, "UpdateEngine failed to stop the ongoing update", e);
363 }
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700364 }
365
366 /**
367 * Resets update engine to IDLE state. Requests to cancel any onging update, or to revert if an
368 * update has been applied.
369 */
370 private void resetUpdate() {
Zhomart Mukhamejanovf7a70382018-05-02 20:37:12 -0700371 try {
372 mUpdateEngine.resetStatus();
373 } catch (Exception e) {
374 Log.w(TAG, "UpdateEngine failed to reset the update", e);
375 }
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700376 }
377
378 /**
Zhomart Mukhamejanovda7e2372018-05-01 12:50:55 -0700379 * Helper class to delegate {@code update_engine} callbacks to MainActivity
Zhomart Mukhamejanovf4d280c2018-04-17 13:20:22 -0700380 */
381 class UpdateEngineCallbackImpl extends UpdateEngineCallback {
382 @Override
383 public void onStatusUpdate(int status, float percent) {
384 MainActivity.this.onStatusUpdate(status, percent);
385 }
386
387 @Override
388 public void onPayloadApplicationComplete(int errorCode) {
389 MainActivity.this.onPayloadApplicationComplete(errorCode);
390 }
391 }
392
393}