| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.car.systemupdater; |
| |
| import android.app.Activity; |
| import android.app.AlertDialog; |
| import android.app.FragmentManager; |
| import android.app.ProgressDialog; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.os.AsyncTask; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.RecoverySystem; |
| import android.util.Log; |
| import android.widget.Toast; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.FilenameFilter; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.security.GeneralSecurityException; |
| import java.util.List; |
| |
| import android.os.storage.StorageEventListener; |
| import android.os.storage.StorageManager; |
| import android.os.storage.VolumeInfo; |
| |
| /** |
| * A prototype of performing system update using an ota package on internal or external storage. |
| * TODO(yaochen): Move the code to a proper location and let it extend CarActivity once available. |
| */ |
| public class SystemUpdaterActivity extends Activity { |
| private static final String TAG = "SystemUpdaterActivity"; |
| private static final boolean DEBUG = true; |
| private static final String UPDATE_FILE_NAME = "update.zip"; |
| |
| private final Handler mHandler = new Handler(); |
| private StorageManager mStorageManager = null; |
| private ProgressDialog mVerifyPackageDialog = null; |
| |
| |
| private final StorageEventListener mListener = new StorageEventListener() { |
| @Override |
| public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { |
| if (DEBUG) { |
| Log.d(TAG, "onVolumeMetadataChanged " + oldState + " " + newState |
| + " " + vol.toString()); |
| } |
| showMountedVolumes(); |
| } |
| }; |
| |
| @Override |
| protected void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| setContentView(R.layout.activity_main); |
| mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE); |
| if (mStorageManager == null) { |
| Log.w(TAG, "Failed to get StorageManager"); |
| Toast.makeText(this, "Cannot get StorageManager!", Toast.LENGTH_LONG).show(); |
| } |
| } |
| |
| @Override |
| protected void onResume() { |
| super.onResume(); |
| if (mStorageManager != null) { |
| mStorageManager.registerListener(mListener); |
| showMountedVolumes(); |
| } |
| } |
| |
| @Override |
| protected void onPause() { |
| super.onPause(); |
| if (mStorageManager != null) { |
| mStorageManager.unregisterListener(mListener); |
| } |
| } |
| |
| @Override |
| public void onBackPressed() { |
| if (getFragmentManager().getBackStackEntryCount() > 0) { |
| getFragmentManager().popBackStackImmediate(); |
| } else { |
| super.onBackPressed(); |
| } |
| } |
| |
| public void showMountedVolumes() { |
| if (mStorageManager == null) { |
| return; |
| } |
| final List<VolumeInfo> vols = mStorageManager.getVolumes(); |
| File[] files = new File[vols.size()]; |
| int i = 0; |
| for (VolumeInfo vol : vols) { |
| File path = vol.getPathForUser(getUserId()); |
| if (vol.getState() != VolumeInfo.STATE_MOUNTED || path == null) { |
| continue; |
| } |
| files[i++] = path; |
| } |
| getFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); |
| DeviceListFragment frag = new DeviceListFragment(); |
| frag.updateList(files); |
| frag.updateTitle(getString(R.string.title)); |
| getFragmentManager().beginTransaction() |
| .replace(R.id.device_container, frag).commit(); |
| } |
| |
| public void showFolderContent(final File location) { |
| if (!location.isDirectory()) { |
| return; |
| } |
| AsyncTask<String, Void, File[]> readFilesTask = new AsyncTask<String, Void, File[]>() { |
| @Override |
| protected File[] doInBackground(String... strings) { |
| File f = new File(strings[0]); |
| /* if we want to filter files, use |
| File[] files = f.listFiles(new FilenameFilter() { |
| @Override |
| public boolean accept(File dir, String filename) { |
| return true; |
| } |
| }); */ |
| return f.listFiles(); |
| } |
| |
| @Override |
| protected void onPostExecute(File[] results) { |
| super.onPostExecute(results); |
| if (results == null) { |
| results = new File[0]; |
| } |
| DeviceListFragment frag = new DeviceListFragment(); |
| frag.updateTitle(location.getAbsolutePath()); |
| frag.updateList(results); |
| getFragmentManager().beginTransaction() |
| .replace(R.id.device_container, frag).addToBackStack(null).commit(); |
| } |
| }; |
| readFilesTask.execute(location.getAbsolutePath()); |
| } |
| |
| public void checkPackage(File file) { |
| mVerifyPackageDialog = new ProgressDialog(this); |
| mVerifyPackageDialog.setTitle("Verifying... " + file.getAbsolutePath()); |
| |
| final PackageVerifier verifyPackage = new PackageVerifier(); |
| verifyPackage.execute(file); |
| mVerifyPackageDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { |
| @Override |
| public void onCancel(DialogInterface dialogInterface) { |
| verifyPackage.cancel(true); |
| } |
| }); |
| mVerifyPackageDialog.setProgressStyle(mVerifyPackageDialog.STYLE_HORIZONTAL); |
| mVerifyPackageDialog.setMax(100); |
| mVerifyPackageDialog.setProgress(0); |
| mVerifyPackageDialog.show(); |
| } |
| |
| private class PackageVerifier extends AsyncTask<File, Void, Exception> { |
| File mFile; |
| |
| @Override |
| protected Exception doInBackground(File... files) { |
| File file = files[0]; |
| mFile = file; |
| try { |
| RecoverySystem.verifyPackage(file, mProgressListener, null); |
| } catch (GeneralSecurityException e) { |
| Log.e(TAG, "Security Exception in verifying package " + file, e); |
| return e; |
| } catch (IOException e) { |
| Log.e(TAG, "IO Exception in verifying package " + file, e); |
| return e; |
| } |
| return null; |
| } |
| |
| @Override |
| protected void onPostExecute(Exception result) { |
| mVerifyPackageDialog.cancel(); |
| if (result == null) { |
| mVerifyPackageDialog = new ProgressDialog(SystemUpdaterActivity.this); |
| mVerifyPackageDialog.setTitle("Copying " + mFile.getName() |
| + " to " + getCacheDir() + "/" + UPDATE_FILE_NAME); |
| mVerifyPackageDialog.setProgressStyle(mVerifyPackageDialog.STYLE_HORIZONTAL); |
| mVerifyPackageDialog.setMax((int) (mFile.length() / 1024)); |
| mVerifyPackageDialog.show(); |
| new CopyFile().execute(mFile); |
| } else { |
| AlertDialog.Builder doneDialog = |
| new AlertDialog.Builder(SystemUpdaterActivity.this); |
| doneDialog.setMessage("Verification failed! " + result.getMessage()).show(); |
| } |
| } |
| } |
| |
| |
| private class CopyFile extends AsyncTask<File, Void, Exception> { |
| @Override |
| protected Exception doInBackground(File... files) { |
| File file = files[0]; |
| if (getCacheDir().getFreeSpace() < file.length()) { |
| return new IOException("Not enough cache space!"); |
| } |
| File dest = new File(getCacheDir(), UPDATE_FILE_NAME); |
| try { |
| copy(file, dest); |
| } catch (IOException e) { |
| Log.e(TAG, "Error when coping file to cache", e); |
| dest.delete(); |
| return new IOException(e.getMessage()); |
| } |
| return null; |
| } |
| |
| @Override |
| protected void onPostExecute(Exception result) { |
| mVerifyPackageDialog.cancel(); |
| AlertDialog.Builder doneDialog = new AlertDialog.Builder(SystemUpdaterActivity.this); |
| |
| doneDialog.setMessage("Copy " + (result == null ? "completed!" : "failed!" |
| + result.getMessage())); |
| |
| if (result == null) { |
| doneDialog.setPositiveButton("Start system update", |
| new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialogInterface, int i) { |
| try { |
| RecoverySystem.installPackage(SystemUpdaterActivity.this, |
| new File(getCacheDir(), UPDATE_FILE_NAME)); |
| } catch (IOException e) { |
| Log.e(TAG, "IOException in installing ota package"); |
| Toast.makeText(SystemUpdaterActivity.this, |
| "IOException in installing ota package ", |
| Toast.LENGTH_LONG).show(); |
| } |
| } |
| }); |
| } else { |
| Log.e(TAG, "Copy failed!", result); |
| } |
| doneDialog.create().show(); |
| } |
| } |
| |
| private void copy(File src, File dst) throws IOException { |
| InputStream in = new FileInputStream(src); |
| OutputStream out = new FileOutputStream(dst); |
| try { |
| // Transfer bytes from in to out |
| byte[] buf = new byte[0x10000]; // 64k |
| int len; |
| while ((len = in.read(buf)) > 0) { |
| out.write(buf, 0, len); |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| mVerifyPackageDialog.incrementProgressBy(1); |
| } |
| }); |
| } |
| } finally { |
| in.close(); |
| out.close(); |
| } |
| } |
| |
| private final RecoverySystem.ProgressListener mProgressListener = |
| new RecoverySystem.ProgressListener() { |
| @Override |
| public void onProgress(final int i) { |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| if (mVerifyPackageDialog != null) { |
| mVerifyPackageDialog.setProgress(i); |
| } |
| } |
| }); |
| } |
| }; |
| } |