Home | History | Annotate | Download | only in systemupdater
      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