• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 package com.android.car.systemupdater;
17 
18 import android.app.Notification;
19 import android.app.NotificationChannel;
20 import android.app.NotificationManager;
21 import android.app.PendingIntent;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.os.AsyncTask;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.os.PowerManager;
29 import android.os.UpdateEngine;
30 import android.os.UpdateEngineCallback;
31 import android.text.format.Formatter;
32 import android.util.Log;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.widget.Button;
37 import android.widget.ProgressBar;
38 import android.widget.TextView;
39 
40 import androidx.annotation.NonNull;
41 import androidx.annotation.StringRes;
42 import androidx.appcompat.app.ActionBar;
43 import androidx.appcompat.app.AppCompatActivity;
44 import androidx.fragment.app.Fragment;
45 
46 import com.android.internal.util.Preconditions;
47 
48 import java.io.File;
49 import java.io.IOException;
50 
51 /** Display update state and progress. */
52 public class UpdateLayoutFragment extends Fragment implements UpFragment {
53     public static final String EXTRA_RESUME_UPDATE = "resume_update";
54 
55     private static final String TAG = "UpdateLayoutFragment";
56     private static final String EXTRA_UPDATE_FILE = "extra_update_file";
57     private static final int PERCENT_MAX = 100;
58     private static final String REBOOT_REASON = "reboot-ab-update";
59     private static final String NOTIFICATION_CHANNEL_ID = "update";
60     private static final int NOTIFICATION_ID = 1;
61 
62     private ProgressBar mProgressBar;
63     private TextView mContentTitle;
64     private TextView mContentInfo;
65     private TextView mContentDetails;
66     private File mUpdateFile;
67     private Button mSystemUpdateToolbarAction;
68     private PowerManager mPowerManager;
69     private NotificationManager mNotificationManager;
70     private final UpdateVerifier mPackageVerifier = new UpdateVerifier();
71     private final UpdateEngine mUpdateEngine = new UpdateEngine();
72     private boolean mInstallationInProgress = false;
73 
74     private final CarUpdateEngineCallback mCarUpdateEngineCallback = new CarUpdateEngineCallback();
75 
76     /** Create a {@link UpdateLayoutFragment}. */
getInstance(File file)77     public static UpdateLayoutFragment getInstance(File file) {
78         UpdateLayoutFragment fragment = new UpdateLayoutFragment();
79         Bundle bundle = new Bundle();
80         bundle.putString(EXTRA_UPDATE_FILE, file.getAbsolutePath());
81         fragment.setArguments(bundle);
82         return fragment;
83     }
84 
85     /** Create a {@link UpdateLayoutFragment} showing an update in progress. */
newResumedInstance()86     public static UpdateLayoutFragment newResumedInstance() {
87         UpdateLayoutFragment fragment = new UpdateLayoutFragment();
88         Bundle bundle = new Bundle();
89         bundle.putBoolean(EXTRA_RESUME_UPDATE, true);
90         fragment.setArguments(bundle);
91         return fragment;
92     }
93 
94     @Override
onCreate(Bundle savedInstanceState)95     public void onCreate(Bundle savedInstanceState) {
96         super.onCreate(savedInstanceState);
97 
98         if (!getArguments().getBoolean(EXTRA_RESUME_UPDATE)) {
99             mUpdateFile = new File(getArguments().getString(EXTRA_UPDATE_FILE));
100         }
101         mPowerManager = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
102         mNotificationManager =
103                 (NotificationManager) getContext().getSystemService(NotificationManager.class);
104         mNotificationManager.createNotificationChannel(
105                 new NotificationChannel(
106                         NOTIFICATION_CHANNEL_ID,
107                         getContext().getString(R.id.system_update_auto_content_title),
108                         NotificationManager.IMPORTANCE_DEFAULT));
109     }
110 
111     @Override
onCreateView(@onNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)112     public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
113             Bundle savedInstanceState) {
114         return inflater.inflate(R.layout.system_update_auto_content, container, false);
115     }
116 
117     @Override
onViewCreated(@onNull View view, Bundle savedInstanceState)118     public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
119         mContentTitle = view.findViewById(R.id.system_update_auto_content_title);
120         mContentInfo = view.findViewById(R.id.system_update_auto_content_info);
121         mContentDetails = view.findViewById(R.id.system_update_auto_content_details);
122     }
123 
124     @Override
onActivityCreated(Bundle savedInstanceState)125     public void onActivityCreated(Bundle savedInstanceState) {
126         super.onActivityCreated(savedInstanceState);
127 
128         AppCompatActivity activity = (AppCompatActivity) getActivity();
129 
130         ActionBar actionBar = activity.getSupportActionBar();
131         actionBar.setCustomView(R.layout.action_bar_with_button);
132         actionBar.setDisplayShowCustomEnabled(true);
133         actionBar.setDisplayShowTitleEnabled(false);
134 
135         mProgressBar = (ProgressBar) activity.findViewById(R.id.progress_bar);
136 
137         mSystemUpdateToolbarAction = activity.findViewById(R.id.action_button1);
138         mProgressBar.setIndeterminate(true);
139         mProgressBar.setVisibility(View.VISIBLE);
140         showStatus(R.string.verify_in_progress);
141 
142         if (getArguments().getBoolean(EXTRA_RESUME_UPDATE)) {
143             // Rejoin the update already in progress.
144             showInstallationInProgress();
145         } else {
146             // Extract the necessary information and begin the update.
147             mPackageVerifier.execute(mUpdateFile);
148         }
149     }
150 
151     @Override
onStop()152     public void onStop() {
153         super.onStop();
154         if (mPackageVerifier != null) {
155             mPackageVerifier.cancel(true);
156         }
157     }
158 
159     /** Update the status information. */
showStatus(@tringRes int status)160     private void showStatus(@StringRes int status) {
161         mContentTitle.setText(status);
162         if (mInstallationInProgress) {
163             mNotificationManager.notify(NOTIFICATION_ID, createNotification(getContext(), status));
164         } else {
165             mNotificationManager.cancel(NOTIFICATION_ID);
166         }
167     }
168 
169     /** Show the install now button. */
showInstallNow(UpdateParser.ParsedUpdate update)170     private void showInstallNow(UpdateParser.ParsedUpdate update) {
171         mContentTitle.setText(R.string.install_ready);
172         mContentInfo.append(getString(R.string.update_file_name, mUpdateFile.getName()));
173         mContentInfo.append(System.getProperty("line.separator"));
174         mContentInfo.append(getString(R.string.update_file_size));
175         mContentInfo.append(Formatter.formatFileSize(getContext(), mUpdateFile.length()));
176         mContentDetails.setText(null);
177         mSystemUpdateToolbarAction.setOnClickListener(v -> installUpdate(update));
178         mSystemUpdateToolbarAction.setText(R.string.install_now);
179         mSystemUpdateToolbarAction.setVisibility(View.VISIBLE);
180     }
181 
182     /** Reboot the system. */
rebootNow()183     private void rebootNow() {
184         if (Log.isLoggable(TAG, Log.INFO)) {
185             Log.i(TAG, "Rebooting Now.");
186         }
187         mPowerManager.reboot(REBOOT_REASON);
188     }
189 
190     /** Attempt to install the update that is copied to the device. */
installUpdate(UpdateParser.ParsedUpdate parsedUpdate)191     private void installUpdate(UpdateParser.ParsedUpdate parsedUpdate) {
192         showInstallationInProgress();
193         mUpdateEngine.applyPayload(
194                 parsedUpdate.mUrl, parsedUpdate.mOffset, parsedUpdate.mSize, parsedUpdate.mProps);
195     }
196 
197     /** Set the layout to show installation progress. */
showInstallationInProgress()198     private void showInstallationInProgress() {
199         mInstallationInProgress = true;
200         mProgressBar.setIndeterminate(false);
201         mProgressBar.setVisibility(View.VISIBLE);
202         mProgressBar.setMax(PERCENT_MAX);
203         mSystemUpdateToolbarAction.setVisibility(View.GONE);
204         showStatus(R.string.install_in_progress);
205 
206         mUpdateEngine.bind(mCarUpdateEngineCallback, new Handler(getContext().getMainLooper()));
207     }
208 
209     /** Attempt to verify the update and extract information needed for installation. */
210     private class UpdateVerifier extends AsyncTask<File, Void, UpdateParser.ParsedUpdate> {
211 
212         @Override
doInBackground(File... files)213         protected UpdateParser.ParsedUpdate doInBackground(File... files) {
214             Preconditions.checkArgument(files.length > 0, "No file specified");
215             File file = files[0];
216             try {
217                 return UpdateParser.parse(file);
218             } catch (IOException e) {
219                 Log.e(TAG, String.format("For file %s", file), e);
220                 return null;
221             }
222         }
223 
224         @Override
onPostExecute(UpdateParser.ParsedUpdate result)225         protected void onPostExecute(UpdateParser.ParsedUpdate result) {
226             mProgressBar.setVisibility(View.GONE);
227             if (result == null) {
228                 showStatus(R.string.verify_failure);
229                 return;
230             }
231             if (!result.isValid()) {
232                 showStatus(R.string.verify_failure);
233                 Log.e(TAG, String.format("Failed verification %s", result));
234                 return;
235             }
236             if (Log.isLoggable(TAG, Log.INFO)) {
237                 Log.i(TAG, result.toString());
238             }
239 
240             showInstallNow(result);
241         }
242     }
243 
244     /** Handles events from the UpdateEngine. */
245     public class CarUpdateEngineCallback extends UpdateEngineCallback {
246 
247         @Override
onStatusUpdate(int status, float percent)248         public void onStatusUpdate(int status, float percent) {
249             if (Log.isLoggable(TAG, Log.DEBUG)) {
250                 Log.d(TAG, String.format("onStatusUpdate %d, Percent %.2f", status, percent));
251             }
252             switch (status) {
253                 case UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT:
254                     rebootNow();
255                     break;
256                 case UpdateEngine.UpdateStatusConstants.DOWNLOADING:
257                     mProgressBar.setProgress((int) (percent * 100));
258                     break;
259                 default:
260                     // noop
261             }
262         }
263 
264         @Override
onPayloadApplicationComplete(int errorCode)265         public void onPayloadApplicationComplete(int errorCode) {
266             Log.w(TAG, String.format("onPayloadApplicationComplete %d", errorCode));
267             mInstallationInProgress = false;
268             showStatus(errorCode == UpdateEngine.ErrorCodeConstants.SUCCESS
269                     ? R.string.install_success
270                     : R.string.install_failed);
271             mProgressBar.setVisibility(View.GONE);
272             mSystemUpdateToolbarAction.setVisibility(View.GONE);
273         }
274     }
275 
276     /** Build a notification to show the installation status. */
createNotification(Context context, @StringRes int contents)277     private static Notification createNotification(Context context, @StringRes int contents) {
278         Intent intent = new Intent();
279         intent.setComponent(new ComponentName(context, SystemUpdaterActivity.class));
280         intent.putExtra(EXTRA_RESUME_UPDATE, true);
281         PendingIntent pendingIntent =
282                 PendingIntent.getActivity(
283                         context,
284                         /* requestCode= */ 0,
285                         intent,
286                         PendingIntent.FLAG_UPDATE_CURRENT);
287 
288         return new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
289                 .setVisibility(Notification.VISIBILITY_PUBLIC)
290                 .setContentTitle(context.getString(contents))
291                 .setSmallIcon(R.drawable.ic_system_update_alt_black_48dp)
292                 .setContentIntent(pendingIntent)
293                 .setShowWhen(false)
294                 .setOngoing(true)
295                 .setAutoCancel(false)
296                 .build();
297     }
298 }
299