Add car system updater.

Original sha1: 93fd8e35a521a7737a030e17abd488fcd0d3b948

Credits:
  yaochen@

Bug: 32118797
Test: Manual.
Change-Id: I849949b27653abd1b03c5e59579873d639069b49
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..dea6312
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,30 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_CERTIFICATE := platform
+
+LOCAL_PACKAGE_NAME := SystemUpdater
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_PACKAGE)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..fcb00fc
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.car.systemupdater" >
+    <uses-sdk android:minSdkVersion="21" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM" />
+    <uses-permission android:name="android.permission.DELETE_CACHE_FILES" />
+    <uses-permission android:name="android.permission.REBOOT" />
+    <uses-permission android:name="android.permission.RECOVERY" />
+    <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
+    <uses-feature android:name="android.hardware.usb.host" />
+    <uses-feature
+        android:glEsVersion="0x00020000"
+        android:required="true"/>
+
+    <application
+        android:label="@string/app_name"
+        android:icon="@drawable/ic_launcher">
+        <activity
+            android:name="com.android.car.systemupdater.SystemUpdaterActivity"
+            android:label="@string/app_name"
+            android:icon="@drawable/ic_launcher" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_launcher.png b/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4df1894
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/res/layout/activity_main.xml b/res/layout/activity_main.xml
new file mode 100644
index 0000000..b077f52
--- /dev/null
+++ b/res/layout/activity_main.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (c) 2016, 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    tools:context=".MainActivity">
+    <FrameLayout
+        android:id="@+id/device_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+</RelativeLayout>
diff --git a/res/layout/folder_entry.xml b/res/layout/folder_entry.xml
new file mode 100644
index 0000000..0fec3c0
--- /dev/null
+++ b/res/layout/folder_entry.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (c) 2016, 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <TextView
+        android:id="@+id/text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="5dp"
+        android:layout_marginRight="20dp"
+        android:gravity="center"
+        android:textSize="32sp">
+    </TextView>
+    <TextView
+        android:id="@+id/description"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="5dp"
+        android:gravity="center"
+        android:textSize="32sp">
+    </TextView>
+</LinearLayout>
diff --git a/res/layout/folder_list.xml b/res/layout/folder_list.xml
new file mode 100644
index 0000000..cb651a3
--- /dev/null
+++ b/res/layout/folder_list.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (c) 2016, 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" >
+        <Button
+            android:id="@+id/back"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/back"
+            android:layout_gravity="left"/>
+
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="48dp"
+            android:layout_gravity="center" />
+    </FrameLayout>
+    <ListView
+        android:id="@+id/folder_list"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="10dp"
+        android:gravity="center"
+        android:numColumns="auto_fit"
+        android:verticalSpacing="10dp"
+        android:drawSelectorOnTop="true"
+        android:stretchMode="columnWidth" />
+</LinearLayout>
diff --git a/res/values-v21/styles.xml b/res/values-v21/styles.xml
new file mode 100644
index 0000000..1dd3c66
--- /dev/null
+++ b/res/values-v21/styles.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (c) 2016, 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.
+-->
+<resources>
+    <style name="AppTheme" parent="android:Theme.Material.Light">
+    </style>
+</resources>
diff --git a/res/values-w820dp/dimens.xml b/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..0ef6ab4
--- /dev/null
+++ b/res/values-w820dp/dimens.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (c) 2016, 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.
+-->
+<resources>
+    <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+         (such as screen margins) for screens with more than 820dp of available width. This
+         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+    <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
new file mode 100644
index 0000000..2688318
--- /dev/null
+++ b/res/values/dimens.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (c) 2016, 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.
+-->
+<resources>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..f820d1c
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (c) 2016, 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.
+-->
+<resources>
+    <string name="app_name">SystemUpdater</string>
+    <string name="action_settings">Settings</string>
+    <string name="title">Mounted Volumes</string>
+    <string name="back">Go Back</string>
+</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100644
index 0000000..de155d8
--- /dev/null
+++ b/res/values/styles.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (c) 2016, 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.
+-->
+<resources>
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+    </style>
+</resources>
diff --git a/src/com/android/car/systemupdater/DeviceListFragment.java b/src/com/android/car/systemupdater/DeviceListFragment.java
new file mode 100644
index 0000000..cb0d43f
--- /dev/null
+++ b/src/com/android/car/systemupdater/DeviceListFragment.java
@@ -0,0 +1,102 @@
+/*
+ * 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.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.io.File;
+
+public class DeviceListFragment extends Fragment {
+    private ListView mFolderListView;
+    private SystemUpdaterActivity mActivity;
+    private File[] mFileNames = new File[0];
+    private FileAdapter mAdapter;
+    private Button mBackButton;
+    private TextView mTitle;
+    private String mTitleText;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mActivity = (SystemUpdaterActivity) getActivity();
+        mAdapter = new FileAdapter(mActivity, R.layout.folder_entry, mFileNames);
+    }
+    @Override
+    public View onCreateView(LayoutInflater inflater, final ViewGroup container,
+                             Bundle savedInstanceState) {
+        View v = inflater.inflate(R.layout.folder_list, container, false);
+        mTitle = (TextView) v.findViewById(R.id.title);
+        if (mTitleText != null) {
+            mTitle.setText(mTitleText);
+        }
+        mFolderListView = (ListView) v.findViewById(R.id.folder_list);
+        mFolderListView.setAdapter(mAdapter);
+        mFolderListView.setOnItemClickListener(mItemClickListener);
+        mBackButton = (Button) v.findViewById(R.id.back);
+        mBackButton.setOnClickListener(mBackButtonListener);
+        return v;
+    }
+
+    public void updateList(File[] locations) {
+        if (locations != null) {
+            mFileNames = locations;
+            if (mAdapter != null) {
+                mAdapter.setLocations(mFileNames);
+            }
+        }
+    }
+
+    public void updateTitle(String title) {
+        if (mTitle != null) {
+            mTitle.setText(title);
+        } else {
+            mTitleText = title;
+        }
+    }
+
+    private final View.OnClickListener mBackButtonListener =
+            new View.OnClickListener() {
+                @Override
+                public void onClick(View view) {
+                    mActivity.onBackPressed();
+                }
+            };
+
+    private final AdapterView.OnItemClickListener mItemClickListener =
+            new AdapterView.OnItemClickListener() {
+                @Override
+                public void onItemClick(AdapterView<?> adapterView, View view,
+                                        int position, long id) {
+                    if (mFileNames[position].getName().endsWith(".zip")) {
+                        mActivity.checkPackage(mFileNames[position]);
+                    } else if (mFileNames[position].isDirectory()) {
+                        mActivity.showFolderContent(mFileNames[position]);
+                    } else {
+                        Toast.makeText(mActivity, "This is not a valid file for updating",
+                                Toast.LENGTH_LONG).show();
+                    }
+                }
+            };
+}
diff --git a/src/com/android/car/systemupdater/FileAdapter.java b/src/com/android/car/systemupdater/FileAdapter.java
new file mode 100644
index 0000000..1b0ecd6
--- /dev/null
+++ b/src/com/android/car/systemupdater/FileAdapter.java
@@ -0,0 +1,78 @@
+/*
+ * 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.content.Context;
+import android.graphics.Color;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+import java.io.File;
+
+
+public class FileAdapter extends ArrayAdapter<File> {
+    private final Context mContext;
+    private File[] mLocations;
+    private final int mLayoutResourceId;
+
+    public FileAdapter(Context c, int layoutResourceId, File[] locations) {
+        super(c, layoutResourceId, locations);
+        mContext = c;
+        this.mLayoutResourceId = layoutResourceId;
+        this.mLocations = locations;
+    }
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        ViewHolder vh = new ViewHolder();
+        if (convertView == null) {
+            LayoutInflater inflater = LayoutInflater.from(mContext);
+            convertView = inflater.inflate(mLayoutResourceId, parent, false);
+            vh.textView = (TextView) convertView.findViewById(R.id.text);
+            vh.descriptionView = (TextView) convertView.findViewById(R.id.description);
+            convertView.setTag(vh);
+        } else {
+            vh = (ViewHolder) convertView.getTag();
+        }
+        if (mLocations[position] != null) {
+            vh.textView.setText(mLocations[position].getAbsolutePath());
+            if (mLocations[position].getAbsolutePath().endsWith(".zip")
+                    || mLocations[position].isDirectory()) {
+                vh.textView.setTextColor(Color.GREEN);
+            } else {
+                vh.textView.setTextColor(Color.GRAY);
+            }
+        }
+        return convertView;
+    }
+
+    @Override
+    public int getCount() {
+        return mLocations.length;
+    }
+
+    public void setLocations(File[] locations) {
+        mLocations = locations;
+        notifyDataSetChanged();
+    }
+
+    static class ViewHolder {
+        TextView textView;
+        TextView descriptionView;
+    }
+}
diff --git a/src/com/android/car/systemupdater/SystemUpdaterActivity.java b/src/com/android/car/systemupdater/SystemUpdaterActivity.java
new file mode 100644
index 0000000..e442731
--- /dev/null
+++ b/src/com/android/car/systemupdater/SystemUpdaterActivity.java
@@ -0,0 +1,305 @@
+/*
+ * 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);
+                    }
+                }
+            });
+        }
+    };
+}