1 /* 2 * Copyright (C) 2015 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.Activity; 19 import android.app.AlertDialog; 20 import android.app.FragmentManager; 21 import android.app.ProgressDialog; 22 import android.content.Context; 23 import android.content.DialogInterface; 24 import android.os.AsyncTask; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.os.RecoverySystem; 28 import android.util.Log; 29 import android.widget.Toast; 30 31 import java.io.File; 32 import java.io.FileInputStream; 33 import java.io.FileOutputStream; 34 import java.io.FilenameFilter; 35 import java.io.IOException; 36 import java.io.InputStream; 37 import java.io.OutputStream; 38 import java.security.GeneralSecurityException; 39 import java.util.List; 40 41 import android.os.storage.StorageEventListener; 42 import android.os.storage.StorageManager; 43 import android.os.storage.VolumeInfo; 44 45 /** 46 * A prototype of performing system update using an ota package on internal or external storage. 47 * TODO(yaochen): Move the code to a proper location and let it extend CarActivity once available. 48 */ 49 public class SystemUpdaterActivity extends Activity { 50 private static final String TAG = "SystemUpdaterActivity"; 51 private static final boolean DEBUG = true; 52 private static final String UPDATE_FILE_NAME = "update.zip"; 53 54 private final Handler mHandler = new Handler(); 55 private StorageManager mStorageManager = null; 56 private ProgressDialog mVerifyPackageDialog = null; 57 58 59 private final StorageEventListener mListener = new StorageEventListener() { 60 @Override 61 public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { 62 if (DEBUG) { 63 Log.d(TAG, "onVolumeMetadataChanged " + oldState + " " + newState 64 + " " + vol.toString()); 65 } 66 showMountedVolumes(); 67 } 68 }; 69 70 @Override onCreate(Bundle savedInstanceState)71 protected void onCreate(Bundle savedInstanceState) { 72 super.onCreate(savedInstanceState); 73 setContentView(R.layout.activity_main); 74 mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE); 75 if (mStorageManager == null) { 76 Log.w(TAG, "Failed to get StorageManager"); 77 Toast.makeText(this, "Cannot get StorageManager!", Toast.LENGTH_LONG).show(); 78 } 79 } 80 81 @Override onResume()82 protected void onResume() { 83 super.onResume(); 84 if (mStorageManager != null) { 85 mStorageManager.registerListener(mListener); 86 showMountedVolumes(); 87 } 88 } 89 90 @Override onPause()91 protected void onPause() { 92 super.onPause(); 93 if (mStorageManager != null) { 94 mStorageManager.unregisterListener(mListener); 95 } 96 } 97 98 @Override onBackPressed()99 public void onBackPressed() { 100 if (getFragmentManager().getBackStackEntryCount() > 0) { 101 getFragmentManager().popBackStackImmediate(); 102 } else { 103 super.onBackPressed(); 104 } 105 } 106 showMountedVolumes()107 public void showMountedVolumes() { 108 if (mStorageManager == null) { 109 return; 110 } 111 final List<VolumeInfo> vols = mStorageManager.getVolumes(); 112 File[] files = new File[vols.size()]; 113 int i = 0; 114 for (VolumeInfo vol : vols) { 115 File path = vol.getPathForUser(getUserId()); 116 if (vol.getState() != VolumeInfo.STATE_MOUNTED || path == null) { 117 continue; 118 } 119 files[i++] = path; 120 } 121 getFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); 122 DeviceListFragment frag = new DeviceListFragment(); 123 frag.updateList(files); 124 frag.updateTitle(getString(R.string.title)); 125 getFragmentManager().beginTransaction() 126 .replace(R.id.device_container, frag).commit(); 127 } 128 showFolderContent(final File location)129 public void showFolderContent(final File location) { 130 if (!location.isDirectory()) { 131 return; 132 } 133 AsyncTask<String, Void, File[]> readFilesTask = new AsyncTask<String, Void, File[]>() { 134 @Override 135 protected File[] doInBackground(String... strings) { 136 File f = new File(strings[0]); 137 /* if we want to filter files, use 138 File[] files = f.listFiles(new FilenameFilter() { 139 @Override 140 public boolean accept(File dir, String filename) { 141 return true; 142 } 143 }); */ 144 return f.listFiles(); 145 } 146 147 @Override 148 protected void onPostExecute(File[] results) { 149 super.onPostExecute(results); 150 if (results == null) { 151 results = new File[0]; 152 } 153 DeviceListFragment frag = new DeviceListFragment(); 154 frag.updateTitle(location.getAbsolutePath()); 155 frag.updateList(results); 156 getFragmentManager().beginTransaction() 157 .replace(R.id.device_container, frag).addToBackStack(null).commit(); 158 } 159 }; 160 readFilesTask.execute(location.getAbsolutePath()); 161 } 162 checkPackage(File file)163 public void checkPackage(File file) { 164 mVerifyPackageDialog = new ProgressDialog(this); 165 mVerifyPackageDialog.setTitle("Verifying... " + file.getAbsolutePath()); 166 167 final PackageVerifier verifyPackage = new PackageVerifier(); 168 verifyPackage.execute(file); 169 mVerifyPackageDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { 170 @Override 171 public void onCancel(DialogInterface dialogInterface) { 172 verifyPackage.cancel(true); 173 } 174 }); 175 mVerifyPackageDialog.setProgressStyle(mVerifyPackageDialog.STYLE_HORIZONTAL); 176 mVerifyPackageDialog.setMax(100); 177 mVerifyPackageDialog.setProgress(0); 178 mVerifyPackageDialog.show(); 179 } 180 181 private class PackageVerifier extends AsyncTask<File, Void, Exception> { 182 File mFile; 183 184 @Override doInBackground(File... files)185 protected Exception doInBackground(File... files) { 186 File file = files[0]; 187 mFile = file; 188 try { 189 RecoverySystem.verifyPackage(file, mProgressListener, null); 190 } catch (GeneralSecurityException e) { 191 Log.e(TAG, "Security Exception in verifying package " + file, e); 192 return e; 193 } catch (IOException e) { 194 Log.e(TAG, "IO Exception in verifying package " + file, e); 195 return e; 196 } 197 return null; 198 } 199 200 @Override onPostExecute(Exception result)201 protected void onPostExecute(Exception result) { 202 mVerifyPackageDialog.cancel(); 203 if (result == null) { 204 mVerifyPackageDialog = new ProgressDialog(SystemUpdaterActivity.this); 205 mVerifyPackageDialog.setTitle("Copying " + mFile.getName() 206 + " to " + getCacheDir() + "/" + UPDATE_FILE_NAME); 207 mVerifyPackageDialog.setProgressStyle(mVerifyPackageDialog.STYLE_HORIZONTAL); 208 mVerifyPackageDialog.setMax((int) (mFile.length() / 1024)); 209 mVerifyPackageDialog.show(); 210 new CopyFile().execute(mFile); 211 } else { 212 AlertDialog.Builder doneDialog = 213 new AlertDialog.Builder(SystemUpdaterActivity.this); 214 doneDialog.setMessage("Verification failed! " + result.getMessage()).show(); 215 } 216 } 217 } 218 219 220 private class CopyFile extends AsyncTask<File, Void, Exception> { 221 @Override doInBackground(File... files)222 protected Exception doInBackground(File... files) { 223 File file = files[0]; 224 if (getCacheDir().getFreeSpace() < file.length()) { 225 return new IOException("Not enough cache space!"); 226 } 227 File dest = new File(getCacheDir(), UPDATE_FILE_NAME); 228 try { 229 copy(file, dest); 230 } catch (IOException e) { 231 Log.e(TAG, "Error when coping file to cache", e); 232 dest.delete(); 233 return new IOException(e.getMessage()); 234 } 235 return null; 236 } 237 238 @Override onPostExecute(Exception result)239 protected void onPostExecute(Exception result) { 240 mVerifyPackageDialog.cancel(); 241 AlertDialog.Builder doneDialog = new AlertDialog.Builder(SystemUpdaterActivity.this); 242 243 doneDialog.setMessage("Copy " + (result == null ? "completed!" : "failed!" 244 + result.getMessage())); 245 246 if (result == null) { 247 doneDialog.setPositiveButton("Start system update", 248 new DialogInterface.OnClickListener() { 249 @Override 250 public void onClick(DialogInterface dialogInterface, int i) { 251 try { 252 RecoverySystem.installPackage(SystemUpdaterActivity.this, 253 new File(getCacheDir(), UPDATE_FILE_NAME)); 254 } catch (IOException e) { 255 Log.e(TAG, "IOException in installing ota package"); 256 Toast.makeText(SystemUpdaterActivity.this, 257 "IOException in installing ota package ", 258 Toast.LENGTH_LONG).show(); 259 } 260 } 261 }); 262 } else { 263 Log.e(TAG, "Copy failed!", result); 264 } 265 doneDialog.create().show(); 266 } 267 } 268 copy(File src, File dst)269 private void copy(File src, File dst) throws IOException { 270 InputStream in = new FileInputStream(src); 271 OutputStream out = new FileOutputStream(dst); 272 try { 273 // Transfer bytes from in to out 274 byte[] buf = new byte[0x10000]; // 64k 275 int len; 276 while ((len = in.read(buf)) > 0) { 277 out.write(buf, 0, len); 278 mHandler.post(new Runnable() { 279 @Override 280 public void run() { 281 mVerifyPackageDialog.incrementProgressBy(1); 282 } 283 }); 284 } 285 } finally { 286 in.close(); 287 out.close(); 288 } 289 } 290 291 private final RecoverySystem.ProgressListener mProgressListener = 292 new RecoverySystem.ProgressListener() { 293 @Override 294 public void onProgress(final int i) { 295 mHandler.post(new Runnable() { 296 @Override 297 public void run() { 298 if (mVerifyPackageDialog != null) { 299 mVerifyPackageDialog.setProgress(i); 300 } 301 } 302 }); 303 } 304 }; 305 } 306