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