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}. */ 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. */ 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 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 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 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 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 152 public void onStop() { 153 super.onStop(); 154 if (mPackageVerifier != null) { 155 mPackageVerifier.cancel(true); 156 } 157 } 158 159 /** Update the status information. */ 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. */ 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. */ 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. */ 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. */ 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 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 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 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 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. */ 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