auto import from //depot/cupcake/@135843
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..d35d7cb
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := user development
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := Camera
+LOCAL_CERTIFICATE := media
+
+LOCAL_STATIC_JAVA_LIBRARIES := googlelogin-client
+
+include $(BUILD_PACKAGE)
+
+# Use the following include to make our test apk.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..66ef99e
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,223 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.camera"
+        android:sharedUserId="android.media">
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.SET_WALLPAPER" />
+    <!-- Needed by the ZoomRingController to set the bit saying we've already shown the
+         tutorial toast. -->
+    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
+    <application android:icon="@drawable/ic_launcher_camera"
+            android:label="@string/camera_label"
+            android:taskAffinity="">
+        <service android:name="UploadService" android:process="android.process.media" />
+        <receiver android:name="CameraButtonIntentReceiver">
+            <intent-filter>
+                <action android:name="android.intent.action.CAMERA_BUTTON"/>
+            </intent-filter>
+        </receiver>
+        <activity android:name="Camera"
+                android:configChanges="orientation|keyboardHidden"
+                android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
+                android:screenOrientation="landscape"
+                android:clearTaskOnLaunch="true"
+                android:taskAffinity="android.task.camera">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.media.action.IMAGE_CAPTURE" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.media.action.STILL_IMAGE_CAMERA" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+        <activity android:name="VideoCamera"
+                android:configChanges="orientation|keyboardHidden"
+                android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
+                android:screenOrientation="landscape"
+                android:clearTaskOnLaunch="true"
+                android:taskAffinity="android.task.camera">
+            <intent-filter>
+                <action android:name="android.media.action.VIDEO_CAMERA" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.media.action.VIDEO_CAPTURE" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+        <activity android:name="GalleryPicker" android:label="@string/gallery_picker_label"
+                android:configChanges="orientation|keyboardHidden"
+                android:icon="@drawable/ic_launcher_gallery"
+                android:clearTaskOnLaunch="true"
+                android:taskAffinity="android.task.pictures">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name="ImageGallery2" android:label="@string/gallery_label"
+                android:configChanges="orientation|keyboardHidden"
+                android:icon="@drawable/ic_launcher_gallery">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="vnd.android.cursor.dir/image" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="vnd.android.cursor.dir/video" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.GET_CONTENT" />
+                <category android:name="android.intent.category.OPENABLE" />
+                <data android:mimeType="vnd.android.cursor.dir/image" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.GET_CONTENT" />
+                <category android:name="android.intent.category.OPENABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="image/*" />
+                <data android:mimeType="video/*" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.PICK" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="image/*" />
+                <data android:mimeType="video/*" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.PICK" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="vnd.android.cursor.dir/image" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name="CropImage"
+            android:process=":CropImage"
+            android:configChanges="orientation|keyboardHidden"
+            android:label="@string/crop_label">
+            <intent-filter android:label="@string/crop_label">
+                <action android:name="com.android.camera.action.CROP" />
+                <data android:mimeType="image/*" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.ALTERNATIVE" />
+                <category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name="ViewImage" 
+        		android:label="@string/view_label"
+                android:configChanges="orientation|keyboardHidden">
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="image/*" />
+            </intent-filter>
+        </activity>
+        <activity android:name="MovieView"
+                android:label="@string/movieviewlabel"
+                android:screenOrientation="landscape"
+                android:configChanges="orientation|keyboardHidden"
+                android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
+             <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="rtsp" />
+             </intent-filter>
+             <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="video/*" />
+             </intent-filter>
+             <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="http" />
+                <data android:mimeType="video/mp4" />
+                <data android:mimeType="video/3gp" />
+                <data android:mimeType="video/3gpp" />
+                <data android:mimeType="video/3gpp2" />
+             </intent-filter>
+        </activity>
+
+        <activity android:name="CameraSettings" android:label="@string/preferences_label">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEVELOPMENT_PREFERENCE" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name="GallerySettings" android:label="@string/preferences_label">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEVELOPMENT_PREFERENCE" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name="SlideShow" android:label="Raw Image Viewer"
+                android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.TEST" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".Wallpaper"
+            android:label="@string/camera_setas_wallpaper"
+            android:icon="@drawable/ic_launcher_gallery">
+            <intent-filter>
+                <action android:name="android.intent.action.ATTACH_DATA" />
+                <data android:mimeType="image/*" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".PickWallpaper"
+            android:label="@string/camera_pick_wallpaper"
+            android:icon="@drawable/ic_launcher_gallery">
+            <intent-filter>
+                <action android:name="android.intent.action.SET_WALLPAPER" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <receiver android:name="PhotoGadgetProvider" android:label="@string/gadget_title">
+            <intent-filter>
+                <action android:name="android.gadget.action.GADGET_UPDATE" />
+            </intent-filter>
+            <meta-data android:name="android.gadget.provider" android:resource="@xml/gadget_info" />
+        </receiver>
+
+        <!-- We configure a gadget by asking to pick a photo, then crop it, and store the config internally -->
+        <activity android:name="PhotoGadgetConfigure">
+            <intent-filter>
+                <action android:name="android.gadget.action.GADGET_CONFIGURE" />
+            </intent-filter>
+        </activity>
+
+        <!-- We also allow direct binding where the caller provides a bitmap and
+             gadgetId to bind.  We require the permission because this changes our
+             internal database without user confirmation. -->
+        <activity android:name="PhotoGadgetBind" android:exported="true"
+                android:theme="@android:style/Theme.NoDisplay"
+                android:permission="android.permission.BIND_GADGET" />
+
+    </application>
+</manifest>
+
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2005-2008, 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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/res/anim/auto_focus_blink.xml b/res/anim/auto_focus_blink.xml
new file mode 100644
index 0000000..dff217d
--- /dev/null
+++ b/res/anim/auto_focus_blink.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+       android:interpolator="@android:anim/accelerate_interpolator"
+       android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="200" />
diff --git a/res/anim/on_screen_hint_enter.xml b/res/anim/on_screen_hint_enter.xml
new file mode 100644
index 0000000..0f00760
--- /dev/null
+++ b/res/anim/on_screen_hint_enter.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2009, 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.
+*/
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@android:anim/decelerate_interpolator"
+        android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="400"
+/>
diff --git a/res/anim/on_screen_hint_exit.xml b/res/anim/on_screen_hint_exit.xml
new file mode 100644
index 0000000..e9b38f8
--- /dev/null
+++ b/res/anim/on_screen_hint_exit.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2009, 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.
+*/
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@android:anim/accelerate_interpolator"
+        android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="400"
+/>
diff --git a/res/anim/slide_in.xml b/res/anim/slide_in.xml
new file mode 100644
index 0000000..d1b9e5e
--- /dev/null
+++ b/res/anim/slide_in.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<translate xmlns:android="http://schemas.android.com/apk/res/android" 
+	android:fromXDelta="100%p" 
+	android:toXDelta="0" 
+	android:startOffset="0"
+	android:duration="400" />
diff --git a/res/anim/slide_in_vertical.xml b/res/anim/slide_in_vertical.xml
new file mode 100644
index 0000000..4727689
--- /dev/null
+++ b/res/anim/slide_in_vertical.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<translate xmlns:android="http://schemas.android.com/apk/res/android" 
+	android:fromYDelta="100%p" 
+	android:toYDelta="0" 
+	android:startOffset="0"
+	android:duration="400" />
diff --git a/res/anim/slide_out.xml b/res/anim/slide_out.xml
new file mode 100644
index 0000000..204cf28
--- /dev/null
+++ b/res/anim/slide_out.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<translate xmlns:android="http://schemas.android.com/apk/res/android" 
+	android:fromXDelta="0" 
+	android:toXDelta="-100%p" 
+	android:startOffset="0"
+	android:duration="400" />
diff --git a/res/anim/slide_out_vertical.xml b/res/anim/slide_out_vertical.xml
new file mode 100644
index 0000000..3f3f2a0
--- /dev/null
+++ b/res/anim/slide_out_vertical.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<translate xmlns:android="http://schemas.android.com/apk/res/android" 
+	android:fromYDelta="0" 
+	android:toYDelta="-100%p" 
+	android:startOffset="0"
+	android:duration="400" />
diff --git a/res/anim/transition_in.xml b/res/anim/transition_in.xml
new file mode 100644
index 0000000..6c2ad61
--- /dev/null
+++ b/res/anim/transition_in.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/fade_in.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android" 
+	android:fromAlpha="0.0" 
+	android:toAlpha="1.0" 
+	android:duration="1000" />
diff --git a/res/anim/transition_out.xml b/res/anim/transition_out.xml
new file mode 100644
index 0000000..76e67f4
--- /dev/null
+++ b/res/anim/transition_out.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/fade_out.xml
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fromAlpha="1.0"
+    android:toAlpha="0.0"
+    android:duration="1000" 
+/>
diff --git a/res/drawable/btn_camera_arrow_left.xml b/res/drawable/btn_camera_arrow_left.xml
new file mode 100644
index 0000000..494899b
--- /dev/null
+++ b/res/drawable/btn_camera_arrow_left.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_pressed="true"
+        android:drawable="@drawable/btn_camera_arrow_left_press" />
+
+    <item android:state_window_focused="true" android:state_focused="true"
+        android:drawable="@drawable/btn_camera_arrow_left_selected" />
+        
+    <item 
+        android:drawable="@drawable/btn_camera_arrow_left_default" />
+
+</selector>
diff --git a/res/drawable/btn_camera_arrow_left_default.png b/res/drawable/btn_camera_arrow_left_default.png
new file mode 100644
index 0000000..a9709aa
--- /dev/null
+++ b/res/drawable/btn_camera_arrow_left_default.png
Binary files differ
diff --git a/res/drawable/btn_camera_arrow_left_press.png b/res/drawable/btn_camera_arrow_left_press.png
new file mode 100644
index 0000000..262c928
--- /dev/null
+++ b/res/drawable/btn_camera_arrow_left_press.png
Binary files differ
diff --git a/res/drawable/btn_camera_arrow_left_selected.png b/res/drawable/btn_camera_arrow_left_selected.png
new file mode 100644
index 0000000..839704f
--- /dev/null
+++ b/res/drawable/btn_camera_arrow_left_selected.png
Binary files differ
diff --git a/res/drawable/btn_camera_arrow_right.xml b/res/drawable/btn_camera_arrow_right.xml
new file mode 100644
index 0000000..1d0cc0c
--- /dev/null
+++ b/res/drawable/btn_camera_arrow_right.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+
+    <item android:state_pressed="true"
+        android:drawable="@drawable/btn_camera_arrow_right_press" />
+
+    <item android:state_window_focused="true" android:state_focused="true"
+        android:drawable="@drawable/btn_camera_arrow_right_selected" />
+
+    <item 
+        android:drawable="@drawable/btn_camera_arrow_right_default" />
+
+</selector>
diff --git a/res/drawable/btn_camera_arrow_right_default.png b/res/drawable/btn_camera_arrow_right_default.png
new file mode 100644
index 0000000..a4f78dc
--- /dev/null
+++ b/res/drawable/btn_camera_arrow_right_default.png
Binary files differ
diff --git a/res/drawable/btn_camera_arrow_right_press.png b/res/drawable/btn_camera_arrow_right_press.png
new file mode 100644
index 0000000..4b37b0d
--- /dev/null
+++ b/res/drawable/btn_camera_arrow_right_press.png
Binary files differ
diff --git a/res/drawable/btn_camera_arrow_right_selected.png b/res/drawable/btn_camera_arrow_right_selected.png
new file mode 100644
index 0000000..630a606
--- /dev/null
+++ b/res/drawable/btn_camera_arrow_right_selected.png
Binary files differ
diff --git a/res/drawable/camera_crop_height.png b/res/drawable/camera_crop_height.png
new file mode 100644
index 0000000..b089aec
--- /dev/null
+++ b/res/drawable/camera_crop_height.png
Binary files differ
diff --git a/res/drawable/camera_crop_width.png b/res/drawable/camera_crop_width.png
new file mode 100644
index 0000000..65216af
--- /dev/null
+++ b/res/drawable/camera_crop_width.png
Binary files differ
diff --git a/res/drawable/detail_photo_border.9.png b/res/drawable/detail_photo_border.9.png
new file mode 100644
index 0000000..6de37b7
--- /dev/null
+++ b/res/drawable/detail_photo_border.9.png
Binary files differ
diff --git a/res/drawable/frame_gallery_preview.xml b/res/drawable/frame_gallery_preview.xml
new file mode 100644
index 0000000..5550bff
--- /dev/null
+++ b/res/drawable/frame_gallery_preview.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_window_focused="false"
+        android:drawable="@drawable/frame_gallery_preview_album" />
+
+    <item android:state_selected="true" android:state_pressed="true"
+        android:drawable="@drawable/frame_gallery_preview_album_pressed" />
+        
+    <item android:state_selected="false" android:state_pressed="true"
+        android:drawable="@drawable/frame_gallery_preview_album_pressed" />
+
+    <item android:state_selected="true"
+        android:drawable="@drawable/frame_gallery_preview_album_selected" />
+
+</selector>
diff --git a/res/drawable/frame_gallery_preview_album.png b/res/drawable/frame_gallery_preview_album.png
new file mode 100644
index 0000000..5b92ee2
--- /dev/null
+++ b/res/drawable/frame_gallery_preview_album.png
Binary files differ
diff --git a/res/drawable/frame_gallery_preview_album_mask.png b/res/drawable/frame_gallery_preview_album_mask.png
new file mode 100644
index 0000000..b053f30
--- /dev/null
+++ b/res/drawable/frame_gallery_preview_album_mask.png
Binary files differ
diff --git a/res/drawable/frame_gallery_preview_album_pressed.png b/res/drawable/frame_gallery_preview_album_pressed.png
new file mode 100644
index 0000000..e3575ff
--- /dev/null
+++ b/res/drawable/frame_gallery_preview_album_pressed.png
Binary files differ
diff --git a/res/drawable/frame_gallery_preview_album_selected.png b/res/drawable/frame_gallery_preview_album_selected.png
new file mode 100644
index 0000000..222a38a
--- /dev/null
+++ b/res/drawable/frame_gallery_preview_album_selected.png
Binary files differ
diff --git a/res/drawable/frame_overlay_gallery_camera.png b/res/drawable/frame_overlay_gallery_camera.png
new file mode 100644
index 0000000..e0c24a6
--- /dev/null
+++ b/res/drawable/frame_overlay_gallery_camera.png
Binary files differ
diff --git a/res/drawable/frame_overlay_gallery_folder.png b/res/drawable/frame_overlay_gallery_folder.png
new file mode 100644
index 0000000..05f64cc
--- /dev/null
+++ b/res/drawable/frame_overlay_gallery_folder.png
Binary files differ
diff --git a/res/drawable/frame_overlay_gallery_video.png b/res/drawable/frame_overlay_gallery_video.png
new file mode 100644
index 0000000..4b84854
--- /dev/null
+++ b/res/drawable/frame_overlay_gallery_video.png
Binary files differ
diff --git a/res/drawable/frame_thumbnail.xml b/res/drawable/frame_thumbnail.xml
new file mode 100644
index 0000000..0cbbacc
--- /dev/null
+++ b/res/drawable/frame_thumbnail.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/frame_thumbnail_pressed" />
+    <item android:state_window_focused="true" android:state_focused="true" android:drawable="@drawable/frame_thumbnail_selected" />
+    <item android:drawable="@drawable/frame_thumbnail_default" />
+</selector>
diff --git a/res/drawable/frame_thumbnail_default.png b/res/drawable/frame_thumbnail_default.png
new file mode 100644
index 0000000..3db932e
--- /dev/null
+++ b/res/drawable/frame_thumbnail_default.png
Binary files differ
diff --git a/res/drawable/frame_thumbnail_pressed.png b/res/drawable/frame_thumbnail_pressed.png
new file mode 100644
index 0000000..b15aea7
--- /dev/null
+++ b/res/drawable/frame_thumbnail_pressed.png
Binary files differ
diff --git a/res/drawable/frame_thumbnail_selected.png b/res/drawable/frame_thumbnail_selected.png
new file mode 100644
index 0000000..408b14a
--- /dev/null
+++ b/res/drawable/frame_thumbnail_selected.png
Binary files differ
diff --git a/res/drawable/grid_background.xml b/res/drawable/grid_background.xml
new file mode 100644
index 0000000..fad1d2a
--- /dev/null
+++ b/res/drawable/grid_background.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* grid_background.xml
+**
+** Copyright 2006, 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.
+*/
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/image_border_bg_pressed_blue" />
+    <item android:state_window_focused="true" android:state_focused="true" android:drawable="@drawable/image_border_bg_focus_blue" />
+    <item android:drawable="@drawable/image_border_bg_normal" />
+</selector>
diff --git a/res/drawable/ic_btn_actionmenu_attach_default.png b/res/drawable/ic_btn_actionmenu_attach_default.png
new file mode 100644
index 0000000..78d6e1b
--- /dev/null
+++ b/res/drawable/ic_btn_actionmenu_attach_default.png
Binary files differ
diff --git a/res/drawable/ic_btn_actionmenu_attach_pressed.png b/res/drawable/ic_btn_actionmenu_attach_pressed.png
new file mode 100644
index 0000000..01cbe46
--- /dev/null
+++ b/res/drawable/ic_btn_actionmenu_attach_pressed.png
Binary files differ
diff --git a/res/drawable/ic_btn_actionmenu_attach_selected.png b/res/drawable/ic_btn_actionmenu_attach_selected.png
new file mode 100644
index 0000000..ee7af0b
--- /dev/null
+++ b/res/drawable/ic_btn_actionmenu_attach_selected.png
Binary files differ
diff --git a/res/drawable/ic_btn_actionmenu_cancel_default.png b/res/drawable/ic_btn_actionmenu_cancel_default.png
new file mode 100644
index 0000000..cb8f7bc
--- /dev/null
+++ b/res/drawable/ic_btn_actionmenu_cancel_default.png
Binary files differ
diff --git a/res/drawable/ic_btn_actionmenu_cancel_pressed.png b/res/drawable/ic_btn_actionmenu_cancel_pressed.png
new file mode 100644
index 0000000..02bdb49
--- /dev/null
+++ b/res/drawable/ic_btn_actionmenu_cancel_pressed.png
Binary files differ
diff --git a/res/drawable/ic_btn_actionmenu_cancel_selected.png b/res/drawable/ic_btn_actionmenu_cancel_selected.png
new file mode 100644
index 0000000..a0b6017
--- /dev/null
+++ b/res/drawable/ic_btn_actionmenu_cancel_selected.png
Binary files differ
diff --git a/res/drawable/ic_btn_actionmenu_delete_default.png b/res/drawable/ic_btn_actionmenu_delete_default.png
new file mode 100644
index 0000000..7e25f0d
--- /dev/null
+++ b/res/drawable/ic_btn_actionmenu_delete_default.png
Binary files differ
diff --git a/res/drawable/ic_btn_actionmenu_delete_pressed.png b/res/drawable/ic_btn_actionmenu_delete_pressed.png
new file mode 100644
index 0000000..94087f6
--- /dev/null
+++ b/res/drawable/ic_btn_actionmenu_delete_pressed.png
Binary files differ
diff --git a/res/drawable/ic_btn_actionmenu_delete_selected.png b/res/drawable/ic_btn_actionmenu_delete_selected.png
new file mode 100644
index 0000000..780310b
--- /dev/null
+++ b/res/drawable/ic_btn_actionmenu_delete_selected.png
Binary files differ
diff --git a/res/drawable/ic_btn_actionmenu_gallery_default.png b/res/drawable/ic_btn_actionmenu_gallery_default.png
new file mode 100644
index 0000000..a2d38db
--- /dev/null
+++ b/res/drawable/ic_btn_actionmenu_gallery_default.png
Binary files differ
diff --git a/res/drawable/ic_btn_actionmenu_gallery_pressed.png b/res/drawable/ic_btn_actionmenu_gallery_pressed.png
new file mode 100644
index 0000000..a01b7f8
--- /dev/null
+++ b/res/drawable/ic_btn_actionmenu_gallery_pressed.png
Binary files differ
diff --git a/res/drawable/ic_btn_actionmenu_gallery_selected.png b/res/drawable/ic_btn_actionmenu_gallery_selected.png
new file mode 100644
index 0000000..400a63b
--- /dev/null
+++ b/res/drawable/ic_btn_actionmenu_gallery_selected.png
Binary files differ
diff --git a/res/drawable/ic_btn_actionmenu_play_default.png b/res/drawable/ic_btn_actionmenu_play_default.png
new file mode 100644
index 0000000..17e0748
--- /dev/null
+++ b/res/drawable/ic_btn_actionmenu_play_default.png
Binary files differ
diff --git a/res/drawable/ic_btn_actionmenu_play_pressed.png b/res/drawable/ic_btn_actionmenu_play_pressed.png
new file mode 100644
index 0000000..c671669
--- /dev/null
+++ b/res/drawable/ic_btn_actionmenu_play_pressed.png
Binary files differ
diff --git a/res/drawable/ic_btn_actionmenu_play_selected.png b/res/drawable/ic_btn_actionmenu_play_selected.png
new file mode 100644
index 0000000..35e1425
--- /dev/null
+++ b/res/drawable/ic_btn_actionmenu_play_selected.png
Binary files differ
diff --git a/res/drawable/ic_btn_actionmenu_set_as_default.png b/res/drawable/ic_btn_actionmenu_set_as_default.png
new file mode 100644
index 0000000..0d341c7
--- /dev/null
+++ b/res/drawable/ic_btn_actionmenu_set_as_default.png
Binary files differ
diff --git a/res/drawable/ic_btn_actionmenu_set_as_pressed.png b/res/drawable/ic_btn_actionmenu_set_as_pressed.png
new file mode 100644
index 0000000..58dc85c
--- /dev/null
+++ b/res/drawable/ic_btn_actionmenu_set_as_pressed.png
Binary files differ
diff --git a/res/drawable/ic_btn_actionmenu_set_as_selected.png b/res/drawable/ic_btn_actionmenu_set_as_selected.png
new file mode 100644
index 0000000..6281229
--- /dev/null
+++ b/res/drawable/ic_btn_actionmenu_set_as_selected.png
Binary files differ
diff --git a/res/drawable/ic_btn_actionmenu_share_default.png b/res/drawable/ic_btn_actionmenu_share_default.png
new file mode 100644
index 0000000..19f6ef4
--- /dev/null
+++ b/res/drawable/ic_btn_actionmenu_share_default.png
Binary files differ
diff --git a/res/drawable/ic_btn_actionmenu_share_pressed.png b/res/drawable/ic_btn_actionmenu_share_pressed.png
new file mode 100644
index 0000000..8583114
--- /dev/null
+++ b/res/drawable/ic_btn_actionmenu_share_pressed.png
Binary files differ
diff --git a/res/drawable/ic_btn_actionmenu_share_selected.png b/res/drawable/ic_btn_actionmenu_share_selected.png
new file mode 100644
index 0000000..fc9679b
--- /dev/null
+++ b/res/drawable/ic_btn_actionmenu_share_selected.png
Binary files differ
diff --git a/res/drawable/ic_btn_camera_background.xml b/res/drawable/ic_btn_camera_background.xml
new file mode 100644
index 0000000..2e28a80
--- /dev/null
+++ b/res/drawable/ic_btn_camera_background.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2006, 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.
+*/
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/ic_btn_camera_pressed_background" />
+    <item android:state_window_focused="true" android:state_focused="true" android:drawable="@drawable/ic_btn_camera_selected_background" />
+    <item android:drawable="@drawable/ic_btn_camera_default_background" />
+</selector>
diff --git a/res/drawable/ic_btn_camera_default_background.png b/res/drawable/ic_btn_camera_default_background.png
new file mode 100644
index 0000000..c37a86e
--- /dev/null
+++ b/res/drawable/ic_btn_camera_default_background.png
Binary files differ
diff --git a/res/drawable/ic_btn_camera_pressed_background.png b/res/drawable/ic_btn_camera_pressed_background.png
new file mode 100644
index 0000000..8707b8a
--- /dev/null
+++ b/res/drawable/ic_btn_camera_pressed_background.png
Binary files differ
diff --git a/res/drawable/ic_btn_camera_selected_background.png b/res/drawable/ic_btn_camera_selected_background.png
new file mode 100644
index 0000000..a6bbdb1
--- /dev/null
+++ b/res/drawable/ic_btn_camera_selected_background.png
Binary files differ
diff --git a/res/drawable/ic_camera_bar_indicator_record.png b/res/drawable/ic_camera_bar_indicator_record.png
new file mode 100644
index 0000000..60f7a01
--- /dev/null
+++ b/res/drawable/ic_camera_bar_indicator_record.png
Binary files differ
diff --git a/res/drawable/ic_camera_indicator_auto_focus_green.png b/res/drawable/ic_camera_indicator_auto_focus_green.png
new file mode 100644
index 0000000..7bc09db
--- /dev/null
+++ b/res/drawable/ic_camera_indicator_auto_focus_green.png
Binary files differ
diff --git a/res/drawable/ic_camera_indicator_photo.png b/res/drawable/ic_camera_indicator_photo.png
new file mode 100644
index 0000000..3b0483b
--- /dev/null
+++ b/res/drawable/ic_camera_indicator_photo.png
Binary files differ
diff --git a/res/drawable/ic_camera_indicator_video.png b/res/drawable/ic_camera_indicator_video.png
new file mode 100644
index 0000000..a4cb8d0
--- /dev/null
+++ b/res/drawable/ic_camera_indicator_video.png
Binary files differ
diff --git a/res/drawable/ic_error_mms_video_overlay.png b/res/drawable/ic_error_mms_video_overlay.png
new file mode 100644
index 0000000..4f50072
--- /dev/null
+++ b/res/drawable/ic_error_mms_video_overlay.png
Binary files differ
diff --git a/res/drawable/ic_gallery_empty2.png b/res/drawable/ic_gallery_empty2.png
new file mode 100644
index 0000000..f950e08
--- /dev/null
+++ b/res/drawable/ic_gallery_empty2.png
Binary files differ
diff --git a/res/drawable/ic_gallery_video_overlay.png b/res/drawable/ic_gallery_video_overlay.png
new file mode 100644
index 0000000..ae263a7
--- /dev/null
+++ b/res/drawable/ic_gallery_video_overlay.png
Binary files differ
diff --git a/res/drawable/ic_launcher_camera.png b/res/drawable/ic_launcher_camera.png
new file mode 100644
index 0000000..9bb4c61
--- /dev/null
+++ b/res/drawable/ic_launcher_camera.png
Binary files differ
diff --git a/res/drawable/ic_launcher_gallery.png b/res/drawable/ic_launcher_gallery.png
new file mode 100644
index 0000000..965fb71
--- /dev/null
+++ b/res/drawable/ic_launcher_gallery.png
Binary files differ
diff --git a/res/drawable/ic_menu_attach.xml b/res/drawable/ic_menu_attach.xml
new file mode 100644
index 0000000..209f6b5
--- /dev/null
+++ b/res/drawable/ic_menu_attach.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+** Copyright 2006, 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.
+*/
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/ic_btn_actionmenu_attach_pressed" />
+    <item android:state_window_focused="true" android:state_focused="true" android:drawable="@drawable/ic_btn_actionmenu_attach_selected" />
+    <item android:drawable="@drawable/ic_btn_actionmenu_attach_default" />
+</selector>
diff --git a/res/drawable/ic_menu_camera_play.xml b/res/drawable/ic_menu_camera_play.xml
new file mode 100644
index 0000000..3a70e69
--- /dev/null
+++ b/res/drawable/ic_menu_camera_play.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* grid_background.xml
+**
+** Copyright 2006, 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.
+*/
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/ic_btn_actionmenu_play_pressed" />
+    <item android:state_window_focused="true" android:state_focused="true" android:drawable="@drawable/ic_btn_actionmenu_play_selected" />
+    <item android:drawable="@drawable/ic_btn_actionmenu_play_default" />
+</selector>
diff --git a/res/drawable/ic_menu_camera_video_view.png b/res/drawable/ic_menu_camera_video_view.png
new file mode 100644
index 0000000..f7e52c2
--- /dev/null
+++ b/res/drawable/ic_menu_camera_video_view.png
Binary files differ
diff --git a/res/drawable/ic_menu_cancel.xml b/res/drawable/ic_menu_cancel.xml
new file mode 100644
index 0000000..92960d9
--- /dev/null
+++ b/res/drawable/ic_menu_cancel.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+** Copyright 2006, 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.
+*/
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/ic_btn_actionmenu_cancel_pressed" />
+    <item android:state_window_focused="true" android:state_focused="true" android:drawable="@drawable/ic_btn_actionmenu_cancel_selected" />
+    <item android:drawable="@drawable/ic_btn_actionmenu_cancel_default" />
+</selector>
diff --git a/res/drawable/ic_menu_delete.xml b/res/drawable/ic_menu_delete.xml
new file mode 100644
index 0000000..efea2df
--- /dev/null
+++ b/res/drawable/ic_menu_delete.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* grid_background.xml
+**
+** Copyright 2006, 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.
+*/
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/ic_btn_actionmenu_delete_pressed" />
+    <item android:state_window_focused="true" android:state_focused="true" android:drawable="@drawable/ic_btn_actionmenu_delete_selected" />
+    <item android:drawable="@drawable/ic_btn_actionmenu_delete_default" />
+</selector>
diff --git a/res/drawable/ic_menu_gallery.xml b/res/drawable/ic_menu_gallery.xml
new file mode 100644
index 0000000..4b0319a
--- /dev/null
+++ b/res/drawable/ic_menu_gallery.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* grid_background.xml
+**
+** Copyright 2006, 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.
+*/
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/ic_btn_actionmenu_gallery_pressed" />
+    <item android:state_window_focused="true" android:state_focused="true" android:drawable="@drawable/ic_btn_actionmenu_gallery_selected" />
+    <item android:drawable="@drawable/ic_btn_actionmenu_gallery_default" />
+</selector>
diff --git a/res/drawable/ic_menu_set_as.xml b/res/drawable/ic_menu_set_as.xml
new file mode 100644
index 0000000..119e712
--- /dev/null
+++ b/res/drawable/ic_menu_set_as.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* grid_background.xml
+**
+** Copyright 2006, 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.
+*/
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/ic_btn_actionmenu_set_as_pressed" />
+    <item android:state_window_focused="true" android:state_focused="true" android:drawable="@drawable/ic_btn_actionmenu_set_as_selected" />
+    <item android:drawable="@drawable/ic_btn_actionmenu_set_as_default" />
+</selector>
diff --git a/res/drawable/ic_menu_share.xml b/res/drawable/ic_menu_share.xml
new file mode 100644
index 0000000..cf376ac
--- /dev/null
+++ b/res/drawable/ic_menu_share.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* grid_background.xml
+**
+** Copyright 2006, 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.
+*/
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/ic_btn_actionmenu_share_pressed" />
+    <item android:state_window_focused="true" android:state_focused="true" android:drawable="@drawable/ic_btn_actionmenu_share_selected" />
+    <item android:drawable="@drawable/ic_btn_actionmenu_share_default" />
+</selector>
diff --git a/res/drawable/ic_menu_view_details.png b/res/drawable/ic_menu_view_details.png
new file mode 100644
index 0000000..a5a184f
--- /dev/null
+++ b/res/drawable/ic_menu_view_details.png
Binary files differ
diff --git a/res/drawable/ic_missing_thumbnail_picture.png b/res/drawable/ic_missing_thumbnail_picture.png
new file mode 100644
index 0000000..ebab487
--- /dev/null
+++ b/res/drawable/ic_missing_thumbnail_picture.png
Binary files differ
diff --git a/res/drawable/ic_missing_thumbnail_video.png b/res/drawable/ic_missing_thumbnail_video.png
new file mode 100644
index 0000000..b44d821
--- /dev/null
+++ b/res/drawable/ic_missing_thumbnail_video.png
Binary files differ
diff --git a/res/drawable/image_border_bg_focus_blue.9.png b/res/drawable/image_border_bg_focus_blue.9.png
new file mode 100644
index 0000000..89debd2
--- /dev/null
+++ b/res/drawable/image_border_bg_focus_blue.9.png
Binary files differ
diff --git a/res/drawable/image_border_bg_normal.9.png b/res/drawable/image_border_bg_normal.9.png
new file mode 100644
index 0000000..18e3607
--- /dev/null
+++ b/res/drawable/image_border_bg_normal.9.png
Binary files differ
diff --git a/res/drawable/image_border_bg_pressed_blue.9.png b/res/drawable/image_border_bg_pressed_blue.9.png
new file mode 100644
index 0000000..94fa74f
--- /dev/null
+++ b/res/drawable/image_border_bg_pressed_blue.9.png
Binary files differ
diff --git a/res/drawable/indicator_autocrop.png b/res/drawable/indicator_autocrop.png
new file mode 100644
index 0000000..d960b1f
--- /dev/null
+++ b/res/drawable/indicator_autocrop.png
Binary files differ
diff --git a/res/drawable/on_screen_hint_frame.9.png b/res/drawable/on_screen_hint_frame.9.png
new file mode 100755
index 0000000..08c4f86
--- /dev/null
+++ b/res/drawable/on_screen_hint_frame.9.png
Binary files differ
diff --git a/res/drawable/photo_frame.9.png b/res/drawable/photo_frame.9.png
new file mode 100644
index 0000000..b153260
--- /dev/null
+++ b/res/drawable/photo_frame.9.png
Binary files differ
diff --git a/res/layout/camera.xml b/res/layout/camera.xml
new file mode 100644
index 0000000..d23066e
--- /dev/null
+++ b/res/layout/camera.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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"
+        android:id="@+id/camera"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:orientation="horizontal">
+
+    <com.android.camera.VideoPreview
+        android:id="@+id/camera_preview"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:layout_centerInParent="true" />
+          
+    <View
+        android:id="@+id/blackout"
+        android:background="#ff000000"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:visibility="invisible" />
+
+    <com.android.camera.ShutterButton
+        android:id="@+id/shutter_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@drawable/ic_camera_indicator_photo"
+        android:background="@drawable/ic_btn_camera_background"
+        android:clickable="true"
+        android:focusable="true"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentTop="true"
+        android:layout_marginRight="50dip"
+        android:layout_marginTop="10dip"
+        android:scaleType="center" />
+
+    <ImageView
+        android:id="@+id/focus_indicator"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@drawable/ic_camera_indicator_auto_focus_green"
+        android:layout_alignLeft="@id/shutter_button"
+        android:layout_alignTop="@id/shutter_button"
+        android:layout_marginLeft="39dip"
+        android:layout_marginTop="16dip"
+        android:scaleType="center"
+        android:visibility="gone" />
+        
+    <ImageView
+        android:visibility="gone"
+        android:id="@+id/last_picture_button"
+        android:layout_width="72dip"
+        android:layout_height="72dip"
+        android:clickable="true"
+        android:focusable="true"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentTop="true"
+        android:layout_marginLeft="10dip"
+        android:layout_marginTop="10dip" />
+</RelativeLayout>
diff --git a/res/layout/cropimage.xml b/res/layout/cropimage.xml
new file mode 100644
index 0000000..4f3dcb7
--- /dev/null
+++ b/res/layout/cropimage.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" 	 	
+	android:layout_width="fill_parent" 
+	android:layout_height="fill_parent"> 
+	
+	<RelativeLayout 	 	
+		android:layout_width="fill_parent" 
+		android:layout_height="fill_parent"
+		android:orientation="horizontal">
+
+ 	    <view class="com.android.camera.CropImage$CropImageView" android:id="@+id/image"
+	    	android:background="#55000000"
+	    	android:layout_width="fill_parent"
+	    	android:layout_height="fill_parent"
+	    	android:layout_x="0dip"
+	    	android:layout_y="0dip"
+	    />
+	    <RelativeLayout android:layout_width="wrap_content" 
+	    		android:layout_height="wrap_content" 
+	    		android:orientation="horizontal"
+                android:paddingLeft="10dip"
+                android:paddingRight="10dip"
+	            android:layout_alignParentBottom="true"
+                android:layout_centerHorizontal="true">
+		    <Button
+                android:id="@+id/save"
+                android:layout_width="100dip"
+                android:layout_height="wrap_content"
+                android:layout_alignParentLeft="true"
+                android:text="@string/crop_save_text"
+            />
+		    <Button
+                android:id="@+id/discard"
+                android:layout_width="100dip"
+                android:layout_height="wrap_content"
+                android:layout_alignParentRight="true"
+                android:text="@string/crop_discard_text"
+            />
+        </RelativeLayout>
+	</RelativeLayout>
+
+</FrameLayout>
+
diff --git a/res/layout/custom_gallery_title.xml b/res/layout/custom_gallery_title.xml
new file mode 100644
index 0000000..0652678
--- /dev/null
+++ b/res/layout/custom_gallery_title.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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"
+    android:id="@+id/screen" android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+
+    <TextView android:id="@+id/left_text"
+        android:gravity="center_vertical"
+        style="?android:attr/windowTitleStyle"
+        android:layout_width="wrap_content"
+        android:layout_height="fill_parent" android:paddingLeft="5dip"
+        android:layout_alignParentLeft="true" />
+
+    <TextView android:id="@+id/right_text"
+        android:gravity="center_vertical"
+        style="?android:attr/windowTitleStyle"
+        android:layout_width="wrap_content"
+        android:layout_height="fill_parent" android:paddingRight="5dip"
+        android:layout_alignParentRight="true"
+        android:visibility="gone" />
+
+    <LinearLayout android:id="@+id/loading_indicator"
+        android:orientation="horizontal"
+        android:gravity="center_vertical"
+        android:paddingTop="2dip"
+        android:layout_alignParentRight="true"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content">
+
+        <TextView android:id="@+id/loading_text"
+            android:gravity="center_vertical"
+            style="?android:attr/windowTitleStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingRight="5dip"
+            android:layout_alignParentRight="true" />
+
+        <ProgressBar android:id="@android:id/progress"
+            style="?android:attr/progressBarStyleSmallTitle"
+            android:gravity="center_vertical"
+            android:paddingRight="5dip"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+    </LinearLayout>
+</RelativeLayout>
diff --git a/res/layout/details.xml b/res/layout/details.xml
new file mode 100644
index 0000000..482e567
--- /dev/null
+++ b/res/layout/details.xml
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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:padding="10dip"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content">
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/details_title_label"
+            android:textSize="10sp"
+        />
+
+    </LinearLayout>
+
+    <EditText
+        android:id="@+id/title"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/details_title_text"
+        android:autoText="true"
+        android:capitalize="words"
+        android:textSize="10sp"
+        android:textColor="#FF661700"
+    />
+
+    <TextView
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/details_description_label"
+        android:textSize="10sp"
+    />
+
+    <EditText
+        android:id="@+id/description"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/details_description_text"
+        android:autoText="true"
+        android:capitalize="sentences"
+        android:textSize="10sp"
+        android:textColor="#FF661700"
+    />
+
+    <TextView android:id="@+id/tags_label"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/details_tags_label"
+        android:textSize="10sp"
+        android:visibility="gone"
+    />
+
+    <EditText
+        android:id="@+id/tags"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/details_tags_text"
+        android:textSize="10sp"
+        android:textColor="#FF661700"
+        android:autoText="false"
+        android:capitalize="none"
+        android:visibility="gone"
+    />
+
+    <LinearLayout android:id="@+id/categories"
+        android:orientation="horizontal"
+        android:visibility="gone"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/details_category_label"
+            android:textSize="10sp"
+        />
+
+        <Spinner android:id="@+id/category"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:drawSelectorOnTop="false"
+            android:textSize="10sp"
+        />
+    </LinearLayout>
+
+    <LinearLayout android:id="@+id/languages"
+        android:orientation="horizontal"
+        android:visibility="gone"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/details_language_label"
+            android:textSize="10sp"
+        />
+
+        <Spinner android:id="@+id/language"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:drawSelectorOnTop="false"
+            android:textSize="10sp"
+        />
+    </LinearLayout>
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content">
+
+        <RadioGroup android:id="@+id/publicprivate"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+	        <RadioButton android:id="@+id/publicView"
+	            android:layout_width="wrap_content"
+	            android:layout_height="wrap_content"
+	            android:checked="false"
+	            android:text="@string/details_publicView_text"
+	        />
+	
+	        <RadioButton android:id="@+id/privateView"
+	            android:layout_width="wrap_content"
+	            android:layout_height="wrap_content"
+	            android:checked="false"
+	            android:text="@string/details_privateView_text"
+	        />
+	    </RadioGroup>
+
+        <Button android:id="@+id/save"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/details_save_text"
+            android:layout_alignParentRight="true"
+        />
+    </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/detailsview.xml b/res/layout/detailsview.xml
new file mode 100644
index 0000000..e28f06c
--- /dev/null
+++ b/res/layout/detailsview.xml
@@ -0,0 +1,166 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/scroll_view"
+    android:orientation="vertical"
+    android:layout_width="300dip"
+    android:layout_height="fill_parent">
+
+	<LinearLayout 
+		android:orientation="vertical"
+		android:layout_width="fill_parent"
+		android:layout_height="wrap_content"
+		android:paddingLeft="5dip"
+		android:paddingRight="5dip"
+		android:paddingBottom="10dp">
+		
+		<LinearLayout
+			android:orientation="horizontal"
+			android:gravity="center_vertical"
+			android:layout_width="fill_parent"
+			android:layout_height="wrap_content">
+
+			<LinearLayout
+			    android:orientation="horizontal"
+			    android:gravity="center_vertical"
+			    android:layout_width="wrap_content"
+			    android:layout_height="wrap_content"
+                android:background="@drawable/detail_photo_border">
+			    <ImageView
+				    android:id="@+id/details_thumbnail_image"
+				    android:layout_width="64dip"
+				    android:layout_height="64dip"/>
+		    </LinearLayout>
+
+		    <TextView
+		    	android:id="@+id/details_image_title"
+        	   	android:layout_height="wrap_content" 
+			   	android:layout_width="wrap_content"
+			   	android:textAppearance="?android:attr/textAppearanceMedium"
+			   	android:paddingLeft="6dip"
+			   	android:layout_weight="1"/>
+		
+		</LinearLayout>
+
+		<TableLayout
+		    android:layout_width="fill_parent"
+		    android:layout_height="wrap_content"
+		    android:paddingLeft="5dip"
+		    android:layout_marginTop="10dip">
+		
+		    <TableRow>
+		        <TextView
+				    android:gravity="right"
+				    android:textAppearance="?android:attr/textAppearanceSmall" 
+				    android:text="@string/details_file_size"/>
+		        <TextView
+		            android:id="@+id/details_file_size_value"
+		            android:textAppearance="?android:attr/textAppearanceSmall"
+		            android:paddingLeft="5dip"
+		            android:textColor="?android:attr/textColorPrimary"/>
+		    </TableRow>
+		
+		    <TableRow>
+		        <TextView
+		            android:gravity="right"
+		            android:textAppearance="?android:attr/textAppearanceSmall"
+		            android:text="@string/details_image_resolution"/>
+		        <TextView
+		            android:id="@+id/details_resolution_value"
+        	   	    android:layout_height="wrap_content" 
+			   	    android:layout_width="fill_parent"
+		            android:textAppearance="?android:attr/textAppearanceSmall"
+		            android:paddingLeft="5dip"
+		            android:textColor="?android:attr/textColorPrimary"/>
+		    </TableRow>
+		    <TableRow
+		    	android:id="@+id/details_duration_row">
+		        <TextView
+		            android:gravity="right"
+		            android:textAppearance="?android:attr/textAppearanceSmall"
+		            android:text="@string/details_duration"/>
+		        <TextView
+		            android:id="@+id/details_duration_value"
+		            android:textAppearance="?android:attr/textAppearanceSmall"
+		            android:paddingLeft="5dip"
+		            android:textColor="?android:attr/textColorPrimary"/>
+		    </TableRow>
+		    <TableRow
+		    	android:id="@+id/details_frame_rate_row">
+		        <TextView
+		            android:gravity="right"
+		            android:textAppearance="?android:attr/textAppearanceSmall"
+		            android:text="@string/details_frame_rate"/>
+		        <TextView
+		            android:id="@+id/details_frame_rate_value"
+		            android:textAppearance="?android:attr/textAppearanceSmall"
+		            android:paddingLeft="5dip"
+		            android:textColor="?android:attr/textColorPrimary"/>
+		    </TableRow>
+		    
+		    <TableRow
+		    	android:id="@+id/details_bit_rate_row">
+		        <TextView
+		            android:gravity="right"
+		            android:textAppearance="?android:attr/textAppearanceSmall"
+		            android:text="@string/details_bit_rate"/>
+		        <TextView
+		            android:id="@+id/details_bit_rate_value"
+		            android:textAppearance="?android:attr/textAppearanceSmall"
+		            android:paddingLeft="5dip"
+		            android:textColor="?android:attr/textColorPrimary"/>
+		    </TableRow>
+		    <TableRow
+		    	android:id="@+id/details_format_row">
+		        <TextView
+		            android:gravity="right"
+		            android:textAppearance="?android:attr/textAppearanceSmall"
+		            android:text="@string/details_format"/>
+		        <TextView
+		            android:id="@+id/details_format_value"
+		            android:textAppearance="?android:attr/textAppearanceSmall"
+		            android:paddingLeft="5dip"
+		            android:textColor="?android:attr/textColorPrimary"/>
+		    </TableRow>
+		    <TableRow
+		    	android:id="@+id/details_codec_row">
+		        <TextView
+		            android:gravity="right"
+		            android:textAppearance="?android:attr/textAppearanceSmall"
+		            android:text="@string/details_codec"/>
+		        <TextView
+		            android:id="@+id/details_codec_value"
+		            android:textAppearance="?android:attr/textAppearanceSmall"
+		            android:paddingLeft="5dip"
+		            android:textColor="?android:attr/textColorPrimary"/>
+		    </TableRow>
+		    <TableRow
+		    	android:id="@+id/details_date_taken_row">
+		        <TextView
+		            android:gravity="right"
+		            android:textAppearance="?android:attr/textAppearanceSmall"
+		            android:text="@string/details_date_taken"/>
+		        <TextView
+		            android:id="@+id/details_date_taken_value"
+		            android:textAppearance="?android:attr/textAppearanceSmall"
+		            android:paddingLeft="5dip"
+		            android:textColor="?android:attr/textColorPrimary"/>
+		    </TableRow>
+		</TableLayout>
+
+	</LinearLayout>
+    
+</ScrollView>
diff --git a/res/layout/gallery.xml b/res/layout/gallery.xml
new file mode 100644
index 0000000..61a0db2
--- /dev/null
+++ b/res/layout/gallery.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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" 	 	
+    android:layout_width="fill_parent" 
+    android:layout_height="fill_parent"
+    android:orientation="horizontal">
+
+    <View android:background="#FF000000"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentLeft="true"
+    />
+
+    <ImageView android:id="@+id/switcher1"
+        android:background="#FF000000"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentLeft="true"
+    android:visibility="invisible"
+    />
+    
+    <ImageView android:id="@+id/switcher2"
+        android:background="#55000000"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentLeft="true"
+    android:visibility="invisible"
+    />
+    
+    <Gallery android:id="@+id/gallery"
+        android:background="#55000000"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+        android:layout_alignParentLeft="true"
+        
+        android:gravity="center_vertical"
+        android:spacing="16dp"
+    />
+    
+</RelativeLayout>
diff --git a/res/layout/gallery_picker_item.xml b/res/layout/gallery_picker_item.xml
new file mode 100644
index 0000000..62d8333
--- /dev/null
+++ b/res/layout/gallery_picker_item.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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="148dp"
+    android:layout_height="wrap_content"
+    android:gravity="center_horizontal">
+
+    <com.android.camera.GalleryPickerItem android:id="@+id/thumbnail"
+        android:layout_width="142dip"
+        android:layout_height="142dip"
+        android:scaleType="centerCrop" />
+
+    <TextView android:id="@+id/title"
+        android:paddingTop="3dip"
+        android:paddingBottom="9dip"
+        android:layout_width="wrap_content"
+        android:textSize="14sp"
+        android:singleLine="true"
+        android:textColor="#ffffffff"
+        android:ellipsize="middle"
+        android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/res/layout/gallerypicker.xml b/res/layout/gallerypicker.xml
new file mode 100644
index 0000000..4bc7789
--- /dev/null
+++ b/res/layout/gallerypicker.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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" android:id="@+id/root"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+	<GridView xmlns:android="http://schemas.android.com/apk/res/android"
+		android:id="@+id/albums"
+		android:layout_width="fill_parent"
+		android:layout_height="fill_parent"
+		android:paddingTop="2dip"
+		android:numColumns="auto_fit"
+		android:columnWidth="148dp"
+		android:stretchMode="spacingWidthUniform"
+		android:drawSelectorOnTop="false"
+		android:cacheColorHint="#000000"
+		android:background="#000000"/>
+
+    <RelativeLayout android:id="@+id/no_images"
+    	android:visibility="gone"
+        android:orientation="vertical"
+        android:layout_centerInParent="true"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent">
+
+	    <ImageView
+	    	android:id="@+id/no_pictures_image"
+			android:layout_centerInParent="true"
+            android:layout_gravity="center"
+	    	android:background="@drawable/ic_gallery_empty2"
+	        android:layout_width="wrap_content"
+	        android:layout_height="wrap_content"
+	    />
+
+	    <TextView
+	    	android:layout_below="@id/no_pictures_image"
+	    	android:layout_centerHorizontal="true"
+ 			android:paddingTop="5dip"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+        	android:text="@string/image_gallery_NoImageView_text"
+        	android:textColor="#FFBEBEBE"
+        	android:textSize="18dip"
+    	/>
+    </RelativeLayout>
+
+</RelativeLayout>
diff --git a/res/layout/grid.xml b/res/layout/grid.xml
new file mode 100644
index 0000000..2bdbc8a
--- /dev/null
+++ b/res/layout/grid.xml
@@ -0,0 +1,44 @@
+<!--
+/**
+ * Copyright (c) 2007, 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"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:background="#FFFFFF"> 
+
+    <ImageSwitcher android:id="@+id/switcher"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentLeft="true"
+    />
+
+    <GridView android:id="@+id/gallery"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+
+    android:padding="0dp"
+    android:verticalSpacing="0dp"
+    android:horizontalSpacing="0dp"
+    android:stretchMode="columnWidth"
+
+    android:gravity="center"
+    />
+
+</RelativeLayout>
+
diff --git a/res/layout/image_gallery_2.xml b/res/layout/image_gallery_2.xml
new file mode 100644
index 0000000..d7158a9
--- /dev/null
+++ b/res/layout/image_gallery_2.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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" android:id="@+id/root"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+
+    <view class="com.android.camera.ImageGallery2$GridViewSpecial"
+    	android:id="@+id/grid"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:listSelector="@drawable/grid_background"
+    />
+    
+    <RelativeLayout android:id="@+id/no_images"
+    	android:visibility="gone"
+        android:orientation="vertical"
+        android:layout_centerInParent="true"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent">
+
+	    <ImageView
+	    	android:id="@+id/no_pictures_image"
+			android:layout_centerInParent="true"
+            android:layout_gravity="center"
+	    	android:background="@drawable/ic_gallery_empty2"
+	        android:layout_width="wrap_content"
+	        android:layout_height="wrap_content"
+	    />
+
+	    <TextView
+	    	android:layout_below="@id/no_pictures_image"
+	    	android:layout_centerHorizontal="true"
+ 			android:paddingTop="5dip"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+        	android:text="@string/image_gallery_NoImageView_text"
+        	android:textColor="#FFBEBEBE"
+        	android:textSize="18dip"
+    	/>
+    </RelativeLayout>
+
+</RelativeLayout>
+
diff --git a/res/layout/movie_view.xml b/res/layout/movie_view.xml
new file mode 100644
index 0000000..6344d8b
--- /dev/null
+++ b/res/layout/movie_view.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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"
+    android:layout_width="fill_parent" 
+    android:layout_height="fill_parent">
+
+    <VideoView 
+     android:id="@+id/surface_view" 
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:layout_centerInParent="true"
+    />
+
+    <LinearLayout android:id="@+id/progress_indicator"
+        android:orientation="vertical"
+        android:layout_centerInParent="true"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content">
+
+        <ProgressBar android:id="@android:id/progress"
+            style="?android:attr/progressBarStyleLarge"
+            android:layout_gravity="center"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <TextView android:paddingTop="5dip"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:text="@string/loading_video" android:textSize="14sp"
+            android:textColor="#ffffffff" />
+    </LinearLayout>
+
+</RelativeLayout>
diff --git a/res/layout/on_screen_hint.xml b/res/layout/on_screen_hint.xml
new file mode 100644
index 0000000..a56b728
--- /dev/null
+++ b/res/layout/on_screen_hint.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2009, 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:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical"
+    android:background="@drawable/on_screen_hint_frame">
+
+    <TextView
+        android:id="@+id/message"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:textAppearance="@style/OnScreenHintTextAppearance.Small"
+        android:textColor="#ffffffff"
+        android:shadowColor="#BB000000"
+        android:shadowRadius="2.75"
+        />
+</LinearLayout>
+
+
diff --git a/res/layout/photo_frame.xml b/res/layout/photo_frame.xml
new file mode 100644
index 0000000..5537b1e
--- /dev/null
+++ b/res/layout/photo_frame.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/photo"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:scaleType="center"
+    android:cropToPadding="true"
+    android:background="@drawable/photo_frame"
+    />
diff --git a/res/layout/post_picture_panel.xml b/res/layout/post_picture_panel.xml
new file mode 100644
index 0000000..17cafe2
--- /dev/null
+++ b/res/layout/post_picture_panel.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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:visibility="gone"
+    android:id="@+id/post_picture_panel"
+    android:layout_alignBottom="@id/shutter_button"
+    android:layout_toLeftOf="@id/shutter_button"
+    android:layout_marginRight="15dip"
+    android:layout_width="wrap_content" 
+    android:layout_height="wrap_content"
+    android:orientation="horizontal">
+
+    <com.android.camera.ActionMenuButton
+        android:id="@+id/attach"
+        android:drawableTop="@drawable/ic_menu_attach"
+        android:text="@string/camera_attach"
+        style="@style/OnscreenActionIcon"
+    />
+    <com.android.camera.ActionMenuButton
+        android:id="@+id/cancel"
+        android:drawableTop="@drawable/ic_menu_cancel"
+        android:text="@string/camera_cancel"
+        style="@style/OnscreenActionIcon"
+    />
+</LinearLayout>
diff --git a/res/layout/slide_show.xml b/res/layout/slide_show.xml
new file mode 100644
index 0000000..e9bfe0f
--- /dev/null
+++ b/res/layout/slide_show.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+	android:layout_width="fill_parent"
+	android:layout_height="fill_parent">
+	
+	<view android:id="@+id/imageview"
+			class="com.android.camera.SlideShow$ImageViewTouch"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+    />
+
+</FrameLayout>
diff --git a/res/layout/video_camera.xml b/res/layout/video_camera.xml
new file mode 100644
index 0000000..da37bcd
--- /dev/null
+++ b/res/layout/video_camera.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:orientation="horizontal">
+
+    <com.android.camera.VideoPreview
+            android:id="@+id/camera_preview"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_centerInParent="true"
+            />
+          
+    <ImageView
+    		android:id="@+id/video_frame"
+    		android:layout_width="fill_parent"
+    		android:layout_height="fill_parent"
+    		android:visibility="gone" 
+    />
+    
+    <ImageView
+    		android:id="@+id/blackout"
+    		android:layout_width="fill_parent"
+    		android:layout_height="fill_parent"
+    		android:visibility="invisible" 
+    />
+
+    <com.android.camera.ShutterButton
+        android:id="@+id/shutter_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@drawable/ic_camera_indicator_video"
+	    android:background="@drawable/ic_btn_camera_background"
+        android:clickable="true"
+		android:focusable="true"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentTop="true"
+        android:layout_marginRight="50dip"
+        android:layout_marginTop="10dip"
+        android:scaleType="center"/>
+
+    <!-- Note: In this TextView the paddingRight="2"
+         attribute is required because otherwise the
+         text's drop shadow will be clipped. -->
+    <TextView
+        android:id="@+id/recording_time"
+        android:layout_width="90dip"
+        android:layout_height="wrap_content"
+        android:layout_toLeftOf="@id/shutter_button"
+        android:layout_alignTop="@id/shutter_button"
+        android:layout_marginTop="20dip"
+        android:layout_marginRight="6dip"
+        android:paddingRight="2dip"
+        android:shadowColor="#c0000000"
+        android:shadowDx="1"
+        android:shadowDy="1"
+        android:shadowRadius="1"
+        android:gravity="right"
+        android:textColor="#ffffffff"
+        android:textSize="20dip"
+        android:textStyle="bold"
+        android:visibility="gone"/>
+
+	<LinearLayout
+			android:visibility="gone"
+			android:id="@+id/post_picture_panel"
+			android:layout_alignTop="@id/shutter_button"
+			android:layout_toLeftOf="@id/shutter_button"
+            android:layout_marginRight="15dip"
+			android:layout_width="wrap_content" 
+			android:layout_height="wrap_content"
+			android:orientation="horizontal">
+
+        <com.android.camera.ActionMenuButton
+            android:id="@+id/gallery"
+            android:drawableTop="@drawable/ic_menu_gallery"
+            android:text="@string/camera_gallery"
+            style="@style/OnscreenActionIcon"
+        />
+        <com.android.camera.ActionMenuButton
+			android:id="@+id/play"
+			android:drawableTop="@drawable/ic_menu_camera_play"
+			android:text="@string/camera_play"
+			style="@style/OnscreenActionIcon"
+		/>
+		<com.android.camera.ActionMenuButton
+			android:id="@+id/share"
+			android:drawableTop="@drawable/ic_menu_share"
+			android:text="@string/camera_share"
+			style="@style/OnscreenActionIcon"
+		/>
+		<com.android.camera.ActionMenuButton
+			android:id="@+id/discard"
+			android:drawableTop="@drawable/ic_menu_delete"
+			android:text="@string/camera_toss"
+			style="@style/OnscreenActionIcon"
+		/>
+		<com.android.camera.ActionMenuButton
+			android:id="@+id/attach"
+			android:drawableTop="@drawable/ic_menu_attach"
+			android:text="@string/camera_attach"
+			style="@style/OnscreenActionIcon"
+		/>
+		<com.android.camera.ActionMenuButton
+			android:id="@+id/cancel"
+			android:drawableTop="@drawable/ic_menu_cancel"
+			android:text="@string/camera_cancel"
+			style="@style/OnscreenActionIcon"
+		/>
+	</LinearLayout>
+</RelativeLayout>
+
diff --git a/res/layout/viewimage.xml b/res/layout/viewimage.xml
new file mode 100644
index 0000000..99d76b0
--- /dev/null
+++ b/res/layout/viewimage.xml
@@ -0,0 +1,160 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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"
+    android:id="@+id/rootLayout"
+	android:layout_width="fill_parent"
+	android:layout_height="fill_parent">
+
+	<AbsoluteLayout android:id="@+id/slideShowContainer"
+		android:layout_width="fill_parent"
+		android:layout_height="fill_parent">
+		 	    <view android:id="@+id/image1_slideShow"
+					class="com.android.camera.ViewImage$ImageViewTouch"
+	    			android:background="#00000000"
+	    			android:layout_width="fill_parent"
+		    		android:layout_height="fill_parent"
+	    		/>
+		 	    <view android:id="@+id/image2_slideShow"
+					class="com.android.camera.ViewImage$ImageViewTouch"
+	    			android:background="#00000000"
+	    			android:layout_width="fill_parent"
+		    		android:layout_height="fill_parent"
+	    		/>
+	</AbsoluteLayout>	
+
+	<AbsoluteLayout android:id="@+id/abs"
+		android:layout_width="fill_parent"
+		android:layout_height="fill_parent">
+		<view class="com.android.camera.ViewImage$ScrollHandler" android:id="@+id/scroller"
+			android:layout_width="fill_parent"
+			android:layout_height="fill_parent"
+			android:orientation="horizontal"
+			
+	   		android:background="#FF000000"
+	   		android:layout_x="0dip"
+	   		android:layout_y="0dip">
+	   		
+		 	    <view android:id="@+id/image1"
+					class="com.android.camera.ViewImage$ImageViewTouch"
+	    			android:background="#FF000000"
+	    			android:layout_width="0dip"
+		    		android:layout_height="fill_parent" />
+	    	
+		    	<View android:id="@+id/padding1"
+	 		   		android:layout_height="fill_parent"
+		    		android:layout_width="0dip" />
+			    	
+		 	    <view android:id="@+id/image2"
+					class="com.android.camera.ViewImage$ImageViewTouch"
+	    			android:background="#FF000000"
+	    			android:layout_width="0dip"
+	    			android:layout_height="fill_parent"/>
+
+		    	<View android:id="@+id/padding2"
+		    		android:layout_height="fill_parent"
+		    		android:layout_width="0dip" />
+	    	
+		 	    <view android:id="@+id/image3"
+					class="com.android.camera.ViewImage$ImageViewTouch"
+	    			android:background="#FF000000"
+	    			android:layout_width="0dip"
+		    		android:layout_height="fill_parent"/>
+		</view>
+	</AbsoluteLayout>
+	
+	<ImageView
+        android:visibility="gone"
+		android:id="@+id/shutter_button"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:src="@drawable/ic_camera_indicator_photo"
+		android:background="@drawable/ic_btn_camera_background"
+		android:clickable="true"
+		android:focusable="true"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentTop="true"
+        android:layout_marginRight="50dip"
+        android:layout_marginTop="10dip"
+		android:scaleType="center"/>
+	
+    <LinearLayout
+            android:visibility="gone"
+            android:id="@+id/action_icon_panel"
+			android:layout_alignBottom="@id/shutter_button"
+			android:layout_toLeftOf="@id/shutter_button"
+            android:layout_marginRight="15dip"
+            android:layout_width="wrap_content" 
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+        <com.android.camera.ActionMenuButton
+            android:id="@+id/gallery"
+            android:drawableTop="@drawable/ic_menu_gallery"
+            android:text="@string/camera_gallery"
+            style="@style/OnscreenActionIcon"
+        />
+        <com.android.camera.ActionMenuButton
+            android:id="@+id/setas"
+            android:drawableTop="@drawable/ic_menu_set_as"
+            android:text="@string/camera_set"
+            style="@style/OnscreenActionIcon"
+        />
+        <com.android.camera.ActionMenuButton
+            android:id="@+id/share"
+            android:drawableTop="@drawable/ic_menu_share"
+            android:text="@string/camera_share"
+            style="@style/OnscreenActionIcon"
+        />
+        <com.android.camera.ActionMenuButton
+            android:id="@+id/discard"
+            android:drawableTop="@drawable/ic_menu_delete"
+            android:text="@string/camera_toss"
+            style="@style/OnscreenActionIcon"
+        />
+        <com.android.camera.ActionMenuButton
+            android:id="@+id/attach"
+            android:drawableTop="@drawable/ic_menu_attach"
+            android:text="@string/camera_attach"
+            style="@style/OnscreenActionIcon"
+        />
+        <com.android.camera.ActionMenuButton
+            android:id="@+id/cancel"
+            android:drawableTop="@drawable/ic_menu_cancel"
+            android:text="@string/camera_cancel"
+            style="@style/OnscreenActionIcon"
+        />
+    </LinearLayout>
+	<ImageView android:id="@+id/prev_image"
+		android:clickable="true"
+		android:layout_width="wrap_content" android:layout_height="wrap_content"
+		android:layout_alignParentLeft="true"
+		android:layout_centerVertical="true"
+		android:visibility="invisible"
+		android:src="@drawable/btn_camera_arrow_left"
+	/>
+
+	<ImageView android:id="@+id/next_image"
+		android:clickable="true"
+		android:layout_width="wrap_content" android:layout_height="wrap_content"
+		android:layout_alignParentRight="true"
+		android:layout_centerVertical="true"
+		android:visibility="invisible"
+		android:src="@drawable/btn_camera_arrow_right"
+	/>
+
+</RelativeLayout>
+
diff --git a/res/raw/camera_click.ogg b/res/raw/camera_click.ogg
new file mode 100644
index 0000000..0a769ff
--- /dev/null
+++ b/res/raw/camera_click.ogg
Binary files differ
diff --git a/res/values-cs/arrays.xml b/res/values-cs/arrays.xml
new file mode 100644
index 0000000..0583b87
--- /dev/null
+++ b/res/values-cs/arrays.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for pref_camera_videoquality_entries:0 (4921712231611437934) -->
+    <!-- no translation found for pref_camera_videoquality_entries:1 (3206329580124804732) -->
+  <string-array name="pref_camera_videoquality_entryvalues">
+    <item>"0"</item>
+    <item>"1"</item>
+  </string-array>
+</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
new file mode 100644
index 0000000..b0e06c1
--- /dev/null
+++ b/res/values-cs/strings.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="all_images">"Všechny fotografie"</string>
+    <string name="all_videos">"Všechna videa"</string>
+    <string name="camera_label">"Fotoaparát"</string>
+    <string name="gallery_picker_label">"Galerie"</string>
+    <string name="gallery_camera_bucket_name">"Pořízené fotografie"</string>
+    <string name="gallery_camera_videos_bucket_name">"Pořízená videa"</string>
+    <string name="switch_to_video_lable">"Přepnout do režimu video"</string>
+    <string name="switch_to_camera_lable">"Přepnout do režimu fotoaparát"</string>
+    <string name="crop_label">"Oříznout fotografii"</string>
+    <string name="view_label">"Zobrazit fotografii"</string>
+    <string name="preferences_label">"Nastavení fotoaparátu"</string>
+    <string name="wait">"Čekejte prosím..."</string>
+    <string name="no_storage">"Než začnete používat fotoaparát, vložte kartu SD."</string>
+    <string name="not_enough_space">"Vaše karta SD je plná."</string>
+    <string name="preparing_sd">"Příprava karty SD..."</string>
+    <string name="wallpaper">"Nastavování tapety, čekejte prosím..."</string>
+    <string name="savingImage">"Ukládání fotografie..."</string>
+    <string name="runningFaceDetection">"Čekejte prosím..."</string>
+    <string name="flip_orientation">"Změnit orientaci"</string>
+    <string name="settings">"Nastavení"</string>
+    <string name="view">"Zobrazit"</string>
+    <string name="details">"Podrobnosti"</string>
+    <string name="rotate">"Otočit"</string>
+    <string name="rotate_left">"Otočit doleva"</string>
+    <string name="rotate_right">"Otočit doprava"</string>
+    <string name="slide_show">"Prezentace"</string>
+    <string name="capture_picture">"Fotografovat"</string>
+    <string name="capture_video">"Natočit video"</string>
+    <string name="crop_save_text">"Uložit"</string>
+    <string name="crop_discard_text">"Zahodit"</string>
+    <string name="confirm_delete_title">"Smazat"</string>
+    <string name="confirm_delete_message">"Fotografie bude smazána."</string>
+    <string name="confirm_delete_video_message">"Video bude smazáno."</string>
+    <string name="camera_toss">"Smazat"</string>
+    <string name="camera_gallery">"Galerie"</string>
+    <string name="camera_share">"Sdílet"</string>
+    <string name="camera_set">"Použít jako"</string>
+    <string name="camera_play">"Přehrát"</string>
+    <string name="camera_attach">"Připojit přílohu"</string>
+    <string name="camera_cancel">"Zrušit"</string>
+    <string name="camera_crop">"Oříznout"</string>
+    <string name="camera_tossing">"Mazání..."</string>
+    <string name="no_way_to_share_image">"Tuto fotografii nelze sdílet."</string>
+    <string name="no_way_to_share_video">"Toto video nelze sdílet."</string>
+    <string name="camera_gallery_photos_text">"Galerie"</string>
+    <string name="camera_pick_wallpaper">"Fotografie"</string>
+    <string name="camera_setas_wallpaper">"Tapeta"</string>
+    <string name="pref_gallery_category">"Obecné nastavení"</string>
+    <string name="pref_slideshow_category">"Nastavení prezentace"</string>
+    <string name="pref_camera_general_settings_category">"Obecné nastavení"</string>
+    <string name="pref_gallery_size_title">"Zobrazovaná velikost"</string>
+    <string name="pref_gallery_size_summary">"Vyberte zobrazovanou velikost obrázků a videí"</string>
+    <string name="pref_gallery_size_dialogtitle">"Velikost fotografie"</string>
+  <string-array name="pref_gallery_size_choices">
+    <item>"Velké"</item>
+    <item>"Malé"</item>
+  </string-array>
+    <string name="pref_gallery_sort_title">"Řazení"</string>
+    <string name="pref_gallery_sort_summary">"Vyberte způsob řazení obrázků a videí"</string>
+    <string name="pref_gallery_sort_dialogtitle">"Řazení fotografií"</string>
+  <string-array name="pref_gallery_sort_choices">
+    <item>"Od nejnovějších"</item>
+    <item>"Od nejstarších"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_interval_title">"Interval prezentace"</string>
+    <string name="pref_gallery_slideshow_interval_summary">"Vyberte, jak dlouho má být zobrazen každý snímek"</string>
+    <string name="pref_gallery_slideshow_interval_dialogtitle">"Interval prezentace"</string>
+  <string-array name="pref_gallery_slideshow_interval_choices">
+    <item>"2 sekundy"</item>
+    <item>"3 sekundy"</item>
+    <item>"4 sekundy"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_transition_title">"Přechod v prezentaci"</string>
+    <string name="pref_gallery_slideshow_transition_summary">"Vyberte efekt, který má být použit při přechodu z jednoho snímku na další"</string>
+    <string name="pref_gallery_slideshow_transition_dialogtitle">"Přechod v prezentaci"</string>
+  <string-array name="pref_gallery_slideshow_transition_choices">
+    <item>"Prolínat"</item>
+    <item>"Posouvat zleva doprava"</item>
+    <item>"Posouvat shora dolů"</item>
+    <item>"Náhodný výběr"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_repeat_title">"Opakovat prezentaci"</string>
+    <string name="pref_gallery_slideshow_repeat_summary">"Přehrát prezentaci více než jednou"</string>
+    <string name="pref_gallery_slideshow_shuffle_title">"Náhodné pořadí snímků"</string>
+    <string name="pref_gallery_slideshow_shuffle_summary">"Zobrazit fotografie v náhodném pořadí"</string>
+    <string name="pref_camera_recordlocation_title">"K fotografiím ukládat místo pořízení"</string>
+    <string name="pref_camera_recordlocation_summary">"V datech fotografie zaznamenat místo pořízení"</string>
+    <string name="pref_camera_videoquality_category">"Kvalita videa"</string>
+    <string name="pref_camera_videoquality_title">"Vybrat kvalitu videa"</string>
+    <string name="pref_camera_videoquality_entry_0">"Nízká (pro zprávy MMS)"</string>
+    <string name="pref_camera_videoquality_entry_1">"Vysoká (pro kartu SD)"</string>
+    <string name="pref_camera_videoquality_dialogtitle">"Kvalita videa"</string>
+    <string name="camerasettings">"Nastavení"</string>
+    <string name="image_gallery_NoImageView_text">"Žádná média nebyla nalezena."</string>
+    <string name="pref_gallery_confirm_delete_title">"Potvrdit smazání"</string>
+    <string name="pref_gallery_confirm_delete_summary">"Žádat potvrzení před smazáním obrázku nebo videa"</string>
+    <string name="details_panel_title">"Podrobnosti"</string>
+    <string name="details_file_size">"Velikost souboru:"</string>
+    <string name="details_image_resolution">"Rozlišení:"</string>
+    <string name="details_duration">"Délka:"</string>
+    <string name="details_date_taken">"Datum pořízení:"</string>
+    <string name="details_frame_rate">"Počet snímků za sekundu:"</string>
+    <string name="details_bit_rate">"Bitová rychlost:"</string>
+    <string name="details_codec">"Kodek:"</string>
+    <string name="details_format">"Formát:"</string>
+    <string name="details_dimension_x">"%1$d x %2$d"</string>
+    <string name="details_ms">"%1$02d:%2$02d"</string>
+    <string name="details_hms">"%1$d:%2$02d:%3$02d"</string>
+    <string name="details_fps">"%1$d snímků/s"</string>
+    <string name="details_kbps">"%1$d Kb/s"</string>
+    <string name="details_mbps">"%1$g Mb/s"</string>
+    <string name="details_ok">"OK"</string>
+    <string name="context_menu_header">"Možnosti fotografií"</string>
+    <string name="video_context_menu_header">"Možnosti videa"</string>
+    <string name="multiface_crop_help">"Začněte klepnutím na obličej."</string>
+    <string name="photos_gallery_title">"Galerie"</string>
+    <string name="pick_photos_gallery_title">"Vyberte fotografii"</string>
+    <string name="videos_gallery_title">"Galerie"</string>
+    <string name="pick_videos_gallery_title">"Vybrat video"</string>
+    <string name="loading_progress_format_string">"Zbývá <xliff:g id="COUNTER">%d</xliff:g>"</string>
+    <string name="sendImage">"Sdílet fotografii pomocí"</string>
+    <string name="setImage">"Fotografie bude použita jako"</string>
+    <string name="sendVideo">"Sdílet video pomocí"</string>
+    <string name="movieviewlabel">"Filmy"</string>
+    <string name="loading_video">"Načítání videa..."</string>
+    <string name="spaceIsLow_title">"Dochází volné místo"</string>
+    <string name="spaceIsLow_content">"Na vaší kartě SD je málo místa. Změňte nastavení kvality nebo smažte položky z Galerie."</string>
+    <string name="resume_playing_title">"Pokračovat v přehrávání videa"</string>
+    <string name="resume_playing_message">"Pokračovat v přehrávání od %s?"</string>
+    <string name="resume_playing_resume">"Pokračovat v přehrávání"</string>
+    <string name="resume_playing_restart">"Začít znovu"</string>
+    <string name="gadget_title">"Rámeček fotografie"</string>
+    <string name="video_file_name_format">"\'video\'-yyyy-MM-dd-HH-mm-ss"</string>
+    <!-- no translation found for file_info_title (1628963357466012538) -->
+    <skip />
+    <!-- no translation found for video_exceed_mms_limit (3835075281230780010) -->
+    <skip />
+</resources>
diff --git a/res/values-de/arrays.xml b/res/values-de/arrays.xml
new file mode 100644
index 0000000..0583b87
--- /dev/null
+++ b/res/values-de/arrays.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for pref_camera_videoquality_entries:0 (4921712231611437934) -->
+    <!-- no translation found for pref_camera_videoquality_entries:1 (3206329580124804732) -->
+  <string-array name="pref_camera_videoquality_entryvalues">
+    <item>"0"</item>
+    <item>"1"</item>
+  </string-array>
+</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
new file mode 100644
index 0000000..c166287
--- /dev/null
+++ b/res/values-de/strings.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="all_images">"Alle Bilder"</string>
+    <string name="all_videos">"Alle Videos"</string>
+    <string name="camera_label">"Kamera"</string>
+    <string name="gallery_picker_label">"Galerie"</string>
+    <string name="gallery_camera_bucket_name">"Kamerabilder"</string>
+    <string name="gallery_camera_videos_bucket_name">"Kameravideos"</string>
+    <string name="switch_to_video_lable">"Zu Video wechseln"</string>
+    <string name="switch_to_camera_lable">"Zu Kamera wechseln"</string>
+    <string name="crop_label">"Bild zuschneiden"</string>
+    <string name="view_label">"Bild anzeigen"</string>
+    <string name="preferences_label">"Kameraeinstellungen"</string>
+    <string name="wait">"Bitte warten..."</string>
+    <string name="no_storage">"Legen Sie vor Verwendung der Kamera eine SD-Karte ein."</string>
+    <string name="not_enough_space">"Ihre SD-Karte ist voll."</string>
+    <string name="preparing_sd">"SD-Karte wird vorbereitet..."</string>
+    <string name="wallpaper">"Hintergrund wird eingestellt, bitte warten..."</string>
+    <string name="savingImage">"Bild wird gespeichert..."</string>
+    <string name="runningFaceDetection">"Bitte warten..."</string>
+    <string name="flip_orientation">"Ausrichtung drehen"</string>
+    <string name="settings">"Einstellungen"</string>
+    <string name="view">"Anzeigen"</string>
+    <string name="details">"Details"</string>
+    <string name="rotate">"Drehen"</string>
+    <string name="rotate_left">"Nach links drehen"</string>
+    <string name="rotate_right">"Nach rechts drehen"</string>
+    <string name="slide_show">"Diashow"</string>
+    <string name="capture_picture">"Bild aufnehmen"</string>
+    <string name="capture_video">"Video aufnehmen"</string>
+    <string name="crop_save_text">"Speichern"</string>
+    <string name="crop_discard_text">"Verwerfen"</string>
+    <string name="confirm_delete_title">"Löschen"</string>
+    <string name="confirm_delete_message">"Das Bild wird gelöscht."</string>
+    <string name="confirm_delete_video_message">"Das Video wird gelöscht."</string>
+    <string name="camera_toss">"Löschen"</string>
+    <string name="camera_gallery">"Galerie"</string>
+    <string name="camera_share">"Senden"</string>
+    <string name="camera_set">"Festlegen als"</string>
+    <string name="camera_play">"Wiedergeben"</string>
+    <string name="camera_attach">"Anhängen"</string>
+    <string name="camera_cancel">"Abbrechen"</string>
+    <string name="camera_crop">"Zuschneiden"</string>
+    <string name="camera_tossing">"Löschvorgang..."</string>
+    <string name="no_way_to_share_image">"Dieses Bild kann nicht weitergeleitet werden."</string>
+    <string name="no_way_to_share_video">"Dieses Video kann nicht weitergeleitet werden."</string>
+    <string name="camera_gallery_photos_text">"Galerie"</string>
+    <string name="camera_pick_wallpaper">"Bilder"</string>
+    <string name="camera_setas_wallpaper">"Hintergrund"</string>
+    <string name="pref_gallery_category">"Allgemeine Einstellungen"</string>
+    <string name="pref_slideshow_category">"Einstellungen für Diashow"</string>
+    <string name="pref_camera_general_settings_category">"Allgemeine Einstellungen"</string>
+    <string name="pref_gallery_size_title">"Anzeigegröße"</string>
+    <string name="pref_gallery_size_summary">"Anzeigegröße für Bilder und Videos auswählen"</string>
+    <string name="pref_gallery_size_dialogtitle">"Bildgröße"</string>
+  <string-array name="pref_gallery_size_choices">
+    <item>"Groß"</item>
+    <item>"Klein"</item>
+  </string-array>
+    <string name="pref_gallery_sort_title">"Sortierreihenfolge"</string>
+    <string name="pref_gallery_sort_summary">"Sortierreihenfolge für Bilder und Videos auswählen"</string>
+    <string name="pref_gallery_sort_dialogtitle">"Sortierreihenfolge für Bilder"</string>
+  <string-array name="pref_gallery_sort_choices">
+    <item>"Neue zuerst"</item>
+    <item>"Neue zuletzt"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_interval_title">"Diashow-Intervall"</string>
+    <string name="pref_gallery_slideshow_interval_summary">"Auswählen, wie lange jedes Bild in der Diashow angezeigt werden soll"</string>
+    <string name="pref_gallery_slideshow_interval_dialogtitle">"Diashow-Intervall"</string>
+  <string-array name="pref_gallery_slideshow_interval_choices">
+    <item>"2 Sekunden"</item>
+    <item>"3 Sekunden"</item>
+    <item>"4 Sekunden"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_transition_title">"Diashow - Übergang"</string>
+    <string name="pref_gallery_slideshow_transition_summary">"Effekt auswählen, der beim Wechsel zum nächsten Bild angewendet wird"</string>
+    <string name="pref_gallery_slideshow_transition_dialogtitle">"Diashow - Übergang"</string>
+  <string-array name="pref_gallery_slideshow_transition_choices">
+    <item>"Ein- und ausblenden"</item>
+    <item>"Einblendung von links - rechts"</item>
+    <item>"Einblendung von oben - unten"</item>
+    <item>"Zufällige Auswahl"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_repeat_title">"Diashow erneut anzeigen?"</string>
+    <string name="pref_gallery_slideshow_repeat_summary">"Diashow mehrmals anzeigen"</string>
+    <string name="pref_gallery_slideshow_shuffle_title">"Bilder mischen"</string>
+    <string name="pref_gallery_slideshow_shuffle_summary">"Bilder in zufälliger Reihenfolge anzeigen"</string>
+    <string name="pref_camera_recordlocation_title">"Aufnahmeort in Bildern speichern"</string>
+    <string name="pref_camera_recordlocation_summary">"Aufnahmeort in Bilddaten speichern"</string>
+    <string name="pref_camera_videoquality_category">"Videoqualität"</string>
+    <string name="pref_camera_videoquality_title">"Videoqualität auswählen"</string>
+    <string name="pref_camera_videoquality_entry_0">"Niedrig (für MMS-Mitteilungen)"</string>
+    <string name="pref_camera_videoquality_entry_1">"Hoch (für SD-Karte)"</string>
+    <string name="pref_camera_videoquality_dialogtitle">"Videoqualität"</string>
+    <string name="camerasettings">"Einstellungen"</string>
+    <string name="image_gallery_NoImageView_text">"Keine Medien gefunden."</string>
+    <string name="pref_gallery_confirm_delete_title">"Löschvorgang bestätigen"</string>
+    <string name="pref_gallery_confirm_delete_summary">"Bestätigung vor dem Löschen eines Bildes oder Videos anzeigen"</string>
+    <string name="details_panel_title">"Details"</string>
+    <string name="details_file_size">"Dateigröße:"</string>
+    <string name="details_image_resolution">"Auflösung:"</string>
+    <string name="details_duration">"Dauer:"</string>
+    <string name="details_date_taken">"Aufnahmedatum:"</string>
+    <string name="details_frame_rate">"Framerate:"</string>
+    <string name="details_bit_rate">"Bitrate:"</string>
+    <string name="details_codec">"Codec:"</string>
+    <string name="details_format">"Format:"</string>
+    <string name="details_dimension_x">"%1$d x %2$d"</string>
+    <string name="details_ms">"%1$02d:%2$02d"</string>
+    <string name="details_hms">"%1$d:%2$02d:%3$02d"</string>
+    <string name="details_fps">"%1$d fps"</string>
+    <string name="details_kbps">"%1$d Kbit/s"</string>
+    <string name="details_mbps">"%1$g Mbit/s"</string>
+    <string name="details_ok">"OK"</string>
+    <string name="context_menu_header">"Bildoptionen"</string>
+    <string name="video_context_menu_header">"Videooptionen"</string>
+    <string name="multiface_crop_help">"Tippen Sie zum Beginnen auf ein Gesicht."</string>
+    <string name="photos_gallery_title">"Galerie"</string>
+    <string name="pick_photos_gallery_title">"Bild auswählen"</string>
+    <string name="videos_gallery_title">"Galerie"</string>
+    <string name="pick_videos_gallery_title">"Video auswählen"</string>
+    <string name="loading_progress_format_string">"noch <xliff:g id="COUNTER">%d</xliff:g>"</string>
+    <string name="sendImage">"Bild senden via"</string>
+    <string name="setImage">"Bild festlegen als"</string>
+    <string name="sendVideo">"Video weiterleiten via"</string>
+    <string name="movieviewlabel">"Filme"</string>
+    <string name="loading_video">"Video wird geladen..."</string>
+    <string name="spaceIsLow_title">"Geringer Speicherplatz"</string>
+    <string name="spaceIsLow_content">"Auf Ihrer SD-Karte ist nicht mehr genügend Speicherplatz vorhanden. Ändern Sie die Qualitätseinstellung oder löschen Sie Elemente aus der Galerie."</string>
+    <string name="resume_playing_title">"Mit Video fortfahren"</string>
+    <string name="resume_playing_message">"Mit Wiedergabe fortfahren ab %s ?"</string>
+    <string name="resume_playing_resume">"Mit Wiedergabe fortfahren"</string>
+    <string name="resume_playing_restart">"Starten"</string>
+    <string name="gadget_title">"Bildrahmen"</string>
+    <string name="video_file_name_format">"\'video\'-yyyy-MM-dd-HH-mm-ss"</string>
+    <!-- no translation found for file_info_title (1628963357466012538) -->
+    <skip />
+    <!-- no translation found for video_exceed_mms_limit (3835075281230780010) -->
+    <skip />
+</resources>
diff --git a/res/values-es/arrays.xml b/res/values-es/arrays.xml
new file mode 100644
index 0000000..0583b87
--- /dev/null
+++ b/res/values-es/arrays.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for pref_camera_videoquality_entries:0 (4921712231611437934) -->
+    <!-- no translation found for pref_camera_videoquality_entries:1 (3206329580124804732) -->
+  <string-array name="pref_camera_videoquality_entryvalues">
+    <item>"0"</item>
+    <item>"1"</item>
+  </string-array>
+</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
new file mode 100644
index 0000000..5225151
--- /dev/null
+++ b/res/values-es/strings.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="all_images">"Todas las imágenes"</string>
+    <string name="all_videos">"Todos los vídeos"</string>
+    <string name="camera_label">"Cámara"</string>
+    <string name="gallery_picker_label">"Galería"</string>
+    <string name="gallery_camera_bucket_name">"Imágenes de cámara"</string>
+    <string name="gallery_camera_videos_bucket_name">"Vídeos de cámara"</string>
+    <string name="switch_to_video_lable">"Cambiar a vídeo"</string>
+    <string name="switch_to_camera_lable">"Cambiar a cámara"</string>
+    <string name="crop_label">"Recortar imagen"</string>
+    <string name="view_label">"Ver imagen"</string>
+    <string name="preferences_label">"Configuración de cámara"</string>
+    <string name="wait">"Por favor, espera..."</string>
+    <string name="no_storage">"Antes de utilizar la cámara, debes insertar una tarjeta SD."</string>
+    <string name="not_enough_space">"La tarjeta SD está llena."</string>
+    <string name="preparing_sd">"Preparando tarjeta SD…"</string>
+    <string name="wallpaper">"Estableciendo fondo de pantalla; por favor, espera..."</string>
+    <string name="savingImage">"Guardando imagen..."</string>
+    <string name="runningFaceDetection">"Por favor, espera..."</string>
+    <string name="flip_orientation">"Cambiar orientación"</string>
+    <string name="settings">"Ajustes"</string>
+    <string name="view">"Ver"</string>
+    <string name="details">"Detalles"</string>
+    <string name="rotate">"Girar"</string>
+    <string name="rotate_left">"Girar a la izquierda"</string>
+    <string name="rotate_right">"Girar a la derecha"</string>
+    <string name="slide_show">"Presentación"</string>
+    <string name="capture_picture">"Capturar imagen"</string>
+    <string name="capture_video">"Capturar vídeo"</string>
+    <string name="crop_save_text">"Guardar"</string>
+    <string name="crop_discard_text">"Descartar"</string>
+    <string name="confirm_delete_title">"Suprimir"</string>
+    <string name="confirm_delete_message">"Se eliminará la imagen."</string>
+    <string name="confirm_delete_video_message">"Se eliminará el vídeo."</string>
+    <string name="camera_toss">"Suprimir"</string>
+    <string name="camera_gallery">"Galería"</string>
+    <string name="camera_share">"Compartir"</string>
+    <string name="camera_set">"Establecer como"</string>
+    <string name="camera_play">"Reproducir"</string>
+    <string name="camera_attach">"Adjuntar"</string>
+    <string name="camera_cancel">"Cancelar"</string>
+    <string name="camera_crop">"Recortar"</string>
+    <string name="camera_tossing">"Eliminando..."</string>
+    <string name="no_way_to_share_image">"Esta imagen no se puede compartir."</string>
+    <string name="no_way_to_share_video">"Este vídeo no se puede compartir."</string>
+    <string name="camera_gallery_photos_text">"Galería"</string>
+    <string name="camera_pick_wallpaper">"Imágenes"</string>
+    <string name="camera_setas_wallpaper">"Fondo de pantalla"</string>
+    <string name="pref_gallery_category">"Configuración general"</string>
+    <string name="pref_slideshow_category">"Configuración de presentación"</string>
+    <string name="pref_camera_general_settings_category">"Configuración general"</string>
+    <string name="pref_gallery_size_title">"Tamaño de visualización"</string>
+    <string name="pref_gallery_size_summary">"Seleccionar el tamaño de visualización de imágenes y vídeos"</string>
+    <string name="pref_gallery_size_dialogtitle">"Tamaño de imagen"</string>
+  <string-array name="pref_gallery_size_choices">
+    <item>"Grande"</item>
+    <item>"Pequeña"</item>
+  </string-array>
+    <string name="pref_gallery_sort_title">"Orden"</string>
+    <string name="pref_gallery_sort_summary">"Seleccionar el orden de imágenes y vídeos"</string>
+    <string name="pref_gallery_sort_dialogtitle">"Ordenación de imágenes"</string>
+  <string-array name="pref_gallery_sort_choices">
+    <item>"Más recientes primero"</item>
+    <item>"Más recientes al final"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_interval_title">"Intervalo de presentación"</string>
+    <string name="pref_gallery_slideshow_interval_summary">"Seleccionar la duración de cada diapositiva"</string>
+    <string name="pref_gallery_slideshow_interval_dialogtitle">"Intervalo de presentación"</string>
+  <string-array name="pref_gallery_slideshow_interval_choices">
+    <item>"2 segundos"</item>
+    <item>"3 segundos"</item>
+    <item>"4 segundos"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_transition_title">"Transición de diapositivas"</string>
+    <string name="pref_gallery_slideshow_transition_summary">"Seleccionar el efecto que se aplicará al pasar de una diapositiva a otra"</string>
+    <string name="pref_gallery_slideshow_transition_dialogtitle">"Transición de diapositivas"</string>
+  <string-array name="pref_gallery_slideshow_transition_choices">
+    <item>"Fundido de entrada y salida"</item>
+    <item>"Transición de izquierda a derecha"</item>
+    <item>"Transición de arriba a abajo"</item>
+    <item>"Selección aleatoria"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_repeat_title">"Repetir presentación"</string>
+    <string name="pref_gallery_slideshow_repeat_summary">"Mostrar presentación más de una vez"</string>
+    <string name="pref_gallery_slideshow_shuffle_title">"Reproducir diapositivas en orden aleatorio"</string>
+    <string name="pref_gallery_slideshow_shuffle_summary">"Mostrar imágenes en orden aleatorio"</string>
+    <string name="pref_camera_recordlocation_title">"Almacenar ubicación en imágenes"</string>
+    <string name="pref_camera_recordlocation_summary">"Registrar ubicación en datos de imagen"</string>
+    <string name="pref_camera_videoquality_category">"Calidad de vídeo"</string>
+    <string name="pref_camera_videoquality_title">"Seleccionar calidad de vídeo"</string>
+    <string name="pref_camera_videoquality_entry_0">"Baja (para mensajes MMS)"</string>
+    <string name="pref_camera_videoquality_entry_1">"Alta (para tarjeta SD)"</string>
+    <string name="pref_camera_videoquality_dialogtitle">"Calidad de vídeo"</string>
+    <string name="camerasettings">"Ajustes"</string>
+    <string name="image_gallery_NoImageView_text">"No se ha encontrado ningún elemento."</string>
+    <string name="pref_gallery_confirm_delete_title">"Confirmar eliminaciones"</string>
+    <string name="pref_gallery_confirm_delete_summary">"Mostrar confirmación antes de eliminar una imagen o un vídeo"</string>
+    <string name="details_panel_title">"Detalles"</string>
+    <string name="details_file_size">"Tamaño de archivo:"</string>
+    <string name="details_image_resolution">"Resolución:"</string>
+    <string name="details_duration">"Duración:"</string>
+    <string name="details_date_taken">"Fecha de realización:"</string>
+    <string name="details_frame_rate">"Frecuencia de imagen:"</string>
+    <string name="details_bit_rate">"Velocidad binaria:"</string>
+    <string name="details_codec">"Códec:"</string>
+    <string name="details_format">"Formato:"</string>
+    <string name="details_dimension_x">"%1$d x %2$d"</string>
+    <string name="details_ms">"%1$02d:%2$02d"</string>
+    <string name="details_hms">"%1$d:%2$02d:%3$02d"</string>
+    <string name="details_fps">"%1$d fps"</string>
+    <string name="details_kbps">"%1$d Kbps"</string>
+    <string name="details_mbps">"%1$g Mbps"</string>
+    <string name="details_ok">"Aceptar"</string>
+    <string name="context_menu_header">"Opciones de imagen"</string>
+    <string name="video_context_menu_header">"Opciones de vídeo"</string>
+    <string name="multiface_crop_help">"Selecciona una cara para empezar."</string>
+    <string name="photos_gallery_title">"Galería"</string>
+    <string name="pick_photos_gallery_title">"Seleccionar imagen"</string>
+    <string name="videos_gallery_title">"Galería"</string>
+    <string name="pick_videos_gallery_title">"Seleccionar vídeo"</string>
+    <string name="loading_progress_format_string">"<xliff:g id="COUNTER">%d</xliff:g> restantes"</string>
+    <string name="sendImage">"Compartir imagen por"</string>
+    <string name="setImage">"Establecer imagen como"</string>
+    <string name="sendVideo">"Compartir vídeo a través de"</string>
+    <string name="movieviewlabel">"Películas"</string>
+    <string name="loading_video">"Cargando vídeo…"</string>
+    <string name="spaceIsLow_title">"Poco espacio"</string>
+    <string name="spaceIsLow_content">"No queda espacio en la tarjeta SD. Cambia la configuración de calidad o elimina elementos de la galería."</string>
+    <string name="resume_playing_title">"Reanudar vídeo"</string>
+    <string name="resume_playing_message">"Reanudar reproducción a partir de %s ?"</string>
+    <string name="resume_playing_resume">"Reanudar reproducción"</string>
+    <string name="resume_playing_restart">"Volver a reproducir"</string>
+    <string name="gadget_title">"Picture frame"</string>
+    <string name="video_file_name_format">"\'video\'-yyyy-MM-dd-HH-mm-ss"</string>
+    <!-- no translation found for file_info_title (1628963357466012538) -->
+    <skip />
+    <!-- no translation found for video_exceed_mms_limit (3835075281230780010) -->
+    <skip />
+</resources>
diff --git a/res/values-fr/arrays.xml b/res/values-fr/arrays.xml
new file mode 100644
index 0000000..0583b87
--- /dev/null
+++ b/res/values-fr/arrays.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for pref_camera_videoquality_entries:0 (4921712231611437934) -->
+    <!-- no translation found for pref_camera_videoquality_entries:1 (3206329580124804732) -->
+  <string-array name="pref_camera_videoquality_entryvalues">
+    <item>"0"</item>
+    <item>"1"</item>
+  </string-array>
+</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
new file mode 100644
index 0000000..f0e6a1f
--- /dev/null
+++ b/res/values-fr/strings.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="all_images">"Toutes les images"</string>
+    <string name="all_videos">"Toutes les vidéos"</string>
+    <string name="camera_label">"Appareil photo"</string>
+    <string name="gallery_picker_label">"Galerie"</string>
+    <string name="gallery_camera_bucket_name">"Photos de l\'appareil"</string>
+    <string name="gallery_camera_videos_bucket_name">"Vidéos de la caméra"</string>
+    <string name="switch_to_video_lable">"Passer en mode vidéo"</string>
+    <string name="switch_to_camera_lable">"Passer en mode appareil photo"</string>
+    <string name="crop_label">"Rogner l\'image"</string>
+    <string name="view_label">"Afficher l\'image"</string>
+    <string name="preferences_label">"Paramètres de l\'appareil photo"</string>
+    <string name="wait">"Veuillez patienter..."</string>
+    <string name="no_storage">"Veuillez insérer une carte SD avant d\'utiliser l\'appareil photo."</string>
+    <string name="not_enough_space">"Votre carte SD est pleine."</string>
+    <string name="preparing_sd">"Préparation de la carte SD..."</string>
+    <string name="wallpaper">"Configuration de l\'arrière-plan en cours. Veuillez patienter..."</string>
+    <string name="savingImage">"Enregistrement de l\'image"</string>
+    <string name="runningFaceDetection">"Veuillez patienter..."</string>
+    <string name="flip_orientation">"Orientation"</string>
+    <string name="settings">"Paramètres"</string>
+    <string name="view">"Afficher"</string>
+    <string name="details">"Détails"</string>
+    <string name="rotate">"Faire pivoter"</string>
+    <string name="rotate_left">"Faire pivoter à gauche"</string>
+    <string name="rotate_right">"Faire pivoter à droite"</string>
+    <string name="slide_show">"Diaporama"</string>
+    <string name="capture_picture">"Prendre une photo"</string>
+    <string name="capture_video">"Prendre une vidéo"</string>
+    <string name="crop_save_text">"Enregistrer"</string>
+    <string name="crop_discard_text">"Annuler"</string>
+    <string name="confirm_delete_title">"Supprimer"</string>
+    <string name="confirm_delete_message">"L\'image sera supprimée."</string>
+    <string name="confirm_delete_video_message">"La vidéo va être supprimée."</string>
+    <string name="camera_toss">"Supprimer"</string>
+    <string name="camera_gallery">"Galerie"</string>
+    <string name="camera_share">"Partager"</string>
+    <string name="camera_set">"Définir comme"</string>
+    <string name="camera_play">"Lire"</string>
+    <string name="camera_attach">"Joindre"</string>
+    <string name="camera_cancel">"Annuler"</string>
+    <string name="camera_crop">"Rogner"</string>
+    <string name="camera_tossing">"Suppression..."</string>
+    <string name="no_way_to_share_image">"Impossible de partager cette image."</string>
+    <string name="no_way_to_share_video">"Cette vidéo ne peut pas être partagée."</string>
+    <string name="camera_gallery_photos_text">"Galerie"</string>
+    <string name="camera_pick_wallpaper">"Images"</string>
+    <string name="camera_setas_wallpaper">"Arrière-plan"</string>
+    <string name="pref_gallery_category">"Paramètres généraux"</string>
+    <string name="pref_slideshow_category">"Paramètres du diaporama"</string>
+    <string name="pref_camera_general_settings_category">"Paramètres généraux"</string>
+    <string name="pref_gallery_size_title">"Taille d\'affichage"</string>
+    <string name="pref_gallery_size_summary">"Sélectionner la taille d\'affichage des images et des vidéos"</string>
+    <string name="pref_gallery_size_dialogtitle">"Taille de l\'image"</string>
+  <string-array name="pref_gallery_size_choices">
+    <item>"Grande"</item>
+    <item>"Petite"</item>
+  </string-array>
+    <string name="pref_gallery_sort_title">"Ordre de tri"</string>
+    <string name="pref_gallery_sort_summary">"Sélectionner l\'ordre de tri des images et des vidéos"</string>
+    <string name="pref_gallery_sort_dialogtitle">"Trier les images"</string>
+  <string-array name="pref_gallery_sort_choices">
+    <item>"La plus récente en premier"</item>
+    <item>"La plus récente en dernier"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_interval_title">"Intervalle du diaporama"</string>
+    <string name="pref_gallery_slideshow_interval_summary">"Sélectionner la durée d\'affichage de chaque diapositive dans le diaporama"</string>
+    <string name="pref_gallery_slideshow_interval_dialogtitle">"Intervalle du diaporama"</string>
+  <string-array name="pref_gallery_slideshow_interval_choices">
+    <item>"2 secondes"</item>
+    <item>"3 secondes"</item>
+    <item>"4 secondes"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_transition_title">"Transition du diaporama"</string>
+    <string name="pref_gallery_slideshow_transition_summary">"Sélectionner l\'effet utilisé lors de la transition d\'une diapositive à une autre"</string>
+    <string name="pref_gallery_slideshow_transition_dialogtitle">"Transition du diaporama"</string>
+  <string-array name="pref_gallery_slideshow_transition_choices">
+    <item>"Ouverture et fermeture en fondu"</item>
+    <item>"Glisser de gauche à droite"</item>
+    <item>"Glisser de haut en bas"</item>
+    <item>"Sélection aléatoire"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_repeat_title">"Rejouer le diaporama"</string>
+    <string name="pref_gallery_slideshow_repeat_summary">"Rejouer le diaporama plusieurs fois"</string>
+    <string name="pref_gallery_slideshow_shuffle_title">"Lecture aléatoire"</string>
+    <string name="pref_gallery_slideshow_shuffle_summary">"Afficher les images"</string>
+    <string name="pref_camera_recordlocation_title">"Enregistrer le lieu"</string>
+    <string name="pref_camera_recordlocation_summary">"Enregistrer la position géographique dans les données d\'image"</string>
+    <string name="pref_camera_videoquality_category">"Paramètres vidéo"</string>
+    <string name="pref_camera_videoquality_title">"Qualité vidéo"</string>
+    <string name="pref_camera_videoquality_entry_0">"Basse (pour les MMS)"</string>
+    <string name="pref_camera_videoquality_entry_1">"Haute (pour la carte SD)"</string>
+    <string name="pref_camera_videoquality_dialogtitle">"Qualité vidéo"</string>
+    <string name="camerasettings">"Paramètres"</string>
+    <string name="image_gallery_NoImageView_text">"Aucun fichier trouvé."</string>
+    <string name="pref_gallery_confirm_delete_title">"Confirmer suppressions"</string>
+    <string name="pref_gallery_confirm_delete_summary">"Afficher une confirmation avant de supprimer une image ou une vidéo"</string>
+    <string name="details_panel_title">"Détails"</string>
+    <string name="details_file_size">"Taille du fichier :"</string>
+    <string name="details_image_resolution">"Résolution :"</string>
+    <string name="details_duration">"Durée :"</string>
+    <string name="details_date_taken">"Date de création :"</string>
+    <string name="details_frame_rate">"Fréquence d\'images :"</string>
+    <string name="details_bit_rate">"Débit :"</string>
+    <string name="details_codec">"Codec :"</string>
+    <string name="details_format">"Format :"</string>
+    <string name="details_dimension_x">"%1$d x %2$d"</string>
+    <string name="details_ms">"%1$02d:%2$02d"</string>
+    <string name="details_hms">"%1$d:%2$02d:%3$02d"</string>
+    <string name="details_fps">"%1$d ips"</string>
+    <string name="details_kbps">"%1$d Kbits/s"</string>
+    <string name="details_mbps">"%1$g Mbits/s"</string>
+    <string name="details_ok">"OK"</string>
+    <string name="context_menu_header">"Options de l\'image"</string>
+    <string name="video_context_menu_header">"Options vidéo"</string>
+    <string name="multiface_crop_help">"Cliquez sur un visage pour commencer."</string>
+    <string name="photos_gallery_title">"Galerie"</string>
+    <string name="pick_photos_gallery_title">"Sélectionnez une image"</string>
+    <string name="videos_gallery_title">"Galerie"</string>
+    <string name="pick_videos_gallery_title">"Sélectionnez une vidéo"</string>
+    <string name="loading_progress_format_string">"<xliff:g id="COUNTER">%d</xliff:g> vignettes restantes"</string>
+    <string name="sendImage">"Partager l\'image via"</string>
+    <string name="setImage">"Définir l\'image comme"</string>
+    <string name="sendVideo">"Partager la vidéo via"</string>
+    <string name="movieviewlabel">"Films"</string>
+    <string name="loading_video">"Chargement de la vidéo..."</string>
+    <string name="spaceIsLow_title">"L\'espace restant est faible"</string>
+    <string name="spaceIsLow_content">"Votre carte SD est pleine. Modifiez le paramètre de qualité ou supprimez des éléments de la galerie."</string>
+    <string name="resume_playing_title">"Reprendre la vidéo"</string>
+    <string name="resume_playing_message">"Reprendre la lecture à partir de %s ?"</string>
+    <string name="resume_playing_resume">"Reprendre la lecture"</string>
+    <string name="resume_playing_restart">"Démarrer"</string>
+    <string name="gadget_title">"Cadre d\'image"</string>
+    <string name="video_file_name_format">"\'video\'-yyyy-MM-dd-HH-mm-ss"</string>
+    <!-- no translation found for file_info_title (1628963357466012538) -->
+    <skip />
+    <!-- no translation found for video_exceed_mms_limit (3835075281230780010) -->
+    <skip />
+</resources>
diff --git a/res/values-it/arrays.xml b/res/values-it/arrays.xml
new file mode 100644
index 0000000..0583b87
--- /dev/null
+++ b/res/values-it/arrays.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for pref_camera_videoquality_entries:0 (4921712231611437934) -->
+    <!-- no translation found for pref_camera_videoquality_entries:1 (3206329580124804732) -->
+  <string-array name="pref_camera_videoquality_entryvalues">
+    <item>"0"</item>
+    <item>"1"</item>
+  </string-array>
+</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
new file mode 100644
index 0000000..3db66bc
--- /dev/null
+++ b/res/values-it/strings.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="all_images">"Tutte le foto"</string>
+    <string name="all_videos">"Tutti i video"</string>
+    <string name="camera_label">"Fotocamera"</string>
+    <string name="gallery_picker_label">"Galleria"</string>
+    <string name="gallery_camera_bucket_name">"Foto"</string>
+    <string name="gallery_camera_videos_bucket_name">"Video fotocamera"</string>
+    <string name="switch_to_video_lable">"Passa a video"</string>
+    <string name="switch_to_camera_lable">"Passa a fotocamera"</string>
+    <string name="crop_label">"Ritaglia foto"</string>
+    <string name="view_label">"Visualizza foto"</string>
+    <string name="preferences_label">"Impostazioni fotocamera"</string>
+    <string name="wait">"Attendere..."</string>
+    <string name="no_storage">"Per usare la fotocamera devi inserire una scheda SD."</string>
+    <string name="not_enough_space">"La scheda SD è piena."</string>
+    <string name="preparing_sd">"Preparazione scheda SD…"</string>
+    <string name="wallpaper">"Impostazione sfondo, attendi..."</string>
+    <string name="savingImage">"Salvataggio foto..."</string>
+    <string name="runningFaceDetection">"Attendere..."</string>
+    <string name="flip_orientation">"Capovolgi orientamento"</string>
+    <string name="settings">"Impostazioni"</string>
+    <string name="view">"Visualizza"</string>
+    <string name="details">"Dettagli"</string>
+    <string name="rotate">"Ruota"</string>
+    <string name="rotate_left">"Ruota a sinistra"</string>
+    <string name="rotate_right">"Ruota a destra"</string>
+    <string name="slide_show">"Presentazione"</string>
+    <string name="capture_picture">"Scatta foto"</string>
+    <string name="capture_video">"Registra video"</string>
+    <string name="crop_save_text">"Salva"</string>
+    <string name="crop_discard_text">"Annulla"</string>
+    <string name="confirm_delete_title">"Elimina"</string>
+    <string name="confirm_delete_message">"La foto verrà eliminata."</string>
+    <string name="confirm_delete_video_message">"Il video verrà eliminato."</string>
+    <string name="camera_toss">"Elimina"</string>
+    <string name="camera_gallery">"Galleria"</string>
+    <string name="camera_share">"Condividi"</string>
+    <string name="camera_set">"Imposta come"</string>
+    <string name="camera_play">"Riproduci"</string>
+    <string name="camera_attach">"Allega"</string>
+    <string name="camera_cancel">"Annulla"</string>
+    <string name="camera_crop">"Ritaglia"</string>
+    <string name="camera_tossing">"Eliminazione..."</string>
+    <string name="no_way_to_share_image">"Impossibile condividere la foto."</string>
+    <string name="no_way_to_share_video">"Impossibile condividere il video."</string>
+    <string name="camera_gallery_photos_text">"Galleria"</string>
+    <string name="camera_pick_wallpaper">"Foto"</string>
+    <string name="camera_setas_wallpaper">"Sfondo"</string>
+    <string name="pref_gallery_category">"Impostazioni generali"</string>
+    <string name="pref_slideshow_category">"Impostazioni presentazione"</string>
+    <string name="pref_camera_general_settings_category">"Impostazioni generali"</string>
+    <string name="pref_gallery_size_title">"Dimensioni schermo"</string>
+    <string name="pref_gallery_size_summary">"Seleziona dimensioni di visualizz. di foto e video"</string>
+    <string name="pref_gallery_size_dialogtitle">"Dimensioni foto"</string>
+  <string-array name="pref_gallery_size_choices">
+    <item>"Grandi"</item>
+    <item>"Piccole"</item>
+  </string-array>
+    <string name="pref_gallery_sort_title">"Ordinamento"</string>
+    <string name="pref_gallery_sort_summary">"Seleziona l\'ordinamento di foto e video"</string>
+    <string name="pref_gallery_sort_dialogtitle">"Ordine foto"</string>
+  <string-array name="pref_gallery_sort_choices">
+    <item>"Da più recente"</item>
+    <item>"Da meno recente"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_interval_title">"Intervallo presentazione"</string>
+    <string name="pref_gallery_slideshow_interval_summary">"Seleziona la durata di ogni diapositiva"</string>
+    <string name="pref_gallery_slideshow_interval_dialogtitle">"Intervallo presentazione"</string>
+  <string-array name="pref_gallery_slideshow_interval_choices">
+    <item>"2 secondi"</item>
+    <item>"3 secondi"</item>
+    <item>"4 secondi"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_transition_title">"Transizione presentazione"</string>
+    <string name="pref_gallery_slideshow_transition_summary">"Seleziona l\'effetto da usare per passare da una diapositiva all\'altra"</string>
+    <string name="pref_gallery_slideshow_transition_dialogtitle">"Transizione diapositive"</string>
+  <string-array name="pref_gallery_slideshow_transition_choices">
+    <item>"Dissolvenza"</item>
+    <item>"Da sinistra a destra"</item>
+    <item>"Dall\'alto verso il basso"</item>
+    <item>"Selezione casuale"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_repeat_title">"Ripeti presentazione"</string>
+    <string name="pref_gallery_slideshow_repeat_summary">"Riproduci più volte la presentazione"</string>
+    <string name="pref_gallery_slideshow_shuffle_title">"Riproduzione casuale"</string>
+    <string name="pref_gallery_slideshow_shuffle_summary">"Mostra le foto in ordine casuale"</string>
+    <string name="pref_camera_recordlocation_title">"Salva località nelle foto"</string>
+    <string name="pref_camera_recordlocation_summary">"Registra località nei dati della foto"</string>
+    <string name="pref_camera_videoquality_category">"Qualità video"</string>
+    <string name="pref_camera_videoquality_title">"Seleziona la qualità video"</string>
+    <string name="pref_camera_videoquality_entry_0">"Bassa (per MMS)"</string>
+    <string name="pref_camera_videoquality_entry_1">"Alta (per scheda SD)"</string>
+    <string name="pref_camera_videoquality_dialogtitle">"Qualità video"</string>
+    <string name="camerasettings">"Impostazioni"</string>
+    <string name="image_gallery_NoImageView_text">"Nessun media trovato."</string>
+    <string name="pref_gallery_confirm_delete_title">"Conferma eliminazioni"</string>
+    <string name="pref_gallery_confirm_delete_summary">"Mostra conferma prima di eliminare una foto o un video"</string>
+    <string name="details_panel_title">"Dettagli"</string>
+    <string name="details_file_size">"Dim. file:"</string>
+    <string name="details_image_resolution">"Risoluzione:"</string>
+    <string name="details_duration">"Durata:"</string>
+    <string name="details_date_taken">"Data scatto:"</string>
+    <string name="details_frame_rate">"Frequenza fotogrammi:"</string>
+    <string name="details_bit_rate">"Velocità in bit:"</string>
+    <string name="details_codec">"Codec:"</string>
+    <string name="details_format">"Formato:"</string>
+    <string name="details_dimension_x">"%1$d x %2$d"</string>
+    <string name="details_ms">"%1$02d:%2$02d"</string>
+    <string name="details_hms">"%1$d:%2$02d:%3$02d"</string>
+    <string name="details_fps">"%1$d fps"</string>
+    <string name="details_kbps">"%1$d Kbps"</string>
+    <string name="details_mbps">"%1$g Mbps"</string>
+    <string name="details_ok">"OK"</string>
+    <string name="context_menu_header">"Opzioni foto"</string>
+    <string name="video_context_menu_header">"Opzioni video"</string>
+    <string name="multiface_crop_help">"Tocca un viso per iniziare."</string>
+    <string name="photos_gallery_title">"Galleria"</string>
+    <string name="pick_photos_gallery_title">"Seleziona foto"</string>
+    <string name="videos_gallery_title">"Galleria"</string>
+    <string name="pick_videos_gallery_title">"Seleziona video"</string>
+    <string name="loading_progress_format_string">"<xliff:g id="COUNTER">%d</xliff:g> rimanenti"</string>
+    <string name="sendImage">"Condividi foto via"</string>
+    <string name="setImage">"Imposta foto come"</string>
+    <string name="sendVideo">"Condividi video via"</string>
+    <string name="movieviewlabel">"Film"</string>
+    <string name="loading_video">"Caricamento video..."</string>
+    <string name="spaceIsLow_title">"Spazio in esaurimento"</string>
+    <string name="spaceIsLow_content">"Lo spazio della scheda SD si sta esaurendo. Cambia l\'impostazione di qualità o elimina elementi nella galleria."</string>
+    <string name="resume_playing_title">"Riprendi video"</string>
+    <string name="resume_playing_message">"Riprendi riproduzione da %s ?"</string>
+    <string name="resume_playing_resume">"Riprendi riproduzione"</string>
+    <string name="resume_playing_restart">"Ricomincia"</string>
+    <string name="gadget_title">"Cornice immagine"</string>
+    <string name="video_file_name_format">"\'video\'-yyyy-MM-dd-HH-mm-ss"</string>
+    <!-- no translation found for file_info_title (1628963357466012538) -->
+    <skip />
+    <!-- no translation found for video_exceed_mms_limit (3835075281230780010) -->
+    <skip />
+</resources>
diff --git a/res/values-ja/arrays.xml b/res/values-ja/arrays.xml
new file mode 100644
index 0000000..0583b87
--- /dev/null
+++ b/res/values-ja/arrays.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for pref_camera_videoquality_entries:0 (4921712231611437934) -->
+    <!-- no translation found for pref_camera_videoquality_entries:1 (3206329580124804732) -->
+  <string-array name="pref_camera_videoquality_entryvalues">
+    <item>"0"</item>
+    <item>"1"</item>
+  </string-array>
+</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
new file mode 100644
index 0000000..f5e5b1b
--- /dev/null
+++ b/res/values-ja/strings.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="all_images">"すべての写真"</string>
+    <string name="all_videos">"すべての動画"</string>
+    <string name="camera_label">"カメラ"</string>
+    <string name="gallery_picker_label">"ギャラリー"</string>
+    <string name="gallery_camera_bucket_name">"カメラ (写真)"</string>
+    <string name="gallery_camera_videos_bucket_name">"カメラ (動画)"</string>
+    <string name="switch_to_video_lable">"ムービーに切替"</string>
+    <string name="switch_to_camera_lable">"写真に切替"</string>
+    <string name="crop_label">"写真のトリミング"</string>
+    <string name="view_label">"写真を表示"</string>
+    <string name="preferences_label">"カメラ設定"</string>
+    <string name="wait">"お待ちください..."</string>
+    <string name="no_storage">"カメラを使用する前にSDカードを挿入してください。"</string>
+    <string name="not_enough_space">"SDカードがいっぱいです。"</string>
+    <string name="preparing_sd">"SDカードの準備中..."</string>
+    <string name="wallpaper">"壁紙を設定しています。しばらくお待ちください..."</string>
+    <string name="savingImage">"写真を保存中..."</string>
+    <string name="runningFaceDetection">"お待ちください..."</string>
+    <string name="flip_orientation">"画面の向きを変更"</string>
+    <string name="settings">"設定"</string>
+    <string name="view">"表示"</string>
+    <string name="details">"詳細"</string>
+    <string name="rotate">"回転"</string>
+    <string name="rotate_left">"左に回転"</string>
+    <string name="rotate_right">"右に回転"</string>
+    <string name="slide_show">"スライドショー"</string>
+    <string name="capture_picture">"写真撮影"</string>
+    <string name="capture_video">"ムービー撮影"</string>
+    <string name="crop_save_text">"保存"</string>
+    <string name="crop_discard_text">"破棄"</string>
+    <string name="confirm_delete_title">"削除"</string>
+    <string name="confirm_delete_message">"写真を削除します。"</string>
+    <string name="confirm_delete_video_message">"動画を削除します。"</string>
+    <string name="camera_toss">"削除"</string>
+    <string name="camera_gallery">"ギャラリー"</string>
+    <string name="camera_share">"共有"</string>
+    <string name="camera_set">"設定"</string>
+    <string name="camera_play">"再生"</string>
+    <string name="camera_attach">"添付"</string>
+    <string name="camera_cancel">"キャンセル"</string>
+    <string name="camera_crop">"トリミング"</string>
+    <string name="camera_tossing">"削除中..."</string>
+    <string name="no_way_to_share_image">"この写真は共有できません。"</string>
+    <string name="no_way_to_share_video">"この動画は共有できません。"</string>
+    <string name="camera_gallery_photos_text">"ギャラリー"</string>
+    <string name="camera_pick_wallpaper">"写真"</string>
+    <string name="camera_setas_wallpaper">"壁紙"</string>
+    <string name="pref_gallery_category">"全般設定"</string>
+    <string name="pref_slideshow_category">"スライドショー設定"</string>
+    <string name="pref_camera_general_settings_category">"全般設定"</string>
+    <string name="pref_gallery_size_title">"表示サイズ"</string>
+    <string name="pref_gallery_size_summary">"写真と動画の表示サイズを選択"</string>
+    <string name="pref_gallery_size_dialogtitle">"写真サイズ"</string>
+  <string-array name="pref_gallery_size_choices">
+    <item>"大"</item>
+    <item>"小"</item>
+  </string-array>
+    <string name="pref_gallery_sort_title">"並び替え順"</string>
+    <string name="pref_gallery_sort_summary">"写真と動画の並び替え順を選択"</string>
+    <string name="pref_gallery_sort_dialogtitle">"写真の並べ替え"</string>
+  <string-array name="pref_gallery_sort_choices">
+    <item>"日付の新しい順"</item>
+    <item>"日付の古い順"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_interval_title">"スライドショーの間隔"</string>
+    <string name="pref_gallery_slideshow_interval_summary">"各スライドの表示時間"</string>
+    <string name="pref_gallery_slideshow_interval_dialogtitle">"スライドショーの間隔"</string>
+  <string-array name="pref_gallery_slideshow_interval_choices">
+    <item>"2秒"</item>
+    <item>"3秒"</item>
+    <item>"4秒"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_transition_title">"スライドショーのトランジション"</string>
+    <string name="pref_gallery_slideshow_transition_summary">"スライドが切り替わるときの効果を選択"</string>
+    <string name="pref_gallery_slideshow_transition_dialogtitle">"スライドショーのトランジション"</string>
+  <string-array name="pref_gallery_slideshow_transition_choices">
+    <item>"フェードイン&amp;アウト"</item>
+    <item>"左右にスライド"</item>
+    <item>"上下にスライド"</item>
+    <item>"ランダムに選択"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_repeat_title">"スライドショーを繰り返す"</string>
+    <string name="pref_gallery_slideshow_repeat_summary">"スライドショーを繰り返す"</string>
+    <string name="pref_gallery_slideshow_shuffle_title">"スライドのシャッフル"</string>
+    <string name="pref_gallery_slideshow_shuffle_summary">"写真をランダムに表示"</string>
+    <string name="pref_camera_recordlocation_title">"位置情報を写真に記録する"</string>
+    <string name="pref_camera_recordlocation_summary">"写真データに位置情報を記録します"</string>
+    <string name="pref_camera_videoquality_category">"動画のクオリティ"</string>
+    <string name="pref_camera_videoquality_title">"動画の画質を選択"</string>
+    <string name="pref_camera_videoquality_entry_0">"低 (MMSメッセージ向け)"</string>
+    <string name="pref_camera_videoquality_entry_1">"高 (SDカード向け)"</string>
+    <string name="pref_camera_videoquality_dialogtitle">"動画のクオリティ"</string>
+    <string name="camerasettings">"設定"</string>
+    <string name="image_gallery_NoImageView_text">"メディアが見つかりません。"</string>
+    <string name="pref_gallery_confirm_delete_title">"削除を確認"</string>
+    <string name="pref_gallery_confirm_delete_summary">"写真や動画を削除する前に確認メッセージを表示"</string>
+    <string name="details_panel_title">"詳細"</string>
+    <string name="details_file_size">"ファイルサイズ:"</string>
+    <string name="details_image_resolution">"解像度:"</string>
+    <string name="details_duration">"再生時間:"</string>
+    <string name="details_date_taken">"撮影日:"</string>
+    <string name="details_frame_rate">"フレームレート:"</string>
+    <string name="details_bit_rate">"ビットレート:"</string>
+    <string name="details_codec">"コーデック:"</string>
+    <string name="details_format">"形式:"</string>
+    <string name="details_dimension_x">"%1$dx%2$d"</string>
+    <string name="details_ms">"%1$02d:%2$02d"</string>
+    <string name="details_hms">"%1$d:%2$02d:%3$02d"</string>
+    <string name="details_fps">"%1$dfps"</string>
+    <string name="details_kbps">"%1$dKbps"</string>
+    <string name="details_mbps">"%1$gMbps"</string>
+    <string name="details_ok">"OK"</string>
+    <string name="context_menu_header">"写真オプション"</string>
+    <string name="video_context_menu_header">"動画オプション"</string>
+    <string name="multiface_crop_help">"顔をタップして開始します。"</string>
+    <string name="photos_gallery_title">"ギャラリー"</string>
+    <string name="pick_photos_gallery_title">"写真を選択"</string>
+    <string name="videos_gallery_title">"ギャラリー"</string>
+    <string name="pick_videos_gallery_title">"動画を選択"</string>
+    <string name="loading_progress_format_string">"残り<xliff:g id="COUNTER">%d</xliff:g>件"</string>
+    <string name="sendImage">"写真を共有"</string>
+    <string name="setImage">"写真を設定"</string>
+    <string name="sendVideo">"動画を共有"</string>
+    <string name="movieviewlabel">"映画"</string>
+    <string name="loading_video">"動画を読み込み中..."</string>
+    <string name="spaceIsLow_title">"空き容量が残り少なくなっています"</string>
+    <string name="spaceIsLow_content">"SDカードの容量が足りません。クオリティ設定を変更するか、ギャラリーからアイテムを削除してください。"</string>
+    <string name="resume_playing_title">"動画の再開"</string>
+    <string name="resume_playing_message">"再生を%sから再開しますか?"</string>
+    <string name="resume_playing_resume">"再生を再開"</string>
+    <string name="resume_playing_restart">"最初から再生"</string>
+    <string name="gadget_title">"写真フレーム"</string>
+    <string name="video_file_name_format">"\'video\'-yyyy-MM-dd-HH-mm-ss"</string>
+    <!-- no translation found for file_info_title (1628963357466012538) -->
+    <skip />
+    <!-- no translation found for video_exceed_mms_limit (3835075281230780010) -->
+    <skip />
+</resources>
diff --git a/res/values-ko/arrays.xml b/res/values-ko/arrays.xml
new file mode 100644
index 0000000..0583b87
--- /dev/null
+++ b/res/values-ko/arrays.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for pref_camera_videoquality_entries:0 (4921712231611437934) -->
+    <!-- no translation found for pref_camera_videoquality_entries:1 (3206329580124804732) -->
+  <string-array name="pref_camera_videoquality_entryvalues">
+    <item>"0"</item>
+    <item>"1"</item>
+  </string-array>
+</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
new file mode 100644
index 0000000..304b544
--- /dev/null
+++ b/res/values-ko/strings.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="all_images">"모든 사진"</string>
+    <string name="all_videos">"모든 동영상"</string>
+    <string name="camera_label">"카메라"</string>
+    <string name="gallery_picker_label">"갤러리"</string>
+    <string name="gallery_camera_bucket_name">"카메라 사진"</string>
+    <string name="gallery_camera_videos_bucket_name">"카메라 동영상"</string>
+    <string name="switch_to_video_lable">"동영상으로 전환"</string>
+    <string name="switch_to_camera_lable">"카메라로 전환"</string>
+    <string name="crop_label">"사진 자르기"</string>
+    <string name="view_label">"사진 보기"</string>
+    <string name="preferences_label">"카메라 설정"</string>
+    <string name="wait">"잠시 기다려 주세요..."</string>
+    <string name="no_storage">"카메라를 사용하기 전에 먼저 SD 카드를 넣으세요."</string>
+    <string name="not_enough_space">"SD 카드가 꽉 찼습니다."</string>
+    <string name="preparing_sd">"SD 카드 준비중..."</string>
+    <string name="wallpaper">"배경화면 설정 중. 잠시 기다려 주세요..."</string>
+    <string name="savingImage">"사진 저장 중..."</string>
+    <string name="runningFaceDetection">"잠시 기다려 주세요..."</string>
+    <string name="flip_orientation">"방향 바꾸기"</string>
+    <string name="settings">"설정"</string>
+    <string name="view">"보기"</string>
+    <string name="details">"상세정보"</string>
+    <string name="rotate">"회전"</string>
+    <string name="rotate_left">"왼쪽으로 회전"</string>
+    <string name="rotate_right">"오른쪽으로 회전"</string>
+    <string name="slide_show">"슬라이드쇼"</string>
+    <string name="capture_picture">"사진촬영"</string>
+    <string name="capture_video">"동영상 캡처"</string>
+    <string name="crop_save_text">"저장"</string>
+    <string name="crop_discard_text">"무시"</string>
+    <string name="confirm_delete_title">"삭제"</string>
+    <string name="confirm_delete_message">"사진이 삭제됩니다."</string>
+    <string name="confirm_delete_video_message">"동영상이 삭제됩니다."</string>
+    <string name="camera_toss">"삭제"</string>
+    <string name="camera_gallery">"갤러리"</string>
+    <string name="camera_share">"공유"</string>
+    <string name="camera_set">"사진 설정"</string>
+    <string name="camera_play">"재생"</string>
+    <string name="camera_attach">"첨부"</string>
+    <string name="camera_cancel">"취소"</string>
+    <string name="camera_crop">"자르기"</string>
+    <string name="camera_tossing">"삭제 중..."</string>
+    <string name="no_way_to_share_image">"공유할 수 없는 사진입니다."</string>
+    <string name="no_way_to_share_video">"공유할 수 없는 동영상입니다."</string>
+    <string name="camera_gallery_photos_text">"갤러리"</string>
+    <string name="camera_pick_wallpaper">"사진"</string>
+    <string name="camera_setas_wallpaper">"배경화면"</string>
+    <string name="pref_gallery_category">"일반 설정"</string>
+    <string name="pref_slideshow_category">"슬라이드쇼 설정"</string>
+    <string name="pref_camera_general_settings_category">"일반 설정"</string>
+    <string name="pref_gallery_size_title">"디스플레이 크기"</string>
+    <string name="pref_gallery_size_summary">"사진 및 동영상 표시 크기 선택"</string>
+    <string name="pref_gallery_size_dialogtitle">"사진 크기"</string>
+  <string-array name="pref_gallery_size_choices">
+    <item>"크게"</item>
+    <item>"작게"</item>
+  </string-array>
+    <string name="pref_gallery_sort_title">"정렬 순서"</string>
+    <string name="pref_gallery_sort_summary">"사진 및 동영상 정렬 순서 선택"</string>
+    <string name="pref_gallery_sort_dialogtitle">"사진 정렬"</string>
+  <string-array name="pref_gallery_sort_choices">
+    <item>"가장 최근 사진 먼저"</item>
+    <item>"가장 최근 사진 마지막에"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_interval_title">"슬라이드쇼 간격"</string>
+    <string name="pref_gallery_slideshow_interval_summary">"쇼에서 각 슬라이드를 표시할 시간 선택"</string>
+    <string name="pref_gallery_slideshow_interval_dialogtitle">"슬라이드쇼 간격"</string>
+  <string-array name="pref_gallery_slideshow_interval_choices">
+    <item>"2초"</item>
+    <item>"3초"</item>
+    <item>"4초"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_transition_title">"슬라이드쇼 화면전환"</string>
+    <string name="pref_gallery_slideshow_transition_summary">"슬라이드 간에 이동할 때 사용할 효과 선택"</string>
+    <string name="pref_gallery_slideshow_transition_dialogtitle">"슬라이드쇼 화면전환"</string>
+  <string-array name="pref_gallery_slideshow_transition_choices">
+    <item>"페이드 인/아웃"</item>
+    <item>"왼쪽에서 오른쪽으로/오른쪽에서 왼쪽으로"</item>
+    <item>"위에서 아래로/아래에서 위로"</item>
+    <item>"무작위 선택"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_repeat_title">"슬라이드쇼 반복"</string>
+    <string name="pref_gallery_slideshow_repeat_summary">"슬라이드쇼를 한 번 이상 재생"</string>
+    <string name="pref_gallery_slideshow_shuffle_title">"슬라이드 섞기"</string>
+    <string name="pref_gallery_slideshow_shuffle_summary">"사진을 무작위 순서로 표시"</string>
+    <string name="pref_camera_recordlocation_title">"사진에 위치 저장"</string>
+    <string name="pref_camera_recordlocation_summary">"사진 데이터에 위치 기록"</string>
+    <string name="pref_camera_videoquality_category">"동영상 화질"</string>
+    <string name="pref_camera_videoquality_title">"동영상 화질 선택"</string>
+    <string name="pref_camera_videoquality_entry_0">"낮음(MMS 메시지용)"</string>
+    <string name="pref_camera_videoquality_entry_1">"높음(SD 카드용)"</string>
+    <string name="pref_camera_videoquality_dialogtitle">"동영상 화질"</string>
+    <string name="camerasettings">"설정"</string>
+    <string name="image_gallery_NoImageView_text">"미디어 파일이 없습니다."</string>
+    <string name="pref_gallery_confirm_delete_title">"삭제 확인"</string>
+    <string name="pref_gallery_confirm_delete_summary">"사진 및 동영상 삭제 전 확인 표시"</string>
+    <string name="details_panel_title">"상세정보"</string>
+    <string name="details_file_size">"파일 크기:"</string>
+    <string name="details_image_resolution">"해상도:"</string>
+    <string name="details_duration">"재생 시간:"</string>
+    <string name="details_date_taken">"찍은 날짜:"</string>
+    <string name="details_frame_rate">"프레임 속도:"</string>
+    <string name="details_bit_rate">"비트 전송률:"</string>
+    <string name="details_codec">"코덱:"</string>
+    <string name="details_format">"형식:"</string>
+    <string name="details_dimension_x">"%1$d x %2$d"</string>
+    <string name="details_ms">"%1$02d:%2$02d"</string>
+    <string name="details_hms">"%1$d:%2$02d:%3$02d"</string>
+    <string name="details_fps">"%1$dfps"</string>
+    <string name="details_kbps">"%1$dKbps"</string>
+    <string name="details_mbps">"%1$gMbps"</string>
+    <string name="details_ok">"확인"</string>
+    <string name="context_menu_header">"사진 옵션"</string>
+    <string name="video_context_menu_header">"동영상 옵션"</string>
+    <string name="multiface_crop_help">"시작하려면 얼굴을 탭하세요."</string>
+    <string name="photos_gallery_title">"갤러리"</string>
+    <string name="pick_photos_gallery_title">"사진 선택"</string>
+    <string name="videos_gallery_title">"갤러리"</string>
+    <string name="pick_videos_gallery_title">"동영상 선택"</string>
+    <string name="loading_progress_format_string">"<xliff:g id="COUNTER">%d</xliff:g>개 남음"</string>
+    <string name="sendImage">"사진 공유 응용프로그램"</string>
+    <string name="setImage">"사진 설정"</string>
+    <string name="sendVideo">"동영상 공유 응용프로그램"</string>
+    <string name="movieviewlabel">"영화"</string>
+    <string name="loading_video">"동영상 로드 중..."</string>
+    <string name="spaceIsLow_title">"공간부족"</string>
+    <string name="spaceIsLow_content">"SD 카드의 공간이 부족합니다. 화질 설정을 변경하거나 갤러리에서 항목을 삭제하세요."</string>
+    <string name="resume_playing_title">"동영상 다시 시작"</string>
+    <string name="resume_playing_message">"%s에서 재생을 다시 시작하시겠습니까?"</string>
+    <string name="resume_playing_resume">"재생 다시 시작"</string>
+    <string name="resume_playing_restart">"시작"</string>
+    <string name="gadget_title">"사진 프레임"</string>
+    <string name="video_file_name_format">"\'video\'-yyyy-MM-dd-HH-mm-ss"</string>
+    <!-- no translation found for file_info_title (1628963357466012538) -->
+    <skip />
+    <!-- no translation found for video_exceed_mms_limit (3835075281230780010) -->
+    <skip />
+</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
new file mode 100644
index 0000000..c268ecb
--- /dev/null
+++ b/res/values-nb/strings.xml
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="all_images">"Alle bilder"</string>
+    <string name="all_videos">"Alle videoer"</string>
+    <string name="camera_label">"Kamera"</string>
+    <string name="gallery_picker_label">"Galleri"</string>
+    <string name="gallery_camera_bucket_name">"Bilder fra kamera"</string>
+    <string name="gallery_camera_videos_bucket_name">"Videoer fra kamera"</string>
+    <string name="switch_to_video_lable">"Bytt til video"</string>
+    <string name="switch_to_camera_lable">"Bytt til kamera"</string>
+    <string name="crop_label">"Beskjær bilde"</string>
+    <string name="view_label">"Se på bilde"</string>
+    <string name="preferences_label">"Kamerainnstillinger"</string>
+    <string name="wait">"Vent litt…"</string>
+    <string name="no_storage">"Sett inn et minnekort før du bruker kameraet."</string>
+    <string name="not_enough_space">"Minnekortet er fullt."</string>
+    <string name="preparing_sd">"Forbereder minnekort…"</string>
+    <string name="wallpaper">"Setter bakgrunnsbilde, vent litt…"</string>
+    <string name="savingImage">"Lagrer bilde…"</string>
+    <string name="runningFaceDetection">"Vent litt…"</string>
+    <string name="flip_orientation">"Roter skjerm"</string>
+    <string name="settings">"Innstillinger"</string>
+    <string name="view">"Vis"</string>
+    <string name="details">"Detaljer"</string>
+    <string name="rotate">"Roter"</string>
+    <string name="rotate_left">"Roter mot venstre"</string>
+    <string name="rotate_right">"Roter mot høyre"</string>
+    <string name="slide_show">"Lysbildevisning"</string>
+    <string name="capture_picture">"Ta bilde"</string>
+    <string name="capture_video">"Ta opp video"</string>
+    <string name="crop_save_text">"Lagre"</string>
+    <string name="crop_discard_text">"Forkast"</string>
+    <string name="confirm_delete_title">"Slett"</string>
+    <string name="confirm_delete_message">"Bildet vil bli slettet."</string>
+    <string name="confirm_delete_video_message">"Videoen vil bli slettet."</string>
+    <string name="camera_toss">"Slett"</string>
+    <string name="camera_gallery">"Galleri"</string>
+    <string name="camera_share">"Del"</string>
+    <string name="camera_set">"Bruk som"</string>
+    <string name="camera_play">"Spill"</string>
+    <string name="camera_attach">"Legg ved"</string>
+    <string name="camera_cancel">"Avbryt"</string>
+    <string name="camera_crop">"Beskjær"</string>
+    <string name="camera_tossing">"Sletter…"</string>
+    <string name="no_way_to_share_image">"Bildet kan ikke deles."</string>
+    <string name="no_way_to_share_video">"Videoen kan ikke deles."</string>
+    <string name="camera_gallery_photos_text">"Galleri"</string>
+    <string name="camera_pick_wallpaper">"Bilder"</string>
+    <string name="camera_setas_wallpaper">"Bakgrunnsbilder"</string>
+    <string name="pref_gallery_category">"Generelle innstillinger"</string>
+    <string name="pref_slideshow_category">"Innstillinger for lysbildevisning"</string>
+    <string name="pref_camera_general_settings_category">"Generelle innstillinger"</string>
+    <string name="pref_gallery_size_title">"Bildestørrelse"</string>
+    <string name="pref_gallery_size_summary">"Velg hvor stort bilder skal vises"</string>
+    <string name="pref_gallery_size_dialogtitle">"Bildestørrelse"</string>
+  <string-array name="pref_gallery_size_choices">
+    <item>"Stor"</item>
+    <item>"Liten"</item>
+  </string-array>
+    <string name="pref_gallery_sort_title">"Bildesortering"</string>
+    <string name="pref_gallery_sort_summary">"Velg i hvilken rekkefølge bildene skal sorteres"</string>
+    <string name="pref_gallery_sort_dialogtitle">"Bildesortering"</string>
+  <string-array name="pref_gallery_sort_choices">
+    <item>"Nyeste først"</item>
+    <item>"Nyeste sist"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_interval_title">"Pause i lysbildevisning"</string>
+    <string name="pref_gallery_slideshow_interval_summary">"Velg hvor lenge hvert lysbilde skal vises"</string>
+    <string name="pref_gallery_slideshow_interval_dialogtitle">"Pause i lysbildevisning"</string>
+  <string-array name="pref_gallery_slideshow_interval_choices">
+    <item>"2 sekunder"</item>
+    <item>"3 sekunder"</item>
+    <item>"4 sekunder"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_transition_title">"Lysbildeovergang"</string>
+    <string name="pref_gallery_slideshow_transition_summary">"Velg effekten som skal brukes mellom lysbilder"</string>
+    <string name="pref_gallery_slideshow_transition_dialogtitle">"Lysbildevergang"</string>
+  <string-array name="pref_gallery_slideshow_transition_choices">
+    <item>"Ton inn og ut"</item>
+    <item>"Skli venstre - høyre"</item>
+    <item>"Skli opp - ned"</item>
+    <item>"Tilfeldig"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_repeat_title">"Gjenta lysbildeserie"</string>
+    <string name="pref_gallery_slideshow_repeat_summary">"Vis lydbildeserie mer enn en gang"</string>
+    <string name="pref_gallery_slideshow_shuffle_title">"Stokk lysbilder"</string>
+    <string name="pref_gallery_slideshow_shuffle_summary">"Vis bilder i tilfeldig rekkefølge"</string>
+    <string name="pref_camera_recordlocation_title">"Lagre sted i bilder"</string>
+    <string name="pref_camera_recordlocation_summary">"Lagre sted i bildedata"</string>
+    <string name="pref_camera_videoquality_category">"Videokvalitet"</string>
+    <string name="pref_camera_videoquality_title">"Videokvalitet"</string>
+    <string name="pref_camera_videoquality_entry_0">"Lav (for MMS-meldinger)"</string>
+    <string name="pref_camera_videoquality_entry_1">"Høy (for minnekort)"</string>
+    <string name="pref_camera_videoquality_dialogtitle">"Videokvalitet"</string>
+    <string name="camerasettings">"Innstillinger"</string>
+    <string name="image_gallery_NoImageView_text">"Fant ingen bilder."</string>
+    <string name="pref_gallery_confirm_delete_title">"Bekreft sletting"</string>
+    <string name="pref_gallery_confirm_delete_summary">"Vis bekreftelse før bilder slettes"</string>
+    <string name="details_panel_title">"Detaljer"</string>
+    <string name="details_file_size">"Filstørrelse:"</string>
+    <string name="details_image_resolution">"Oppløsning:"</string>
+    <string name="details_duration">"Varighet:"</string>
+    <string name="details_date_taken">"Dato tatt:"</string>
+    <string name="details_frame_rate">"Bildefrekvens:"</string>
+    <string name="details_bit_rate">"Bitrate:"</string>
+    <string name="details_codec">"Kodek:"</string>
+    <string name="details_format">"Format:"</string>
+    <string name="details_dimension_x">"%1$d x %2$d"</string>
+    <string name="details_ms">"%1$02d:%2$02d"</string>
+    <string name="details_hms">"%1$d:%2$02d:%3$02d"</string>
+    <string name="details_fps">"%1$d fps"</string>
+    <string name="details_kbps">"%1$d kbps"</string>
+    <string name="details_mbps">"%1$g Mbps"</string>
+    <string name="details_ok">"OK"</string>
+    <string name="context_menu_header">"Bildeinnstillinger"</string>
+    <string name="video_context_menu_header">"Videoinnstillinger"</string>
+    <string name="multiface_crop_help">"Trykk på et ansikt for å begynne."</string>
+    <string name="photos_gallery_title">"Galleri"</string>
+    <string name="pick_photos_gallery_title">"Velg bilde"</string>
+    <string name="videos_gallery_title">"Galleri"</string>
+    <string name="pick_videos_gallery_title">"Velg video"</string>
+    <string name="loading_progress_format_string">"<xliff:g id="COUNTER">%d</xliff:g> gjenstår"</string>
+    <string name="sendImage">"Del bilde via"</string>
+    <string name="setImage">"Bruk bilde som"</string>
+    <string name="sendVideo">"Del video via"</string>
+    <string name="movieviewlabel">"Filmer"</string>
+    <string name="loading_video">"Laster video…"</string>
+    <string name="spaceIsLow_title">"Lite plass"</string>
+    <string name="spaceIsLow_content">"Minnekortet begynner å gå tom for plass. Endre kvalitetsinnstillingen eller slett bilder fra galleriet."</string>
+    <string name="resume_playing_title">"Fortsett avspilling"</string>
+    <string name="resume_playing_message">"Fortsett avspilling fra %s?"</string>
+    <string name="resume_playing_resume">"Fortsett avspilling"</string>
+    <string name="resume_playing_restart">"Begynn på nytt"</string>
+    <string name="gadget_title">"Bilderamme"</string>
+    <!-- no translation found for video_file_name_format (8555507706353616970) -->
+    <skip />
+    <!-- no translation found for file_info_title (1628963357466012538) -->
+    <skip />
+    <!-- no translation found for video_exceed_mms_limit (3835075281230780010) -->
+    <skip />
+</resources>
diff --git a/res/values-nl/arrays.xml b/res/values-nl/arrays.xml
new file mode 100644
index 0000000..0583b87
--- /dev/null
+++ b/res/values-nl/arrays.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for pref_camera_videoquality_entries:0 (4921712231611437934) -->
+    <!-- no translation found for pref_camera_videoquality_entries:1 (3206329580124804732) -->
+  <string-array name="pref_camera_videoquality_entryvalues">
+    <item>"0"</item>
+    <item>"1"</item>
+  </string-array>
+</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
new file mode 100644
index 0000000..c02a861
--- /dev/null
+++ b/res/values-nl/strings.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="all_images">"Alle foto\'s"</string>
+    <string name="all_videos">"Alle video\'s"</string>
+    <string name="camera_label">"Camera"</string>
+    <string name="gallery_picker_label">"Galerij"</string>
+    <string name="gallery_camera_bucket_name">"Camerafoto\'s"</string>
+    <string name="gallery_camera_videos_bucket_name">"Cameravideo\'s"</string>
+    <string name="switch_to_video_lable">"Overschakelen naar video"</string>
+    <string name="switch_to_camera_lable">"Overschakelen naar camera"</string>
+    <string name="crop_label">"Foto bijsnijden"</string>
+    <string name="view_label">"Foto weergeven"</string>
+    <string name="preferences_label">"Camera-instellingen"</string>
+    <string name="wait">"Een ogenblik geduld..."</string>
+    <string name="no_storage">"Plaats een SD-kaart voordat u de camera gebruikt."</string>
+    <string name="not_enough_space">"De SD-kaart is vol."</string>
+    <string name="preparing_sd">"SD-kaart voorbereiden…"</string>
+    <string name="wallpaper">"Achtergrond wordt ingesteld. Een ogenblik geduld..."</string>
+    <string name="savingImage">"Foto opslaan..."</string>
+    <string name="runningFaceDetection">"Een ogenblik geduld..."</string>
+    <string name="flip_orientation">"Stand draaien"</string>
+    <string name="settings">"Instellingen"</string>
+    <string name="view">"Weergeven"</string>
+    <string name="details">"Details"</string>
+    <string name="rotate">"Draaien"</string>
+    <string name="rotate_left">"Linksom draaien"</string>
+    <string name="rotate_right">"Rechtsom draaien"</string>
+    <string name="slide_show">"Diavoorstelling"</string>
+    <string name="capture_picture">"Foto nemen"</string>
+    <string name="capture_video">"Video opnemen"</string>
+    <string name="crop_save_text">"Opslaan"</string>
+    <string name="crop_discard_text">"Ongedaan maken"</string>
+    <string name="confirm_delete_title">"Verwijderen"</string>
+    <string name="confirm_delete_message">"De foto wordt verwijderd."</string>
+    <string name="confirm_delete_video_message">"De video wordt verwijderd"</string>
+    <string name="camera_toss">"Verwijderen"</string>
+    <string name="camera_gallery">"Galerij"</string>
+    <string name="camera_share">"Delen"</string>
+    <string name="camera_set">"Instellen als"</string>
+    <string name="camera_play">"Afspelen"</string>
+    <string name="camera_attach">"Bijvoegen"</string>
+    <string name="camera_cancel">"Annuleren"</string>
+    <string name="camera_crop">"Bijsnijden"</string>
+    <string name="camera_tossing">"Verwijderen..."</string>
+    <string name="no_way_to_share_image">"Deze foto kan niet worden gedeeld."</string>
+    <string name="no_way_to_share_video">"Deze video kan niet worden gedeeld."</string>
+    <string name="camera_gallery_photos_text">"Galerij"</string>
+    <string name="camera_pick_wallpaper">"Foto\'s"</string>
+    <string name="camera_setas_wallpaper">"Achtergrond"</string>
+    <string name="pref_gallery_category">"Algemene instellingen"</string>
+    <string name="pref_slideshow_category">"Instellingen van diavoorstelling"</string>
+    <string name="pref_camera_general_settings_category">"Algemene instellingen"</string>
+    <string name="pref_gallery_size_title">"Weergavegrootte"</string>
+    <string name="pref_gallery_size_summary">"De weergavegrootte van foto\'s en video\'s selecteren"</string>
+    <string name="pref_gallery_size_dialogtitle">"Grootte van foto"</string>
+  <string-array name="pref_gallery_size_choices">
+    <item>"Groot"</item>
+    <item>"Klein"</item>
+  </string-array>
+    <string name="pref_gallery_sort_title">"Sorteervolgorde"</string>
+    <string name="pref_gallery_sort_summary">"De sorteervolgorde van foto\'s en video\'s selecteren"</string>
+    <string name="pref_gallery_sort_dialogtitle">"Foto\'s sorteren"</string>
+  <string-array name="pref_gallery_sort_choices">
+    <item>"Nieuwste eerst"</item>
+    <item>"Nieuwste eerst"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_interval_title">"Interval in diavoorstelling"</string>
+    <string name="pref_gallery_slideshow_interval_summary">"Selecteren hoe lang elke dia wordt weergegeven in de voorstelling"</string>
+    <string name="pref_gallery_slideshow_interval_dialogtitle">"Interval in diavoorstelling"</string>
+  <string-array name="pref_gallery_slideshow_interval_choices">
+    <item>"2 seconden"</item>
+    <item>"3 seconden"</item>
+    <item>"4 seconden"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_transition_title">"Overgangen in diavoorstellingen"</string>
+    <string name="pref_gallery_slideshow_transition_summary">"Het effect selecteren dat wordt gebruikt om naar de volgende dia te gaan"</string>
+    <string name="pref_gallery_slideshow_transition_dialogtitle">"Overgangen in diavoorstellingen"</string>
+  <string-array name="pref_gallery_slideshow_transition_choices">
+    <item>"Infaden/uitfaden"</item>
+    <item>"Dia vanaf linkerkant/rechterkant"</item>
+    <item>"Dia vanaf bovenkant/onderkant"</item>
+    <item>"Willekeurige selectie"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_repeat_title">"Diavoorstelling herhalen"</string>
+    <string name="pref_gallery_slideshow_repeat_summary">"Diavoorstelling meerdere keren afspelen"</string>
+    <string name="pref_gallery_slideshow_shuffle_title">"Dia\'s in willekeurige volgorde weergeven"</string>
+    <string name="pref_gallery_slideshow_shuffle_summary">"Foto\'s in willekeurige volgorde weergeven"</string>
+    <string name="pref_camera_recordlocation_title">"Locatie opslaan in foto\'s"</string>
+    <string name="pref_camera_recordlocation_summary">"Locatie vastleggen in de gegevens van de foto"</string>
+    <string name="pref_camera_videoquality_category">"Videokwaliteit"</string>
+    <string name="pref_camera_videoquality_title">"Videokwaliteit selecteren"</string>
+    <string name="pref_camera_videoquality_entry_0">"Laag (voor MMS-berichten)"</string>
+    <string name="pref_camera_videoquality_entry_1">"Hoog (voor SD-kaart)"</string>
+    <string name="pref_camera_videoquality_dialogtitle">"Videokwaliteit"</string>
+    <string name="camerasettings">"Instellingen"</string>
+    <string name="image_gallery_NoImageView_text">"Geen medium gevonden."</string>
+    <string name="pref_gallery_confirm_delete_title">"Verwijderen bevestigen"</string>
+    <string name="pref_gallery_confirm_delete_summary">"Bevestiging weergeven voordat een foto of video wordt verwijderd"</string>
+    <string name="details_panel_title">"Details"</string>
+    <string name="details_file_size">"Bestandsgrootte:"</string>
+    <string name="details_image_resolution">"Resolutie:"</string>
+    <string name="details_duration">"Duur:"</string>
+    <string name="details_date_taken">"Gemaakt op:"</string>
+    <string name="details_frame_rate">"Framesnelheid:"</string>
+    <string name="details_bit_rate">"Bitsnelheid:"</string>
+    <string name="details_codec">"Codec:"</string>
+    <string name="details_format">"Indeling:"</string>
+    <string name="details_dimension_x">"%1$d x %2$d"</string>
+    <string name="details_ms">"%1$02d:%2$02d"</string>
+    <string name="details_hms">"%1$d:%2$02d:%3$02d"</string>
+    <string name="details_fps">"%1$d fps"</string>
+    <string name="details_kbps">"%1$d Kbps"</string>
+    <string name="details_mbps">"%1$g Mbps"</string>
+    <string name="details_ok">"OK"</string>
+    <string name="context_menu_header">"Opties voor foto\'s"</string>
+    <string name="video_context_menu_header">"Video-opties"</string>
+    <string name="multiface_crop_help">"Tik op een gezicht om te beginnen."</string>
+    <string name="photos_gallery_title">"Galerij"</string>
+    <string name="pick_photos_gallery_title">"Foto selecteren"</string>
+    <string name="videos_gallery_title">"Galerij"</string>
+    <string name="pick_videos_gallery_title">"Video selecteren"</string>
+    <string name="loading_progress_format_string">"<xliff:g id="COUNTER">%d</xliff:g> resterend"</string>
+    <string name="sendImage">"Foto delen via"</string>
+    <string name="setImage">"Foto instellen als"</string>
+    <string name="sendVideo">"Video delen via"</string>
+    <string name="movieviewlabel">"Films"</string>
+    <string name="loading_video">"Video laden..."</string>
+    <string name="spaceIsLow_title">"Er is weinig ruimte"</string>
+    <string name="spaceIsLow_content">"Er is weinig ruimte beschikbaar op uw SD-kaart. U kunt de kwaliteitsinstelling wijzigen of items uit de galerij verwijderen."</string>
+    <string name="resume_playing_title">"Video hervatten"</string>
+    <string name="resume_playing_message">"Afspelen hervatten vanaf %s ?"</string>
+    <string name="resume_playing_resume">"Afspelen hervatten"</string>
+    <string name="resume_playing_restart">"Opnieuw starten"</string>
+    <string name="gadget_title">"Fotolijstje"</string>
+    <string name="video_file_name_format">"\'video\'-yyyy-MM-dd-HH-mm-ss"</string>
+    <!-- no translation found for file_info_title (1628963357466012538) -->
+    <skip />
+    <!-- no translation found for video_exceed_mms_limit (3835075281230780010) -->
+    <skip />
+</resources>
diff --git a/res/values-pl/arrays.xml b/res/values-pl/arrays.xml
new file mode 100644
index 0000000..0583b87
--- /dev/null
+++ b/res/values-pl/arrays.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for pref_camera_videoquality_entries:0 (4921712231611437934) -->
+    <!-- no translation found for pref_camera_videoquality_entries:1 (3206329580124804732) -->
+  <string-array name="pref_camera_videoquality_entryvalues">
+    <item>"0"</item>
+    <item>"1"</item>
+  </string-array>
+</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
new file mode 100644
index 0000000..7023e80
--- /dev/null
+++ b/res/values-pl/strings.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="all_images">"Wszystkie zdjęcia"</string>
+    <string name="all_videos">"Wszystkie filmy"</string>
+    <string name="camera_label">"Aparat"</string>
+    <string name="gallery_picker_label">"Galeria"</string>
+    <string name="gallery_camera_bucket_name">"Zdjęcia z aparatu"</string>
+    <string name="gallery_camera_videos_bucket_name">"Filmy z aparatu"</string>
+    <string name="switch_to_video_lable">"Przełącz na wideo"</string>
+    <string name="switch_to_camera_lable">"Przełącz na aparat"</string>
+    <string name="crop_label">"Przytnij zdjęcie"</string>
+    <string name="view_label">"Wyświetl zdjęcie"</string>
+    <string name="preferences_label">"Ustawienia aparatu"</string>
+    <string name="wait">"Poczekaj…"</string>
+    <string name="no_storage">"Zanim zaczniesz korzystać z aparatu fotograficznego, włóż kartę SD."</string>
+    <string name="not_enough_space">"Karta SD jest pełna."</string>
+    <string name="preparing_sd">"Przygotowywanie karty SD..."</string>
+    <string name="wallpaper">"Ustawianie tapety, poczekaj…"</string>
+    <string name="savingImage">"Trwa zapisywanie zdjęcia…"</string>
+    <string name="runningFaceDetection">"Poczekaj…"</string>
+    <string name="flip_orientation">"Zmień orientację"</string>
+    <string name="settings">"Ustawienia"</string>
+    <string name="view">"Wyświetl"</string>
+    <string name="details">"Szczegóły"</string>
+    <string name="rotate">"Obróć"</string>
+    <string name="rotate_left">"Obróć w lewo"</string>
+    <string name="rotate_right">"Obróć w prawo"</string>
+    <string name="slide_show">"Pokaz slajdów"</string>
+    <string name="capture_picture">"Zrób zdjęcie"</string>
+    <string name="capture_video">"Nagraj film wideo"</string>
+    <string name="crop_save_text">"Zapisz"</string>
+    <string name="crop_discard_text">"Odrzuć"</string>
+    <string name="confirm_delete_title">"Usuń"</string>
+    <string name="confirm_delete_message">"Zdjęcie zostanie usunięte."</string>
+    <string name="confirm_delete_video_message">"Film wideo zostanie usunięty."</string>
+    <string name="camera_toss">"Usuń"</string>
+    <string name="camera_gallery">"Galeria"</string>
+    <string name="camera_share">"Prześlij"</string>
+    <string name="camera_set">"Ustaw jako"</string>
+    <string name="camera_play">"Odtwórz"</string>
+    <string name="camera_attach">"Załącz"</string>
+    <string name="camera_cancel">"Anuluj"</string>
+    <string name="camera_crop">"Przytnij"</string>
+    <string name="camera_tossing">"Trwa usuwanie…"</string>
+    <string name="no_way_to_share_image">"Nie można przesłać tego zdjęcia."</string>
+    <string name="no_way_to_share_video">"Nie można przesłać tego filmu."</string>
+    <string name="camera_gallery_photos_text">"Galeria"</string>
+    <string name="camera_pick_wallpaper">"Zdjęcia"</string>
+    <string name="camera_setas_wallpaper">"Tapeta"</string>
+    <string name="pref_gallery_category">"Ustawienia ogólne"</string>
+    <string name="pref_slideshow_category">"Ustawienia pokazu slajdów"</string>
+    <string name="pref_camera_general_settings_category">"Ustawienia ogólne"</string>
+    <string name="pref_gallery_size_title">"Rozmiar zdjęć i filmów"</string>
+    <string name="pref_gallery_size_summary">"Ustaw wyświetlany rozmiar zdjęć i filmów"</string>
+    <string name="pref_gallery_size_dialogtitle">"Rozmiar"</string>
+  <string-array name="pref_gallery_size_choices">
+    <item>"Duży"</item>
+    <item>"Mały"</item>
+  </string-array>
+    <string name="pref_gallery_sort_title">"Kolejność sortowania"</string>
+    <string name="pref_gallery_sort_summary">"Wybierz kolejność sortowania zdjęć i filmów"</string>
+    <string name="pref_gallery_sort_dialogtitle">"Sortowanie zdjęć"</string>
+  <string-array name="pref_gallery_sort_choices">
+    <item>"Od najnowszego"</item>
+    <item>"Od najstarszego"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_interval_title">"Pokaz slajdów – interwał"</string>
+    <string name="pref_gallery_slideshow_interval_summary">"Wybierz, jak długo mają być wyświetlane poszczególne zdjęcia w pokazie slajdów"</string>
+    <string name="pref_gallery_slideshow_interval_dialogtitle">"Pokaz slajdów – interwał"</string>
+  <string-array name="pref_gallery_slideshow_interval_choices">
+    <item>"2 sekundy"</item>
+    <item>"3 sekundy"</item>
+    <item>"4 sekundy"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_transition_title">"Pokaz slajdów – efekty"</string>
+    <string name="pref_gallery_slideshow_transition_summary">"Wybierz efekt używany przy przejściu z jednego zdjęcia do kolejnego"</string>
+    <string name="pref_gallery_slideshow_transition_dialogtitle">"Pokaz slajdów – efekty"</string>
+  <string-array name="pref_gallery_slideshow_transition_choices">
+    <item>"Pojawianie i znikanie"</item>
+    <item>"Przesuwanie z lewa w prawo"</item>
+    <item>"Przesuwanie góra-dół"</item>
+    <item>"Wybór losowy"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_repeat_title">"Powtórz pokaz slajdów"</string>
+    <string name="pref_gallery_slideshow_repeat_summary">"Wyświetlaj pokaz slajdów więcej niż raz"</string>
+    <string name="pref_gallery_slideshow_shuffle_title">"Losowa kolejność"</string>
+    <string name="pref_gallery_slideshow_shuffle_summary">"Wyświetlaj zdjęcia w kolejności losowej"</string>
+    <string name="pref_camera_recordlocation_title">"Zapisuj lokalizację w zdjęciach"</string>
+    <string name="pref_camera_recordlocation_summary">"Zapisz lokalizację w danych zdjęcia"</string>
+    <string name="pref_camera_videoquality_category">"Jakość wideo"</string>
+    <string name="pref_camera_videoquality_title">"Wybierz jakość wideo"</string>
+    <string name="pref_camera_videoquality_entry_0">"Niska (dla wiadomości MMS)"</string>
+    <string name="pref_camera_videoquality_entry_1">"Wysoka (dla karty SD)"</string>
+    <string name="pref_camera_videoquality_dialogtitle">"Jakość wideo"</string>
+    <string name="camerasettings">"Ustawienia"</string>
+    <string name="image_gallery_NoImageView_text">"Brak plików."</string>
+    <string name="pref_gallery_confirm_delete_title">"Potwierdzanie usuwania"</string>
+    <string name="pref_gallery_confirm_delete_summary">"Wyświetl potwierdzenie przed usunięciem zdjęcia lub filmu"</string>
+    <string name="details_panel_title">"Szczegóły"</string>
+    <string name="details_file_size">"Rozmiar pliku:"</string>
+    <string name="details_image_resolution">"Rozdzielczość:"</string>
+    <string name="details_duration">"Czas trwania:"</string>
+    <string name="details_date_taken">"Data zrobienia zdjęcia:"</string>
+    <string name="details_frame_rate">"Szybkość klatek:"</string>
+    <string name="details_bit_rate">"Szybkość transmisji:"</string>
+    <string name="details_codec">"Kodek:"</string>
+    <string name="details_format">"Format:"</string>
+    <string name="details_dimension_x">"%1$d x %2$d"</string>
+    <string name="details_ms">"%1$02d:%2$02d"</string>
+    <string name="details_hms">"%1$d:%2$02d:%3$02d"</string>
+    <string name="details_fps">"%1$d ramek/s"</string>
+    <string name="details_kbps">"%1$d Kb/s"</string>
+    <string name="details_mbps">"%1$g Mb/s"</string>
+    <string name="details_ok">"OK"</string>
+    <string name="context_menu_header">"Opcje zdjęć"</string>
+    <string name="video_context_menu_header">"Opcje wideo"</string>
+    <string name="multiface_crop_help">"Dotknij twarzy, aby rozpocząć"</string>
+    <string name="photos_gallery_title">"Galeria"</string>
+    <string name="pick_photos_gallery_title">"Wybierz zdjęcie"</string>
+    <string name="videos_gallery_title">"Galeria"</string>
+    <string name="pick_videos_gallery_title">"Wybierz film wideo"</string>
+    <string name="loading_progress_format_string">"Pozostało <xliff:g id="COUNTER">%d</xliff:g>"</string>
+    <string name="sendImage">"Prześlij zdjęcie następującą drogą:"</string>
+    <string name="setImage">"Ustaw zdjęcie jako"</string>
+    <string name="sendVideo">"Prześlij film następującą drogą:"</string>
+    <string name="movieviewlabel">"Filmy"</string>
+    <string name="loading_video">"Ładowanie filmu..."</string>
+    <string name="spaceIsLow_title">"Miejsca jest mało"</string>
+    <string name="spaceIsLow_content">"Na karcie SD brakuje miejsca. Zmień ustawienie jakości lub usuń elementy z Galerii."</string>
+    <string name="resume_playing_title">"Wznów film"</string>
+    <string name="resume_playing_message">"Wznowić odtwarzanie od %s?"</string>
+    <string name="resume_playing_resume">"Wznów odtwarzanie"</string>
+    <string name="resume_playing_restart">"Rozpocznij"</string>
+    <string name="gadget_title">"Ramka zdjęcia"</string>
+    <string name="video_file_name_format">"\'video\'-yyyy-MM-dd-HH-mm-ss"</string>
+    <!-- no translation found for file_info_title (1628963357466012538) -->
+    <skip />
+    <!-- no translation found for video_exceed_mms_limit (3835075281230780010) -->
+    <skip />
+</resources>
diff --git a/res/values-ru/arrays.xml b/res/values-ru/arrays.xml
new file mode 100644
index 0000000..0583b87
--- /dev/null
+++ b/res/values-ru/arrays.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for pref_camera_videoquality_entries:0 (4921712231611437934) -->
+    <!-- no translation found for pref_camera_videoquality_entries:1 (3206329580124804732) -->
+  <string-array name="pref_camera_videoquality_entryvalues">
+    <item>"0"</item>
+    <item>"1"</item>
+  </string-array>
+</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
new file mode 100644
index 0000000..34cbfd3
--- /dev/null
+++ b/res/values-ru/strings.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="all_images">"Все изображения"</string>
+    <string name="all_videos">"Все видео"</string>
+    <string name="camera_label">"Камера"</string>
+    <string name="gallery_picker_label">"Галерея"</string>
+    <string name="gallery_camera_bucket_name">"Фотографии с камеры"</string>
+    <string name="gallery_camera_videos_bucket_name">"Видео с камеры"</string>
+    <string name="switch_to_video_lable">"Перейти в режим видео"</string>
+    <string name="switch_to_camera_lable">"Перейти в режим камеры"</string>
+    <string name="crop_label">"Обрезать изображение"</string>
+    <string name="view_label">"Просмотреть изображение"</string>
+    <string name="preferences_label">"Настройки камеры"</string>
+    <string name="wait">"Подождите…"</string>
+    <string name="no_storage">"Перед использованием камеры вставьте карту SD."</string>
+    <string name="not_enough_space">"Карта SD заполнена."</string>
+    <string name="preparing_sd">"Подготовка карты SD..."</string>
+    <string name="wallpaper">"Установка фонового рисунка, подождите…"</string>
+    <string name="savingImage">"Идет сохранение изображения…"</string>
+    <string name="runningFaceDetection">"Подождите…"</string>
+    <string name="flip_orientation">"Изменить ориентацию"</string>
+    <string name="settings">"Настройки"</string>
+    <string name="view">"Просмотреть"</string>
+    <string name="details">"Сведения"</string>
+    <string name="rotate">"Повернуть"</string>
+    <string name="rotate_left">"Повернуть налево"</string>
+    <string name="rotate_right">"Повернуть направо"</string>
+    <string name="slide_show">"Слайд-шоу"</string>
+    <string name="capture_picture">"Сделать фотографию"</string>
+    <string name="capture_video">"Снять видео"</string>
+    <string name="crop_save_text">"Сохранить"</string>
+    <string name="crop_discard_text">"Отменить"</string>
+    <string name="confirm_delete_title">"Удаление"</string>
+    <string name="confirm_delete_message">"Изображение будет удалено."</string>
+    <string name="confirm_delete_video_message">"Видео будет удалено."</string>
+    <string name="camera_toss">"Удаление"</string>
+    <string name="camera_gallery">"Галерея"</string>
+    <string name="camera_share">"Отправить"</string>
+    <string name="camera_set">"Установить как"</string>
+    <string name="camera_play">"Воспроизвести"</string>
+    <string name="camera_attach">"Вложить"</string>
+    <string name="camera_cancel">"Отмена"</string>
+    <string name="camera_crop">"Обрезать"</string>
+    <string name="camera_tossing">"Идет удаление…"</string>
+    <string name="no_way_to_share_image">"Отправить это изображение нельзя."</string>
+    <string name="no_way_to_share_video">"Отправить это видео нельзя."</string>
+    <string name="camera_gallery_photos_text">"Галерея"</string>
+    <string name="camera_pick_wallpaper">"Изображения"</string>
+    <string name="camera_setas_wallpaper">"Фоновый рисунок"</string>
+    <string name="pref_gallery_category">"Общие настройки"</string>
+    <string name="pref_slideshow_category">"Настройки слайд-шоу"</string>
+    <string name="pref_camera_general_settings_category">"Общие настройки"</string>
+    <string name="pref_gallery_size_title">"Отображаемый размер"</string>
+    <string name="pref_gallery_size_summary">"Выбор отображаемого размера изображений и видео"</string>
+    <string name="pref_gallery_size_dialogtitle">"Размер изображения"</string>
+  <string-array name="pref_gallery_size_choices">
+    <item>"Крупный"</item>
+    <item>"Мелкий"</item>
+  </string-array>
+    <string name="pref_gallery_sort_title">"Порядок сортировки"</string>
+    <string name="pref_gallery_sort_summary">"Выберите порядок сортировки изображений и видео"</string>
+    <string name="pref_gallery_sort_dialogtitle">"Сортировка изображений"</string>
+  <string-array name="pref_gallery_sort_choices">
+    <item>"Начиная с новых"</item>
+    <item>"Заканчивая новыми"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_interval_title">"Интервал слайд-шоу"</string>
+    <string name="pref_gallery_slideshow_interval_summary">"Выберите время отображения каждого слайда"</string>
+    <string name="pref_gallery_slideshow_interval_dialogtitle">"Интервал слайд-шоу"</string>
+  <string-array name="pref_gallery_slideshow_interval_choices">
+    <item>"2 секунды"</item>
+    <item>"3 секунды"</item>
+    <item>"4 секунды"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_transition_title">"Переходы слайд-шоу"</string>
+    <string name="pref_gallery_slideshow_transition_summary">"Выберите эффект для перехода от одного слайда к другому"</string>
+    <string name="pref_gallery_slideshow_transition_dialogtitle">"Переходы слайд-шоу"</string>
+  <string-array name="pref_gallery_slideshow_transition_choices">
+    <item>"Затемнение"</item>
+    <item>"Слайды слева направо"</item>
+    <item>"Слайды сверху вниз"</item>
+    <item>"Случайный выбор"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_repeat_title">"Повторить слайд-шоу"</string>
+    <string name="pref_gallery_slideshow_repeat_summary">"Воспроизвести слайд-шоу несколько раз"</string>
+    <string name="pref_gallery_slideshow_shuffle_title">"Перемешать слайды"</string>
+    <string name="pref_gallery_slideshow_shuffle_summary">"Показывать изображения в случайном порядке"</string>
+    <string name="pref_camera_recordlocation_title">"Сохранять адрес в изображениях"</string>
+    <string name="pref_camera_recordlocation_summary">"Записывать адрес в данные изображения"</string>
+    <string name="pref_camera_videoquality_category">"Качество видео"</string>
+    <string name="pref_camera_videoquality_title">"Выбор качества видео"</string>
+    <string name="pref_camera_videoquality_entry_0">"Низкое (для MMS)"</string>
+    <string name="pref_camera_videoquality_entry_1">"Высокое (для карты SD)"</string>
+    <string name="pref_camera_videoquality_dialogtitle">"Качество видео"</string>
+    <string name="camerasettings">"Настройки"</string>
+    <string name="image_gallery_NoImageView_text">"Нет мультимедийных материалов."</string>
+    <string name="pref_gallery_confirm_delete_title">"Подтверждение удаления"</string>
+    <string name="pref_gallery_confirm_delete_summary">"Запрашивать подтверждения перед удалением изображения или видео"</string>
+    <string name="details_panel_title">"Сведения"</string>
+    <string name="details_file_size">"Размер файла:"</string>
+    <string name="details_image_resolution">"Разрешение:"</string>
+    <string name="details_duration">"Продолжительность:"</string>
+    <string name="details_date_taken">"Дата съемки:"</string>
+    <string name="details_frame_rate">"Частота кадров:"</string>
+    <string name="details_bit_rate">"Битрейт:"</string>
+    <string name="details_codec">"Кодек:"</string>
+    <string name="details_format">"Формат:"</string>
+    <string name="details_dimension_x">"%1$d на %2$d"</string>
+    <string name="details_ms">"%1$02d:%2$02d"</string>
+    <string name="details_hms">"%1$d:%2$02d:%3$02d"</string>
+    <string name="details_fps">"%1$d кадров/с"</string>
+    <string name="details_kbps">"%1$d кбит/с"</string>
+    <string name="details_mbps">"%1$g мбит/с"</string>
+    <string name="details_ok">"ОК"</string>
+    <string name="context_menu_header">"Параметры изображения"</string>
+    <string name="video_context_menu_header">"Параметры видео"</string>
+    <string name="multiface_crop_help">"Чтобы начать, выберите лицо."</string>
+    <string name="photos_gallery_title">"Галерея"</string>
+    <string name="pick_photos_gallery_title">"Выбор изображения"</string>
+    <string name="videos_gallery_title">"Галерея"</string>
+    <string name="pick_videos_gallery_title">"Выбрать видео"</string>
+    <string name="loading_progress_format_string">"Осталось: <xliff:g id="COUNTER">%d</xliff:g>"</string>
+    <string name="sendImage">"Отправить изображение при помощи"</string>
+    <string name="setImage">"Установить изображение как"</string>
+    <string name="sendVideo">"Отправить видео при помощи"</string>
+    <string name="movieviewlabel">"Фильмы"</string>
+    <string name="loading_video">"Идет загрузка видео…"</string>
+    <string name="spaceIsLow_title">"Осталось мало места"</string>
+    <string name="spaceIsLow_content">"Место на карте SD заканчивается. Измените настройки качества или удалите элементы из галереи."</string>
+    <string name="resume_playing_title">"Продолжение просмотра видео"</string>
+    <string name="resume_playing_message">"Продолжить воспроизведение с %s ?"</string>
+    <string name="resume_playing_resume">"Продолжить воспроизведение"</string>
+    <string name="resume_playing_restart">"Начать с начала"</string>
+    <string name="gadget_title">"Рамка фотографии"</string>
+    <string name="video_file_name_format">"\'video\'-yyyy-MM-dd-HH-mm-ss"</string>
+    <!-- no translation found for file_info_title (1628963357466012538) -->
+    <skip />
+    <!-- no translation found for video_exceed_mms_limit (3835075281230780010) -->
+    <skip />
+</resources>
diff --git a/res/values-zh-rCN/arrays.xml b/res/values-zh-rCN/arrays.xml
new file mode 100644
index 0000000..0583b87
--- /dev/null
+++ b/res/values-zh-rCN/arrays.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for pref_camera_videoquality_entries:0 (4921712231611437934) -->
+    <!-- no translation found for pref_camera_videoquality_entries:1 (3206329580124804732) -->
+  <string-array name="pref_camera_videoquality_entryvalues">
+    <item>"0"</item>
+    <item>"1"</item>
+  </string-array>
+</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..f89c694
--- /dev/null
+++ b/res/values-zh-rCN/strings.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="all_images">"所有图片"</string>
+    <string name="all_videos">"所有视频"</string>
+    <string name="camera_label">"相机"</string>
+    <string name="gallery_picker_label">"图片库"</string>
+    <string name="gallery_camera_bucket_name">"相机照片"</string>
+    <string name="gallery_camera_videos_bucket_name">"相机视频"</string>
+    <string name="switch_to_video_lable">"切换到视频"</string>
+    <string name="switch_to_camera_lable">"切换到相机"</string>
+    <string name="crop_label">"裁切图片"</string>
+    <string name="view_label">"查看图片"</string>
+    <string name="preferences_label">"相机设置"</string>
+    <string name="wait">"请稍候..."</string>
+    <string name="no_storage">"请在使用相机之前插入 SD 卡。"</string>
+    <string name="not_enough_space">"您的 SD 卡已满。"</string>
+    <string name="preparing_sd">"正在准备 SD 卡..."</string>
+    <string name="wallpaper">"正在设置壁纸,请稍候..."</string>
+    <string name="savingImage">"正在保存图片..."</string>
+    <string name="runningFaceDetection">"请稍候..."</string>
+    <string name="flip_orientation">"反转方向"</string>
+    <string name="settings">"设置"</string>
+    <string name="view">"查看"</string>
+    <string name="details">"详细信息"</string>
+    <string name="rotate">"旋转"</string>
+    <string name="rotate_left">"向左旋转"</string>
+    <string name="rotate_right">"向右旋转"</string>
+    <string name="slide_show">"幻灯片演示"</string>
+    <string name="capture_picture">"拍摄照片"</string>
+    <string name="capture_video">"拍摄视频"</string>
+    <string name="crop_save_text">"保存"</string>
+    <string name="crop_discard_text">"放弃"</string>
+    <string name="confirm_delete_title">"删除"</string>
+    <string name="confirm_delete_message">"会删除该图片。"</string>
+    <string name="confirm_delete_video_message">"会删除该视频。"</string>
+    <string name="camera_toss">"删除"</string>
+    <string name="camera_gallery">"图片库"</string>
+    <string name="camera_share">"共享"</string>
+    <string name="camera_set">"设置为"</string>
+    <string name="camera_play">"播放"</string>
+    <string name="camera_attach">"附加"</string>
+    <string name="camera_cancel">"取消"</string>
+    <string name="camera_crop">"裁切"</string>
+    <string name="camera_tossing">"正在删除..."</string>
+    <string name="no_way_to_share_image">"无法共享此图片。"</string>
+    <string name="no_way_to_share_video">"无法共享此视频。"</string>
+    <string name="camera_gallery_photos_text">"图片库"</string>
+    <string name="camera_pick_wallpaper">"图片"</string>
+    <string name="camera_setas_wallpaper">"壁纸"</string>
+    <string name="pref_gallery_category">"常规设置"</string>
+    <string name="pref_slideshow_category">"幻灯片演示设置"</string>
+    <string name="pref_camera_general_settings_category">"常规设置"</string>
+    <string name="pref_gallery_size_title">"显示大小"</string>
+    <string name="pref_gallery_size_summary">"选择图片和视频的显示大小"</string>
+    <string name="pref_gallery_size_dialogtitle">"图片大小"</string>
+  <string-array name="pref_gallery_size_choices">
+    <item>"大"</item>
+    <item>"小"</item>
+  </string-array>
+    <string name="pref_gallery_sort_title">"排序顺序"</string>
+    <string name="pref_gallery_sort_summary">"选择图片和视频的排序顺序"</string>
+    <string name="pref_gallery_sort_dialogtitle">"图片排序"</string>
+  <string-array name="pref_gallery_sort_choices">
+    <item>"最新的在最前面"</item>
+    <item>"最新的在最后面"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_interval_title">"幻灯片演示间隔"</string>
+    <string name="pref_gallery_slideshow_interval_summary">"选择每张幻灯片在演示中显示的时间长度"</string>
+    <string name="pref_gallery_slideshow_interval_dialogtitle">"幻灯片演示间隔"</string>
+  <string-array name="pref_gallery_slideshow_interval_choices">
+    <item>"2 秒"</item>
+    <item>"3 秒"</item>
+    <item>"4 秒"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_transition_title">"幻灯片演示过渡"</string>
+    <string name="pref_gallery_slideshow_transition_summary">"选择从一张幻灯片移动到下一张时使用的效果"</string>
+    <string name="pref_gallery_slideshow_transition_dialogtitle">"幻灯片演示过渡"</string>
+  <string-array name="pref_gallery_slideshow_transition_choices">
+    <item>"逐渐增强和逐渐减弱"</item>
+    <item>"自左至右滑动"</item>
+    <item>"自上至下滑动"</item>
+    <item>"随机选择"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_repeat_title">"重复幻灯片演示"</string>
+    <string name="pref_gallery_slideshow_repeat_summary">"多次播放幻灯片演示"</string>
+    <string name="pref_gallery_slideshow_shuffle_title">"随机演示幻灯片"</string>
+    <string name="pref_gallery_slideshow_shuffle_summary">"以随机顺序显示图片"</string>
+    <string name="pref_camera_recordlocation_title">"在图片中存储位置"</string>
+    <string name="pref_camera_recordlocation_summary">"通过图片数据记录位置"</string>
+    <string name="pref_camera_videoquality_category">"视频质量"</string>
+    <string name="pref_camera_videoquality_title">"选择视频质量"</string>
+    <string name="pref_camera_videoquality_entry_0">"低(对于彩信)"</string>
+    <string name="pref_camera_videoquality_entry_1">"高(对于 SD 卡)"</string>
+    <string name="pref_camera_videoquality_dialogtitle">"视频质量"</string>
+    <string name="camerasettings">"设置"</string>
+    <string name="image_gallery_NoImageView_text">"找不到媒体。"</string>
+    <string name="pref_gallery_confirm_delete_title">"确认删除"</string>
+    <string name="pref_gallery_confirm_delete_summary">"在删除图片或视频前显示确认"</string>
+    <string name="details_panel_title">"详细信息"</string>
+    <string name="details_file_size">"文件大小:"</string>
+    <string name="details_image_resolution">"分辨率:"</string>
+    <string name="details_duration">"持续时间:"</string>
+    <string name="details_date_taken">"拍摄日期:"</string>
+    <string name="details_frame_rate">"帧频:"</string>
+    <string name="details_bit_rate">"比特率:"</string>
+    <string name="details_codec">"编解码器:"</string>
+    <string name="details_format">"格式:"</string>
+    <string name="details_dimension_x">"%1$d x %2$d"</string>
+    <string name="details_ms">"%1$02d:%2$02d"</string>
+    <string name="details_hms">"%1$d:%2$02d:%3$02d"</string>
+    <string name="details_fps">"%1$d fps"</string>
+    <string name="details_kbps">"%1$d Kbps"</string>
+    <string name="details_mbps">"%1$g Mbps"</string>
+    <string name="details_ok">"确定"</string>
+    <string name="context_menu_header">"图片选项"</string>
+    <string name="video_context_menu_header">"视频选项"</string>
+    <string name="multiface_crop_help">"轻击一张脸开始裁切。"</string>
+    <string name="photos_gallery_title">"图片库"</string>
+    <string name="pick_photos_gallery_title">"选择图片"</string>
+    <string name="videos_gallery_title">"图片库"</string>
+    <string name="pick_videos_gallery_title">"选择视频"</string>
+    <string name="loading_progress_format_string">"还剩 <xliff:g id="COUNTER">%d</xliff:g>"</string>
+    <string name="sendImage">"共享图片的方式"</string>
+    <string name="setImage">"将图片设置为"</string>
+    <string name="sendVideo">"共享视频的方式"</string>
+    <string name="movieviewlabel">"电影"</string>
+    <string name="loading_video">"正在载入视频..."</string>
+    <string name="spaceIsLow_title">"空间不足"</string>
+    <string name="spaceIsLow_content">"您的 SD 卡已满。请更改质量设置或删除图片库中的内容。"</string>
+    <string name="resume_playing_title">"重新播放视频"</string>
+    <string name="resume_playing_message">"从 %s 开始重新播放?"</string>
+    <string name="resume_playing_resume">"重新播放"</string>
+    <string name="resume_playing_restart">"重新开始"</string>
+    <string name="gadget_title">"相框"</string>
+    <string name="video_file_name_format">"\'video\'-yyyy-MM-dd-HH-mm-ss"</string>
+    <!-- no translation found for file_info_title (1628963357466012538) -->
+    <skip />
+    <!-- no translation found for video_exceed_mms_limit (3835075281230780010) -->
+    <skip />
+</resources>
diff --git a/res/values-zh-rTW/arrays.xml b/res/values-zh-rTW/arrays.xml
new file mode 100644
index 0000000..0583b87
--- /dev/null
+++ b/res/values-zh-rTW/arrays.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for pref_camera_videoquality_entries:0 (4921712231611437934) -->
+    <!-- no translation found for pref_camera_videoquality_entries:1 (3206329580124804732) -->
+  <string-array name="pref_camera_videoquality_entryvalues">
+    <item>"0"</item>
+    <item>"1"</item>
+  </string-array>
+</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..c311294
--- /dev/null
+++ b/res/values-zh-rTW/strings.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="all_images">"所有相片"</string>
+    <string name="all_videos">"所有影片"</string>
+    <string name="camera_label">"相機"</string>
+    <string name="gallery_picker_label">"圖庫"</string>
+    <string name="gallery_camera_bucket_name">"攝影機圖片"</string>
+    <string name="gallery_camera_videos_bucket_name">"攝影機影片"</string>
+    <string name="switch_to_video_lable">"切換為影片"</string>
+    <string name="switch_to_camera_lable">"切換為攝影機"</string>
+    <string name="crop_label">"裁切相片"</string>
+    <string name="view_label">"檢視相片"</string>
+    <string name="preferences_label">"相機設定"</string>
+    <string name="wait">"請稍候…"</string>
+    <string name="no_storage">"使用相機前請先插入 SD 卡。"</string>
+    <string name="not_enough_space">"SD 卡已滿。"</string>
+    <string name="preparing_sd">"正在準備 SD 卡..."</string>
+    <string name="wallpaper">"設定桌布中,請稍候…"</string>
+    <string name="savingImage">"儲存相片中…"</string>
+    <string name="runningFaceDetection">"請稍候…"</string>
+    <string name="flip_orientation">"旋轉方向"</string>
+    <string name="settings">"設定"</string>
+    <string name="view">"檢視"</string>
+    <string name="details">"詳細資料"</string>
+    <string name="rotate">"旋轉"</string>
+    <string name="rotate_left">"向左旋轉"</string>
+    <string name="rotate_right">"向右旋轉"</string>
+    <string name="slide_show">"投影片"</string>
+    <string name="capture_picture">"拍攝圖片"</string>
+    <string name="capture_video">"拍攝影片"</string>
+    <string name="crop_save_text">"儲存"</string>
+    <string name="crop_discard_text">"放棄"</string>
+    <string name="confirm_delete_title">"刪除"</string>
+    <string name="confirm_delete_message">"此相片會被刪除。"</string>
+    <string name="confirm_delete_video_message">"將刪除此影片。"</string>
+    <string name="camera_toss">"刪除"</string>
+    <string name="camera_gallery">"圖庫"</string>
+    <string name="camera_share">"分享"</string>
+    <string name="camera_set">"設為"</string>
+    <string name="camera_play">"播放"</string>
+    <string name="camera_attach">"附加"</string>
+    <string name="camera_cancel">"取消"</string>
+    <string name="camera_crop">"裁切"</string>
+    <string name="camera_tossing">"刪除中…"</string>
+    <string name="no_way_to_share_image">"此相片無法分享。"</string>
+    <string name="no_way_to_share_video">"此影片無法分享。"</string>
+    <string name="camera_gallery_photos_text">"圖庫"</string>
+    <string name="camera_pick_wallpaper">"相片"</string>
+    <string name="camera_setas_wallpaper">"桌布"</string>
+    <string name="pref_gallery_category">"一般設定"</string>
+    <string name="pref_slideshow_category">"投影片設定"</string>
+    <string name="pref_camera_general_settings_category">"一般設定"</string>
+    <string name="pref_gallery_size_title">"顯示大小"</string>
+    <string name="pref_gallery_size_summary">"選取相片和影片顯示大小"</string>
+    <string name="pref_gallery_size_dialogtitle">"相片大小"</string>
+  <string-array name="pref_gallery_size_choices">
+    <item>"大"</item>
+    <item>"小"</item>
+  </string-array>
+    <string name="pref_gallery_sort_title">"排序順序"</string>
+    <string name="pref_gallery_sort_summary">"選取相片和影片排序順序"</string>
+    <string name="pref_gallery_sort_dialogtitle">"圖片排序"</string>
+  <string-array name="pref_gallery_sort_choices">
+    <item>"最新的優先"</item>
+    <item>"最舊的優先"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_interval_title">"投影片顯示間隔"</string>
+    <string name="pref_gallery_slideshow_interval_summary">"選取每張投影片顯示時間"</string>
+    <string name="pref_gallery_slideshow_interval_dialogtitle">"投影片顯示間隔"</string>
+  <string-array name="pref_gallery_slideshow_interval_choices">
+    <item>"2 秒"</item>
+    <item>"3 秒"</item>
+    <item>"4 秒"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_transition_title">"投影片轉場效果"</string>
+    <string name="pref_gallery_slideshow_transition_summary">"選取相片更換時的轉場效果"</string>
+    <string name="pref_gallery_slideshow_transition_dialogtitle">"投影片轉場效果"</string>
+  <string-array name="pref_gallery_slideshow_transition_choices">
+    <item>"淡入淡出"</item>
+    <item>"左右滑動"</item>
+    <item>"上下滑動"</item>
+    <item>"隨機選取"</item>
+  </string-array>
+    <string name="pref_gallery_slideshow_repeat_title">"重複播放投影片"</string>
+    <string name="pref_gallery_slideshow_repeat_summary">"播放投影片多次"</string>
+    <string name="pref_gallery_slideshow_shuffle_title">"隨機播放投影片"</string>
+    <string name="pref_gallery_slideshow_shuffle_summary">"隨機顯示相片"</string>
+    <string name="pref_camera_recordlocation_title">"在相片儲存位置資訊"</string>
+    <string name="pref_camera_recordlocation_summary">"在相片中紀錄位置資訊"</string>
+    <string name="pref_camera_videoquality_category">"影片品質"</string>
+    <string name="pref_camera_videoquality_title">"選取影片品質"</string>
+    <string name="pref_camera_videoquality_entry_0">"低 (適用於多媒體簡訊)"</string>
+    <string name="pref_camera_videoquality_entry_1">"高 (適用於 SD 卡)"</string>
+    <string name="pref_camera_videoquality_dialogtitle">"影片品質"</string>
+    <string name="camerasettings">"設定"</string>
+    <string name="image_gallery_NoImageView_text">"找不到媒體。"</string>
+    <string name="pref_gallery_confirm_delete_title">"刪除確認"</string>
+    <string name="pref_gallery_confirm_delete_summary">"刪除相片或影片前要顯示確認"</string>
+    <string name="details_panel_title">"詳細資料"</string>
+    <string name="details_file_size">"檔案大小:"</string>
+    <string name="details_image_resolution">"解析度:"</string>
+    <string name="details_duration">"所需時間:"</string>
+    <string name="details_date_taken">"拍攝日期:"</string>
+    <string name="details_frame_rate">"影格速率:"</string>
+    <string name="details_bit_rate">"位元速率:"</string>
+    <string name="details_codec">"編碼器:"</string>
+    <string name="details_format">"格式:"</string>
+    <string name="details_dimension_x">"%1$d x %2$d"</string>
+    <string name="details_ms">"%1$02d:%2$02d"</string>
+    <string name="details_hms">"%1$d:%2$02d:%3$02d"</string>
+    <string name="details_fps">"%1$d fps"</string>
+    <string name="details_kbps">"%1$d Kbps"</string>
+    <string name="details_mbps">"%1$g Mbps"</string>
+    <string name="details_ok">"確定"</string>
+    <string name="context_menu_header">"圖片選項"</string>
+    <string name="video_context_menu_header">"影片選項"</string>
+    <string name="multiface_crop_help">"選取版面開始裁切"</string>
+    <string name="photos_gallery_title">"圖庫"</string>
+    <string name="pick_photos_gallery_title">"選取相片"</string>
+    <string name="videos_gallery_title">"圖庫"</string>
+    <string name="pick_videos_gallery_title">"選取影片"</string>
+    <string name="loading_progress_format_string">"還有 <xliff:g id="COUNTER">%d</xliff:g> 張"</string>
+    <string name="sendImage">"透過…分享圖片"</string>
+    <string name="setImage">"設定相片為…"</string>
+    <string name="sendVideo">"透過…分享影片"</string>
+    <string name="movieviewlabel">"電影"</string>
+    <string name="loading_video">"正在載入影片…"</string>
+    <string name="spaceIsLow_title">"空間不足"</string>
+    <string name="spaceIsLow_content">"您的 SD 卡空間已快用完。請變更品質設定或刪除圖庫中的項目。"</string>
+    <string name="resume_playing_title">"繼續播放影片"</string>
+    <string name="resume_playing_message">"要從 %s 繼續播放嗎?"</string>
+    <string name="resume_playing_resume">"繼續播放"</string>
+    <string name="resume_playing_restart">"重新開始"</string>
+    <string name="gadget_title">"相框"</string>
+    <string name="video_file_name_format">"\'video\'-yyyy-MM-dd-HH-mm-ss"</string>
+    <!-- no translation found for file_info_title (1628963357466012538) -->
+    <skip />
+    <!-- no translation found for video_exceed_mms_limit (3835075281230780010) -->
+    <skip />
+</resources>
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
new file mode 100644
index 0000000..95501e4
--- /dev/null
+++ b/res/values/arrays.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 
+ * Copyright (C) 2008 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>
+    <!-- Camera Preferences Video Quality dialog box entries -->
+    <string-array name="pref_camera_videoquality_entries">
+        <item>@string/pref_camera_videoquality_entry_0</item>
+        <item>@string/pref_camera_videoquality_entry_1</item>
+    </string-array>
+
+	<!-- Do not localize entryvalues -->
+    <string-array name="pref_camera_videoquality_entryvalues">
+        <item>0</item>
+        <item>1</item>
+    </string-array>
+
+</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
new file mode 100644
index 0000000..d07f3b9
--- /dev/null
+++ b/res/values/colors.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/colors.xml
+**
+** Copyright 2008, 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>
+    <color name="bubble_dark_background">#B2191919</color>
+</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
new file mode 100644
index 0000000..5ec105b
--- /dev/null
+++ b/res/values/dimens.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/dimens.xml
+**
+** Copyright 2009, 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>
+    <dimen name="hint_y_offset">64dip</dimen>
+</resources>
diff --git a/res/values/ids.xml b/res/values/ids.xml
new file mode 100644
index 0000000..367bfa2
--- /dev/null
+++ b/res/values/ids.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2007, 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>
+</resources>
+
+        
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..ab7d88e
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,458 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- General strings -->
+
+    <!-- label for the icon meaning 'show me all the images' -->
+    <string name="all_images">All pictures</string>
+
+    <!-- label for the icon meaning 'show me all the videos' -->
+    <string name="all_videos">All videos</string>
+    
+    <!-- label for the icon meaning 'show me all the images that were taken with the camera' -->
+    <string name="camera_label">Camera</string>
+
+    <!-- label for the 'pictures application shown in the top level 'all applications' -->
+    <string name="gallery_picker_label">Gallery</string>
+
+    <!-- unused (do not translate) -->
+    <string name="gallery_label">Pictures</string>
+    
+    <!-- label for the folder that contains Camera pictures in the gallery -->
+    <string name="gallery_camera_bucket_name">Camera pictures</string>
+
+    <!-- label for the folder that contains Camera videos in the gallery -->
+    <string name="gallery_camera_videos_bucket_name">Camera videos</string>
+    
+    <!-- menu pick: switch to Video cam mode -->
+    <string name="switch_to_video_lable">Switch to video</string>
+
+    <!-- menu pick: switch to camera mode -->
+    <string name="switch_to_camera_lable">Switch to camera</string>
+    
+    <!-- menu pick: crop the currently selected image -->
+    <string name="crop_label">Crop picture</string>
+
+    <!-- menu pick: view the currently selected image -->
+    <string name="view_label">View picture</string>
+
+    <!-- menu pick: go to the preferences screen for the camera or image gallery -->
+    <string name="preferences_label">Camera settings</string>
+
+    <!-- alert to the user to wait for some operation to complete -->
+    <string name="wait">Please wait\u2026</string>
+
+    <!-- alert to the user to that an SD card must be installed before using the camera -->
+    <string name="no_storage">Please insert an SD card before using the camera.</string>
+
+    <!-- alert to the user to that the SD card is too full to complete the operation -->
+    <string name="not_enough_space">Your SD card is full.</string>
+
+    <!-- alert to the user to that the SD card is being disk-checked -->
+    <string name="preparing_sd">Preparing SD card\u2026</string>
+
+    <!-- Toast/alert after saving wallpaper -->
+    <string name="wallpaper">Setting wallpaper, please wait\u2026</string>
+
+    <!-- Settings stuff -->
+
+    <!-- Toast/alert that the image is being saved to the SD card -->
+    <string name="savingImage">Saving picture\u2026</string>
+
+    <!-- Toast/alert that the face detection is being run -->
+    <string name="runningFaceDetection">Please wait\u2026</string>
+
+    
+    <!-- Menu items: -->
+    <!-- menu pick to change the orientation of the screen -->
+    <string name="flip_orientation">Flip orientation</string>
+
+    <!-- menu pick to go to the settings screen -->
+    <string name="settings">Settings</string>
+
+    <!-- menu pick to view the currently selected image -->
+    <string name="view">View</string>
+
+    <!-- menu pick to view the details of the currently selected image -->
+    <string name="details">Details</string>
+
+    <!-- menu pick to rotate the currently selected image (brings up submenu) -->
+    <string name="rotate">Rotate</string>
+
+    <!-- menu pick to rotate the currently selected image to the left -->
+    <string name="rotate_left">Rotate left</string>
+
+    <!-- menu pick to rotate the currently selected image to the right -->
+    <string name="rotate_right">Rotate right</string>
+
+    <!-- menu pick to start a slide show -->
+    <string name="slide_show">Slideshow</string>
+    
+    <!-- menu pick to go to camera mode to capture a picture -->
+    <string name="capture_picture">Capture picture</string>
+    <!-- menu pick to go to video mode to capture a video -->
+    <string name="capture_video">Capture video</string>
+    
+    <!-- button indicating that the cropped image should be saved -->
+    <string name="crop_save_text">Save</string>
+    <!-- button indicating that the cropped image should be reverted back to the original -->
+    <string name="crop_discard_text">Discard</string>
+
+    <!-- Confirmation dialog title after deleting a picture -->
+    <string name="confirm_delete_title">Delete</string>
+    <!-- Confirmation dialog message after deleting a picture -->
+    <string name="confirm_delete_message">The picture will be deleted.</string>
+    <!-- Confirmation dialog message after deleting a video -->
+    <string name="confirm_delete_video_message">The video will be deleted.</string>
+    <!-- button indicating that the picture just taken should be deleted -->
+    <string name="camera_toss">Delete</string>
+
+    <!-- Lable for the button that takes the user to the camera pictures Gallery -->
+    <string name="camera_gallery">Gallery</string>
+    
+    <!-- button indicating that the picture just taken should be shared by email, mms, etc -->
+    <string name="camera_share">Share</string>
+
+    <!-- button indicating that the picture just taken should be set as a contact photo, wallpaper, etc -->
+    <string name="camera_set">Set as</string>
+
+    <!-- button indicating that the video just taken should be played -->
+    <string name="camera_play">Play</string>
+    
+    <!-- button indicating that the video just taken should be accepted as an attachment -->
+    <string name="camera_attach">Attach</string>
+    
+    <!-- button indicating that the video recording session should be canceled -->
+    <string name="camera_cancel">Cancel</string>
+    
+    <!-- button indicating that the picture just taken should be cropped -->
+    <string name="camera_crop">Crop</string>
+
+    <!-- Toast after deleting a picture -->
+    <string name="camera_tossing">Deleting\u2026</string>
+
+    <!-- Toast after trying to share a picture indicating that there are no applications which are capable of so doing. -->
+    <string name="no_way_to_share_image">This picture cannot be shared.</string>
+
+    <!-- Toast after trying to share a video indicating that there are no applications which are capable of so doing. -->
+    <string name="no_way_to_share_video">This video cannot be shared.</string>
+
+    <!-- Do not translate -->
+    <string name="video_play">Play</string>
+
+    <!-- Button indicating to go to the image gallery -->
+    <string name="camera_gallery_photos_text">Gallery</string>
+
+    <!-- String indicating an action of picking a picture to use as wallpaper (e.g. set wallpaper from "Pictures") -->
+    <string name="camera_pick_wallpaper">Pictures</string>
+    <string name="camera_setas_wallpaper">Wallpaper</string>
+
+    <!-- Settings screen, section heading  -->
+    <string name="pref_gallery_category">General settings</string>
+
+    <!-- Settings screen, section heading  -->
+    <string name="pref_slideshow_category">Slideshow settings</string>
+    <string name="pref_camera_general_settings_category">General settings</string>
+
+    <!-- Settings screen, setting summary text -->
+    <string name="pref_gallery_size_title">Display size</string>
+
+    <!-- Settings screen, title for preference for image size to be used in the im -->
+    <string name="pref_gallery_size_summary">Select the display size of pictures and videos</string>
+    <!-- Title of dialog that appears after selecting Picture size setting option -->
+    <string name="pref_gallery_size_dialogtitle">Picture size</string>
+    <!-- Options in dialog that appears after selecting Picture size setting option -->
+    <string-array name="pref_gallery_size_choices">
+        <!-- size choice of "large" -->
+        <item>Large</item>
+        <!-- size choice of "small" -->
+        <item>Small</item>
+    </string-array>
+    <!-- Do not translate. Option values in dialog that appears after selecting Picture size setting option -->
+    <string-array name="pref_gallery_size_values">
+        <!-- do not translate -->
+        <item>1</item>
+        <!-- do not translate -->
+        <item>0</item>
+    </string-array>
+    <!-- Do not translate. Default option value in dialog that appears after selecting Picture size setting option -->
+    <string name="default_value_pref_gallery_size">1</string>
+    <!-- Settings screen, setting option name -->
+    <string name="pref_gallery_sort_title">Sort order</string>
+    <!-- Settings screen, setting summary text -->
+    <string name="pref_gallery_sort_summary">Select the sort order of pictures and videos</string>
+    <!-- Title of dialog that appears after selecting Picture sort setting option -->
+    <string name="pref_gallery_sort_dialogtitle">Picture sort</string>
+    <!-- Options in dialog that appears after selecting Picture sort setting option -->
+    <string-array name="pref_gallery_sort_choices">
+        <!-- Preference choice to show "newest first" -->
+        <item>Newest first</item>
+        <!-- Preference choice to show "newest last" -->
+        <item>Newest last</item>
+    </string-array>
+    <!-- Do not translate. Option values in dialog that appears after selecting Picture sort setting option -->
+    <string-array name="pref_gallery_sort_values">
+        <!-- do not translate -->
+        <item>descending</item>
+        <!-- do not translate -->
+        <item>ascending</item>
+    </string-array>
+    <!-- Do not translate. Default option value in dialog that appears after selecting Picture sort setting option -->
+    <string name="default_value_pref_gallery_sort">descending</string>
+    <!-- Settings screen, setting option name -->
+    <string name="pref_gallery_slideshow_interval_title">Slideshow interval</string>
+    <!-- Settings screen, setting summary text -->
+    <string name="pref_gallery_slideshow_interval_summary">Select how long each slide displays in the show</string>
+    <!-- Title of dialog that appears after selecting Slideshow interval setting option -->
+    <string name="pref_gallery_slideshow_interval_dialogtitle">Slideshow interval</string>
+    <!-- Options in dialog that appears after selecting Slideshow interval
+     setting option -->
+    <string-array name="pref_gallery_slideshow_interval_choices">
+        <!-- slide show interval "N seconds" where N is 2 -->
+        <item>2 seconds</item>
+        <!-- slide show interval "N seconds" where N is 3 -->
+        <item>3 seconds</item>
+        <!-- slide show interval "N seconds" where N is 4 -->
+        <item>4 seconds</item>
+    </string-array>
+    <!-- Do not translate. Option values in dialog that appears after selecting Slideshow interval setting option -->
+    <string-array name="pref_gallery_slideshow_interval_values">
+        <!-- do not translate -->
+        <item>"2"</item>
+        <!-- do not translate -->
+        <item>"3"</item>
+        <!-- do not translate -->
+        <item>"4"</item>
+    </string-array>
+    <!-- Do not translate. Default option value in dialog that appears after selecting Slideshow interval setting option -->
+    <string name="default_value_pref_gallery_slideshow_interval">"2"</string>
+    <!-- Settings screen, setting option name -->
+    <string name="pref_gallery_slideshow_transition_title">Slideshow transition</string>
+    <!-- Settings screen, setting summary text -->
+    <string name="pref_gallery_slideshow_transition_summary">Select the effect used when moving from one slide to the next</string>
+    <!-- Title of dialog that appears after selecting Slideshow transition setting option -->
+    <string name="pref_gallery_slideshow_transition_dialogtitle">Slideshow transition</string>
+    <!-- Options in dialog that appears after selecting Slideshow transition
+     setting option -->
+    <string-array name="pref_gallery_slideshow_transition_choices">
+        <!-- Slide show transition to fade in and fade out -->
+        <item>Fade in &amp; out</item>
+        <!-- Slide show transition to slide in and out from the left and right -->
+        <item>Slide left - right</item>
+        <!-- Slide show transition to slide in and out from the top and bottom -->
+        <item>Slide up - down</item>
+        <!-- Slide show transition to be chosen randomly -->
+        <item>Random selection</item>
+    </string-array>
+    <!-- Do not translate. Option values in dialog that appears after selecting Slideshow transition setting option -->
+    <string-array name="pref_gallery_slideshow_transition_values">
+        <!-- do not translate -->
+        <item>"0"</item>
+        <!-- do not translate -->
+        <item>"1"</item>
+        <!-- do not translate -->
+        <item>"2"</item>
+        <!-- do not translate -->
+        <item>"-1"</item>
+    </string-array>
+    <!-- Do not translate. Default option value in dialog that appears after selecting Slideshow transition setting option -->
+    <string name="default_value_pref_gallery_slideshow_transition">"0"</string>
+
+    <!-- Settings screen, setting check box name -->
+    <string name="pref_gallery_slideshow_repeat_title">Repeat slideshow</string>
+
+    <!-- Settings screen, setting summary text -->
+    <string name="pref_gallery_slideshow_repeat_summary">Play slideshow more than once</string>
+
+    <!-- Settings screen, setting check box name -->
+    <string name="pref_gallery_slideshow_shuffle_title">Shuffle slides</string>
+
+    <!-- Settings screen, setting summary text -->
+    <string name="pref_gallery_slideshow_shuffle_summary">Show pictures in random order</string>
+
+    <!-- Settings screen, setting title text -->
+    <string name="pref_camera_recordlocation_title">Store location in pictures</string>
+
+    <!-- Settings screen, setting summary text -->
+    <string name="pref_camera_recordlocation_summary">Record location in picture data</string>
+
+    <!-- Do not translate -->
+    <string name="pref_camera_upload_albumname_title">Picasa album name</string>
+
+    <!-- Do not translate -->
+    <string name="pref_camera_upload_albumname_summary">Name the destination album for your pictures (<xliff:g id="summary">%s</xliff:g>)</string>
+
+    <!-- Do not translate -->
+    <string name="pref_camera_upload_albumname_dialogtitle">Picasa album name</string>
+
+    <!-- Settings screen, Video quality category title -->
+    <string name="pref_camera_videoquality_category">Video quality</string>
+    
+    <!-- Default video quality setting. A numerical value. Do not translate. -->
+    <string name="pref_camera_videoquality_default">1</string>
+    
+    <!-- Settings screen, Select Video quality title -->
+    <string name="pref_camera_videoquality_title">Select video quality</string>
+    
+    <!-- Settings screen, Video quality dialog radio button choices -->
+    <string name="pref_camera_videoquality_entry_0">Low (for MMS messages)</string>
+    <string name="pref_camera_videoquality_entry_1">High (for SD card)</string>
+    
+    <!-- Settings screen, Video codec dialog title -->
+    <string name="pref_camera_videoquality_dialogtitle">Video quality</string>
+    
+    <!-- Menu item to go to the settings screen -->
+    <string name="camerasettings">Settings</string>
+
+    <!-- Text message indicating that there are no pictures or videos in a particular
+         bucket on the SD card -->
+    <string name="image_gallery_NoImageView_text">No media found.</string>
+
+    <!-- Preference title for whether the user should be prompted form confirmation when deleting images  -->
+    <string name="pref_gallery_confirm_delete_title">Confirm deletions</string>
+
+    <!-- Preference summary for whether the user should be prompted form confirmation when deleting images  -->
+    <string name="pref_gallery_confirm_delete_summary">Show confirmation before deleting a picture or video</string>
+
+   <!-- Details stuff -->
+    <!-- Do not translate -->
+    <string name="details_title_text">myvideo</string>
+    <!-- Do not translate -->
+    <string name="details_tags_text">monster</string>
+    <!-- Do not translate -->
+    <string name="details_category_label">Category:</string>
+    <!-- Do not translate -->
+    <string name="details_description_label">Description:</string>
+    <!-- Do not translate -->
+    <string name="details_publicView_text">Public</string>
+    <!-- Do not translate -->
+    <string name="details_tags_label">Tags:</string>
+    <!-- Do not translate -->
+    <string name="details_description_text">testdescription</string>
+    <!-- Do not translate -->
+    <string name="details_title_label">Title:</string>
+    <!-- Do not translate -->
+    <string name="details_save_text">Save</string>
+    <!-- Do not translate -->
+    <string name="details_language_label">Language:</string>
+    <!-- Do not translate -->
+    <string name="details_privateView_text">Private</string>
+
+    <!-- Title of Details dialog  -->
+    <string name="details_panel_title">Details</string> 
+    <!-- Label in message of Details dialog  -->
+    <string name="details_file_size">File size:</string>
+    <!-- Label in message of Details dialog  -->
+    <string name="details_image_resolution">Resolution:</string>
+    <!-- Label in message of Details dialog  -->
+    <string name="details_duration">Duration:</string>
+    <!-- Label in message of Details dialog  -->
+    <string name="details_date_taken">Date taken:</string>
+    <!-- Label in message of Details dialog  -->
+    <string name="details_frame_rate">Frame rate:</string>
+    <!-- Label in message of Details dialog  -->
+    <string name="details_bit_rate">Bit rate:</string>
+    <!-- Label in message of Details dialog  -->
+    <string name="details_codec">Codec:</string>
+    <!-- Label in message of Details dialog  -->
+    <string name="details_format">Format:</string>
+    
+    <!-- Used to format image dimensions in Details dialog. e.g. 64 x 64 -->
+    <string name="details_dimension_x">%1$d x %2$d</string>
+    <!-- Used to format short video duration in Details dialog. minutes:seconds e.g. 00:30 -->
+    <string name="details_ms">%1$02d:%2$02d</string>
+    <!-- Used to format video duration in Details dialog. hours:minutes:seconds e.g. 0:21:30 -->
+    <string name="details_hms">%1$d:%2$02d:%3$02d</string>
+    <!-- Unit of measure in the Details dialog frames per second. e.g. 20 fps -->
+    <string name="details_fps">%1$d fps</string>
+    <!-- Unit of measure in the Details dialog K bits per second. e.g. 192 Kbps -->
+    <string name="details_kbps">%1$d Kbps</string>
+    <!-- Unit of measure in the Details dialog M bits per second. e.g. 2.3 Mbps -->
+    <string name="details_mbps">%1$g Mbps</string>
+    <!-- Details dialog "OK" button. Dismisses dialog. -->
+    <string name="details_ok">OK</string>
+    
+    <!-- Text of context menu when an image is selected -->
+    <string name="context_menu_header">Picture options</string>
+    <!-- Text of context menu when a video is selected -->
+    <string name="video_context_menu_header">Video options</string>
+    <!-- Hint that appears when cropping an image with more than one face -->
+    <string name="multiface_crop_help">Tap a face to begin.</string>
+    
+    <!-- Activity title when in the image gallery to see pictures -->   
+    <string name="photos_gallery_title">Gallery</string>
+
+    <!-- Activity title when in the image gallery to select a picture -->   
+    <string name="pick_photos_gallery_title">Select picture</string>
+
+    <!-- Activity title when in the image gallery to see videos -->
+    <string name="videos_gallery_title">Gallery</string>
+
+    <!-- Activity title when in the image gallery to select a video -->
+    <string name="pick_videos_gallery_title">Select video</string>
+    
+    <!-- onscreen text indicating the progress of generating thumbnails.  Basically some number followed by the word "remaining" -->    
+    <string name="loading_progress_format_string"><xliff:g id="counter">%d</xliff:g> remaining</string>
+    
+    <!-- Displayed in the title of the dialog for things to do with a picture that
+         is to be sent to another application: -->
+    <string name="sendImage">Share picture via</string>
+
+    <!-- Displayed in the title of the dialog for things to do with a picture that
+         is to be "set as" (e.g. set as contact photo or set as wallpaper) -->
+    <string name="setImage">Set picture as</string>
+    
+    <!-- Displayed in the title of the dialog for things to do with a video that
+         is to be sent to another application. -->
+    <string name="sendVideo">Share video via</string>
+    
+    <!-- Activity label. This might show up in the activity-picker -->
+    <string name="movieviewlabel">Movies</string>
+    <!-- shown in the video player view while the video is being loaded, before it starts playing -->
+    <string name="loading_video">Loading video\u2026</string>
+ 
+    <!-- Low-memory dialog title -->
+    <string name="spaceIsLow_title">Space is low</string>
+    <!-- Low-memory dialog message -->
+    <string name="spaceIsLow_content">Your SD card is running out of space. Change the quality setting or delete items from Gallery.</string>
+    
+    <!-- Movie View Resume Playing dialog title -->
+    <string name="resume_playing_title">Resume video</string>
+    
+    <!-- Movie View Start Playing dialog title -->
+    <string name="resume_playing_message">Resume playing from %s ?</string>
+    
+    <!-- Movie View Start Playing button "Resume from bookmark" -->
+    <string name="resume_playing_resume">Resume playing</string>
+    
+    <!-- Movie View Start Playing button "Beginning" -->
+    <string name="resume_playing_restart">Start over</string>
+    
+    <!-- Title for picture frame gadget to show in list of all available gadgets -->
+    <string name="gadget_title">Picture frame</string>
+    
+    <!-- Video Camera format string for new video files. Passed to java.text.SimpleDateFormat.
+         Do not localize -->
+    <string name="video_file_name_format">"'video'-yyyy-MM-dd-HH-mm-ss"</string>
+
+    <!-- Title for the file information dialog -->
+    <string name="file_info_title">File info:</string>
+    
+    <!-- The video is execeed the size limit of a MMS, and suggest user to record a shorter length clip -->
+    <string name="video_exceed_mms_limit">The video you recorded is too large to send via MMS. Try recording a shorter length clip.</string>
+    
+</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100644
index 0000000..bad65fe
--- /dev/null
+++ b/res/values/styles.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2008 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 xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="OnScreenHintTextAppearance">
+        <item name="android:textColor">@android:color/primary_text_dark</item>
+        <item name="android:textColorHighlight">#FFFF9200</item>
+        <item name="android:textColorHint">#808080</item>
+        <item name="android:textColorLink">#5C5CFF</item>
+        <item name="android:textSize">16sp</item>
+        <item name="android:textStyle">normal</item>
+    </style>
+    <style name="OnScreenHintTextAppearance.Small">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textColor">@android:color/secondary_text_dark</item>
+    </style>
+    <style name="Animation" />
+    <style name="Animation.OnScreenHint">
+        <item name="android:windowEnterAnimation">@anim/on_screen_hint_enter</item>
+        <item name="android:windowExitAnimation">@anim/on_screen_hint_exit</item>
+    </style>
+    <style name="OnscreenActionIcon">
+        <item name="android:focusable">true</item>
+        <item name="android:clickable">true</item>
+        <item name="android:textSize">13dip</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:ellipsize">marquee</item>
+        <item name="android:shadowColor">#FF000000</item>
+        <item name="android:shadowRadius">2.0</item>
+        <item name="android:textColor">#FFF</item>
+        <item name="android:gravity">center_horizontal</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_marginLeft">30dip</item>
+        <item name="android:drawablePadding">3dip</item>
+    </style>
+</resources>
diff --git a/res/xml/camera_preferences.xml b/res/xml/camera_preferences.xml
new file mode 100644
index 0000000..4d8b7b7
--- /dev/null
+++ b/res/xml/camera_preferences.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<!-- This is a primitive example showing the different types of preferences available. -->
+<PreferenceScreen
+        xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <PreferenceCategory
+      android:title="@string/pref_camera_general_settings_category">
+
+        <CheckBoxPreference
+                android:key="pref_camera_recordlocation_key"
+                android:title="@string/pref_camera_recordlocation_title"
+                android:summary="@string/pref_camera_recordlocation_summary"
+                android:defaultValue="false"/>
+    </PreferenceCategory>
+    
+    <PreferenceCategory
+      android:title="@string/pref_camera_videoquality_category">
+        <!-- android:summary is filled in by CameraSettings -->
+        <ListPreference
+                android:key="pref_camera_videoquality_key"
+                android:defaultValue="@string/pref_camera_videoquality_default"
+                android:title="@string/pref_camera_videoquality_title"
+                android:entries="@array/pref_camera_videoquality_entries"
+                android:entryValues="@array/pref_camera_videoquality_entryvalues"
+                android:dialogTitle="@string/pref_camera_videoquality_dialogtitle" />
+    </PreferenceCategory>
+</PreferenceScreen>
diff --git a/res/xml/gadget_info.xml b/res/xml/gadget_info.xml
new file mode 100644
index 0000000..5efaddd
--- /dev/null
+++ b/res/xml/gadget_info.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<gadget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="146dip"
+    android:minHeight="146dip"
+    android:updatePeriodMillis="0"
+    android:initialLayout="@layout/photo_frame"
+    android:configure="com.android.camera.PhotoGadgetConfigure"
+    >
+</gadget-provider>
diff --git a/res/xml/gallery_preferences.xml b/res/xml/gallery_preferences.xml
new file mode 100644
index 0000000..0ddc166
--- /dev/null
+++ b/res/xml/gallery_preferences.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<!-- This is a primitive example showing the different types of preferences available. -->
+<PreferenceScreen
+        xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <PreferenceCategory
+            android:title="@string/pref_gallery_category">
+            
+        <ListPreference
+                android:key="pref_gallery_size_key"
+                android:title="@string/pref_gallery_size_title"
+                android:summary="@string/pref_gallery_size_summary"
+                android:entries="@array/pref_gallery_size_choices"
+                android:entryValues="@array/pref_gallery_size_values"
+                android:dialogTitle="@string/pref_gallery_size_dialogtitle"
+                android:defaultValue="@string/default_value_pref_gallery_size" />
+
+        <ListPreference
+                android:key="pref_gallery_sort_key"
+                android:title="@string/pref_gallery_sort_title"
+                android:summary="@string/pref_gallery_sort_summary"
+                android:entries="@array/pref_gallery_sort_choices"
+                android:entryValues="@array/pref_gallery_sort_values"
+                android:dialogTitle="@string/pref_gallery_sort_dialogtitle"
+                android:defaultValue="@string/default_value_pref_gallery_sort" />
+
+        <CheckBoxPreference
+                android:key="pref_gallery_confirm_delete_key"
+                android:title="@string/pref_gallery_confirm_delete_title"
+                android:summary="@string/pref_gallery_confirm_delete_summary"
+                android:defaultValue="true"/>
+<!--
+        <EditTextPreference
+                android:key="pref_camera_upload_albumname_key"
+                android:title="@string/pref_camera_upload_albumname_title"
+                android:summary="@string/pref_camera_upload_albumname_summary"
+                android:dialogTitle="@string/pref_camera_upload_albumname_dialogtitle"
+                android:singleLine="true" />
+-->                
+    </PreferenceCategory>
+    <PreferenceCategory
+      android:title="@string/pref_slideshow_category">
+        <ListPreference
+                android:key="pref_gallery_slideshow_interval_key"
+                android:title="@string/pref_gallery_slideshow_interval_title"
+                android:summary="@string/pref_gallery_slideshow_interval_summary"
+                android:entries="@array/pref_gallery_slideshow_interval_choices"
+                android:entryValues="@array/pref_gallery_slideshow_interval_values"
+                android:dialogTitle="@string/pref_gallery_slideshow_interval_dialogtitle"
+                android:defaultValue="@string/default_value_pref_gallery_slideshow_interval" />
+
+        <ListPreference
+                android:key="pref_gallery_slideshow_transition_key"
+                android:title="@string/pref_gallery_slideshow_transition_title"
+                android:summary="@string/pref_gallery_slideshow_transition_summary"
+                android:entries="@array/pref_gallery_slideshow_transition_choices"
+                android:entryValues="@array/pref_gallery_slideshow_transition_values"
+                android:dialogTitle="@string/pref_gallery_slideshow_transition_dialogtitle"
+                android:defaultValue="@string/default_value_pref_gallery_slideshow_transition" />
+
+        <CheckBoxPreference
+                android:key="pref_gallery_slideshow_repeat_key"
+                android:title="@string/pref_gallery_slideshow_repeat_title"
+                android:summary="@string/pref_gallery_slideshow_repeat_summary"
+                android:defaultValue="false"/>
+
+        <CheckBoxPreference
+                android:key="pref_gallery_slideshow_shuffle_key"
+                android:title="@string/pref_gallery_slideshow_shuffle_title"
+                android:summary="@string/pref_gallery_slideshow_shuffle_summary"
+                android:defaultValue="false"/>
+
+    </PreferenceCategory>
+</PreferenceScreen>
diff --git a/src/com/android/camera/ActionMenuButton.java b/src/com/android/camera/ActionMenuButton.java
new file mode 100644
index 0000000..65e1f0e
--- /dev/null
+++ b/src/com/android/camera/ActionMenuButton.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2008 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.camera;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.text.Layout;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+/**
+ * TextView that draws a bubble behind the text. We cannot use a LineBackgroundSpan
+ * because we want to make the bubble taller than the text and TextView's clip is
+ * too aggressive.
+ */
+public class ActionMenuButton extends TextView {
+    private static final float CORNER_RADIUS = 8.0f;
+    private static final float PADDING_H = 5.0f;
+    private static final float PADDING_V = 1.0f;
+
+    private final RectF mRect = new RectF();
+    private Paint mPaint;
+
+    public ActionMenuButton(Context context) {
+        super(context);
+        init();
+    }
+
+    public ActionMenuButton(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public ActionMenuButton(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init();
+    }
+
+    private void init() {
+        setFocusable(true);
+
+        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mPaint.setColor(getContext().getResources().getColor(R.color.bubble_dark_background));
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        invalidate();
+        super.drawableStateChanged();
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        final Layout layout = getLayout();
+        final RectF rect = mRect;
+        final int left = getCompoundPaddingLeft();
+        final int top = getExtendedPaddingTop();
+
+        rect.set(left + layout.getLineLeft(0) - PADDING_H,
+                top + layout.getLineTop(0) - PADDING_V,
+                Math.min(left + layout.getLineRight(0) + PADDING_H, mScrollX + mRight - mLeft),
+                top + layout.getLineBottom(0) + PADDING_V);
+        canvas.drawRoundRect(rect, CORNER_RADIUS, CORNER_RADIUS, mPaint);
+
+        super.draw(canvas);
+    }
+}
diff --git a/src/com/android/camera/Camera.java b/src/com/android/camera/Camera.java
new file mode 100644
index 0000000..da4c41c
--- /dev/null
+++ b/src/com/android/camera/Camera.java
@@ -0,0 +1,1861 @@
+/*
+ * Copyright (C) 2007 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.camera;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.TransitionDrawable;
+import android.hardware.Camera.PictureCallback;
+import android.hardware.Camera.Size;
+import android.location.Location;
+import android.location.LocationManager;
+import android.location.LocationProvider;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.ToneGenerator;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.preference.PreferenceManager;
+import android.provider.MediaStore;
+import android.text.format.DateFormat;
+import android.util.Config;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MenuItem.OnMenuItemClickListener;
+import android.view.OrientationEventListener;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+public class Camera extends Activity implements View.OnClickListener,
+    ShutterButton.OnShutterButtonListener, SurfaceHolder.Callback {
+
+    private static final String TAG = "camera";
+
+    private static final boolean DEBUG = false;
+    private static final boolean DEBUG_TIME_OPERATIONS = DEBUG && false;
+
+    private static final int CROP_MSG = 1;
+    private static final int KEEP = 2;
+    private static final int RESTART_PREVIEW = 3;
+    private static final int CLEAR_SCREEN_DELAY = 4;
+
+    private static final int SCREEN_DELAY = 2 * 60 * 1000;
+    private static final int FOCUS_BEEP_VOLUME = 100;
+
+    public static final int MENU_SWITCH_TO_VIDEO = 0;
+    public static final int MENU_SWITCH_TO_CAMERA = 1;
+    public static final int MENU_FLASH_SETTING = 2;
+    public static final int MENU_FLASH_AUTO = 3;
+    public static final int MENU_FLASH_ON = 4;
+    public static final int MENU_FLASH_OFF = 5;
+    public static final int MENU_SETTINGS = 6;
+    public static final int MENU_GALLERY_PHOTOS = 7;
+    public static final int MENU_GALLERY_VIDEOS = 8;
+    public static final int MENU_SAVE_SELECT_PHOTOS = 30;
+    public static final int MENU_SAVE_NEW_PHOTO = 31;
+    public static final int MENU_SAVE_GALLERY_PHOTO = 34;
+    public static final int MENU_SAVE_GALLERY_VIDEO_PHOTO = 35;
+    public static final int MENU_SAVE_CAMERA_DONE = 36;
+    public static final int MENU_SAVE_CAMERA_VIDEO_DONE = 37;
+
+    private Toast mToast;
+    private OrientationEventListener mOrientationListener;
+    private int mLastOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
+    private SharedPreferences mPreferences;
+
+    private static final int IDLE = 1;
+    private static final int SNAPSHOT_IN_PROGRESS = 2;
+    private static final int SNAPSHOT_COMPLETED = 3;
+
+    private int mStatus = IDLE;
+    private static final String sTempCropFilename = "crop-temp";
+
+    private android.hardware.Camera mCameraDevice;
+    private android.hardware.Camera.Parameters mParameters;
+    private VideoPreview mSurfaceView;
+    private SurfaceHolder mSurfaceHolder = null;
+    private View mBlackout = null;
+
+    private int mOriginalViewFinderWidth, mOriginalViewFinderHeight;
+    private int mViewFinderWidth, mViewFinderHeight;
+    private boolean mPreviewing = false;
+
+    private MediaPlayer mClickSound;
+
+    private Capturer mCaptureObject;
+    private ImageCapture mImageCapture = null;
+
+    private boolean mPausing = false;
+
+    private boolean mIsFocusing = false;
+    private boolean mIsFocused = false;
+    private boolean mIsFocusButtonPressed = false;
+    private boolean mCaptureOnFocus = false;
+
+    private static ContentResolver mContentResolver;
+    private boolean mDidRegister = false;
+
+    private ArrayList<MenuItem> mGalleryItems = new ArrayList<MenuItem>();
+
+    private boolean mMenuSelectionMade;
+
+    private ImageView mLastPictureButton;
+    private LayerDrawable mVignette;
+    private Animation mShowLastPictureButtonAnimation = new AlphaAnimation(0F, 1F);
+    private boolean mShouldShowLastPictureButton;
+    private TransitionDrawable mThumbnailTransition;
+    private Drawable[] mThumbnails;
+    private boolean mShouldTransitionThumbnails;
+    private Uri mLastPictureUri;
+    private Bitmap mLastPictureThumb;
+    private LocationManager mLocationManager = null;
+
+    private ShutterButton mShutterButton;
+
+    private Animation mFocusBlinkAnimation;
+    private View mFocusIndicator;
+    private ToneGenerator mFocusToneGenerator;
+
+
+    private ShutterCallback mShutterCallback = new ShutterCallback();
+    private RawPictureCallback mRawPictureCallback = new RawPictureCallback();
+    private AutoFocusCallback mAutoFocusCallback = new AutoFocusCallback();
+    private long mFocusStartTime;
+    private long mFocusCallbackTime;
+    private long mCaptureStartTime;
+    private long mShutterCallbackTime;
+    private long mRawPictureCallbackTime;
+    private int mPicturesRemaining;
+
+    private boolean mKeepAndRestartPreview;
+
+    // mPostCaptureAlert is non-null only if isImageCaptureIntent() is true.
+    private View mPostCaptureAlert;
+
+
+    private Handler mHandler = new MainHandler();
+    private ProgressDialog mSavingProgress;
+
+    private interface Capturer {
+        Uri getLastCaptureUri();
+        void onSnap();
+        void dismissFreezeFrame(boolean keep);
+        void cancelSave();
+        void cancelAutoDismiss();
+        void setDone(boolean wait);
+    }
+
+    private void cancelSavingNotification() {
+        if (mToast != null) {
+            mToast.cancel();
+            mToast = null;
+        }
+    }
+
+    /** This Handler is used to post message back onto the main thread of the application */
+    private class MainHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case KEEP: {
+                    keep();
+                    if (mSavingProgress != null) {
+                        mSavingProgress.cancel();
+                        mSavingProgress = null;
+                    }
+
+                    mKeepAndRestartPreview = true;
+
+                    if (msg.obj != null) {
+                        mHandler.post((Runnable)msg.obj);
+                    }
+                    break;
+                }
+
+                case RESTART_PREVIEW: {
+                    if (mStatus == SNAPSHOT_IN_PROGRESS) {
+                        // We are still in the processing of taking the picture, wait.
+                        // This is is strange.  Why are we polling?
+                        // TODO remove polling
+                        mHandler.sendEmptyMessageDelayed(RESTART_PREVIEW, 100);
+                    } else if (mStatus == SNAPSHOT_COMPLETED){
+                        mCaptureObject.dismissFreezeFrame(true);
+                        hidePostCaptureAlert();
+                    }
+                    break;
+                }
+
+                case CLEAR_SCREEN_DELAY: {
+                    getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+                    break;
+                }
+            }
+        }
+    };
+
+    LocationListener [] mLocationListeners = new LocationListener[] {
+            new LocationListener(LocationManager.GPS_PROVIDER),
+            new LocationListener(LocationManager.NETWORK_PROVIDER)
+    };
+
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
+                // SD card available
+                updateStorageHint(calculatePicturesRemaining());
+            } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED) ||
+                    action.equals(Intent.ACTION_MEDIA_CHECKING)) {
+                // SD card unavailable
+                mPicturesRemaining = MenuHelper.NO_STORAGE_ERROR;
+                updateStorageHint(mPicturesRemaining);
+            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
+                Toast.makeText(Camera.this, getResources().getString(R.string.wait), 5000);
+            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
+                updateStorageHint();
+            }
+        }
+    };
+
+    private class LocationListener implements android.location.LocationListener {
+        Location mLastLocation;
+        boolean mValid = false;
+        String mProvider;
+
+        public LocationListener(String provider) {
+            mProvider = provider;
+            mLastLocation = new Location(mProvider);
+        }
+
+        public void onLocationChanged(Location newLocation) {
+            if (newLocation.getLatitude() == 0.0 && newLocation.getLongitude() == 0.0) {
+                // Hack to filter out 0.0,0.0 locations
+                return;
+            }
+            mLastLocation.set(newLocation);
+            mValid = true;
+        }
+
+        public void onProviderEnabled(String provider) {
+        }
+
+        public void onProviderDisabled(String provider) {
+            mValid = false;
+        }
+
+        public void onStatusChanged(String provider, int status, Bundle extras) {
+            if (status == LocationProvider.OUT_OF_SERVICE) {
+                mValid = false;
+            }
+        }
+
+        public Location current() {
+            return mValid ? mLastLocation : null;
+        }
+    };
+
+    private boolean mImageSavingItem = false;
+
+    private final class ShutterCallback implements android.hardware.Camera.ShutterCallback {
+        public void onShutter() {
+            if (DEBUG_TIME_OPERATIONS) {
+                mShutterCallbackTime = System.currentTimeMillis();
+                Log.v(TAG, "Shutter lag was " + (mShutterCallbackTime - mCaptureStartTime) + " ms.");
+            }
+            if (mClickSound != null) {
+                mClickSound.start();
+            }
+            mBlackout.setVisibility(View.VISIBLE);
+
+            Size pictureSize = mParameters.getPictureSize();
+            // Resize the SurfaceView to the aspect-ratio of the still image
+            // and so that we can see the full image that was taken.
+            mSurfaceView.setAspectRatio(pictureSize.width, pictureSize.height);
+        }
+    };
+
+    private final class RawPictureCallback implements PictureCallback {
+        public void onPictureTaken(byte [] rawData, android.hardware.Camera camera) {
+            if (Config.LOGV)
+                Log.v(TAG, "got RawPictureCallback...");
+            mRawPictureCallbackTime = System.currentTimeMillis();
+            if (DEBUG_TIME_OPERATIONS) {
+                Log.v(TAG, (mRawPictureCallbackTime - mShutterCallbackTime) + "ms elapsed between" +
+                        " ShutterCallback and RawPictureCallback.");
+            }
+            mBlackout.setVisibility(View.GONE);
+        }
+    };
+
+    private final class JpegPictureCallback implements PictureCallback {
+        Location mLocation;
+
+        public JpegPictureCallback(Location loc) {
+            mLocation = loc;
+        }
+
+        public void onPictureTaken(byte [] jpegData, android.hardware.Camera camera) {
+            if (Config.LOGV)
+                Log.v(TAG, "got JpegPictureCallback...");
+
+            if (DEBUG_TIME_OPERATIONS) {
+                long mJpegPictureCallback = System.currentTimeMillis();
+                Log.v(TAG, (mJpegPictureCallback - mRawPictureCallbackTime) + "ms elapsed between" +
+                        " RawPictureCallback and JpegPictureCallback.");
+            }
+
+            mImageCapture.storeImage(jpegData, camera, mLocation);
+
+            mStatus = SNAPSHOT_COMPLETED;
+
+            if (mKeepAndRestartPreview) {
+                long delay = 1500 - (System.currentTimeMillis() - mRawPictureCallbackTime);
+                mHandler.sendEmptyMessageDelayed(RESTART_PREVIEW, Math.max(delay, 0));
+            }
+        }
+    };
+
+    private final class AutoFocusCallback implements android.hardware.Camera.AutoFocusCallback {
+        public void onAutoFocus(boolean focused, android.hardware.Camera camera) {
+            if (DEBUG_TIME_OPERATIONS) {
+                mFocusCallbackTime = System.currentTimeMillis();
+                Log.v(TAG, "Auto focus took " + (mFocusCallbackTime - mFocusStartTime) + " ms.");
+            }
+
+            mIsFocusing = false;
+            mIsFocused = focused;
+            if (focused) {
+                if (mCaptureOnFocus && mCaptureObject != null) {
+                    // No need to play the AF sound if we're about to play the shutter sound
+                    mCaptureObject.onSnap();
+                    clearFocus();
+                } else {
+                    ToneGenerator tg = mFocusToneGenerator;
+                    if (tg != null)
+                       tg.startTone(ToneGenerator.TONE_PROP_BEEP2);
+                }
+                mCaptureOnFocus = false;
+            }
+            updateFocusIndicator();
+        }
+    };
+
+    private class ImageCapture implements Capturer {
+
+        private boolean mCancel = false;
+        private boolean mCapturing = false;
+
+        private Uri mLastContentUri;
+        private ImageManager.IAddImage_cancelable mAddImageCancelable;
+
+        Bitmap mCaptureOnlyBitmap;
+
+        /** These member variables are used for various debug timings */
+        private long mThreadTimeStart;
+        private long mThreadTimeEnd;
+        private long mWallTimeStart;
+        private long mWallTimeEnd;
+
+
+        public ImageCapture() {
+        }
+
+        /**
+         * This method sets whether or not we are capturing a picture. This method must be called
+         * with the ImageCapture.this lock held.
+         */
+        public void setCapturingLocked(boolean capturing) {
+            mCapturing = capturing;
+        }
+
+        /*
+         * Tell the ImageCapture thread to exit when possible.
+         */
+        public void setDone(boolean wait) {
+        }
+
+        /*
+         * Tell the image capture thread to not "dismiss" the current
+         * capture when the current image is stored, etc.
+         */
+        public void cancelAutoDismiss() {
+        }
+
+        public void dismissFreezeFrame(boolean keep) {
+            if (keep) {
+                cancelSavingNotification();
+            } else {
+                Toast.makeText(Camera.this, R.string.camera_tossing, Toast.LENGTH_SHORT).show();
+            }
+
+            if (mStatus == SNAPSHOT_IN_PROGRESS) {
+                // If we are still in the process of taking a picture, then just post a message.
+                mHandler.sendEmptyMessage(RESTART_PREVIEW);
+            } else {
+                restartPreview();
+            }
+        }
+
+        private void startTiming() {
+            mWallTimeStart = SystemClock.elapsedRealtime();
+            mThreadTimeStart = Debug.threadCpuTimeNanos();
+        }
+
+        private void stopTiming() {
+            mThreadTimeEnd = Debug.threadCpuTimeNanos();
+            mWallTimeEnd = SystemClock.elapsedRealtime();
+        }
+
+        private void storeImage(byte[] data, Location loc) {
+            try {
+                if (DEBUG_TIME_OPERATIONS) {
+                    startTiming();
+                }
+                long dateTaken = System.currentTimeMillis();
+                String name = createName(dateTaken) + ".jpg";
+                mLastContentUri = ImageManager.instance().addImage(
+                        Camera.this,
+                        mContentResolver,
+                        name,
+                        "",
+                        dateTaken,
+                        // location for the database goes here
+                        loc,
+                        0,   // the dsp will use the right orientation so don't "double set it"
+                        ImageManager.CAMERA_IMAGE_BUCKET_NAME,
+                        name);
+
+                if (mLastContentUri == null) {
+                    // this means we got an error
+                    mCancel = true;
+                }
+                if (!mCancel) {
+                    mAddImageCancelable = ImageManager.instance().storeImage(mLastContentUri,
+                            Camera.this, mContentResolver, 0, null, data);
+                    mAddImageCancelable.get();
+                    mAddImageCancelable = null;
+                }
+
+                if (DEBUG_TIME_OPERATIONS) {
+                    stopTiming();
+                    Log.d(TAG, "Storing image took " + (mWallTimeEnd - mWallTimeStart) + " ms. " +
+                            "Thread time was " + ((mThreadTimeEnd - mThreadTimeStart) / 1000000) +
+                            " ms.");
+                }
+            } catch (Exception ex) {
+                Log.e(TAG, "Exception while compressing image.", ex);
+            }
+        }
+
+        public void storeImage(byte[] data, android.hardware.Camera camera, Location loc) {
+            boolean captureOnly = isImageCaptureIntent();
+
+            if (!captureOnly) {
+                storeImage(data, loc);
+                sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", mLastContentUri));
+                setLastPictureThumb(data, mCaptureObject.getLastCaptureUri());
+                dismissFreezeFrame(true);
+            } else {
+                BitmapFactory.Options options = new BitmapFactory.Options();
+                options.inSampleSize = 4;
+
+                if (DEBUG_TIME_OPERATIONS) {
+                    startTiming();
+                }
+
+                mCaptureOnlyBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);
+
+                if (DEBUG_TIME_OPERATIONS) {
+                    stopTiming();
+                    Log.d(TAG, "Decoded mCaptureOnly bitmap (" + mCaptureOnlyBitmap.getWidth() +
+                            "x" + mCaptureOnlyBitmap.getHeight() + " ) in " +
+                            (mWallTimeEnd - mWallTimeStart) + " ms. Thread time was " +
+                            ((mThreadTimeEnd - mThreadTimeStart) / 1000000) + " ms.");
+                }
+
+                showPostCaptureAlert();
+                cancelAutomaticPreviewRestart();
+            }
+
+            mCapturing = false;
+            if (mPausing) {
+                closeCamera();
+            }
+        }
+
+        /*
+         * Tells the image capture thread to abort the capture of the
+         * current image.
+         */
+        public void cancelSave() {
+            if (!mCapturing) {
+                return;
+            }
+
+            mCancel = true;
+
+            if (mAddImageCancelable != null) {
+                mAddImageCancelable.cancel();
+            }
+            dismissFreezeFrame(false);
+        }
+
+        /*
+         * Initiate the capture of an image.
+         */
+        public void initiate(boolean captureOnly) {
+            if (mCameraDevice == null) {
+                return;
+            }
+
+            mCancel = false;
+            mCapturing = true;
+
+            capture(captureOnly);
+        }
+
+        public Uri getLastCaptureUri() {
+            return mLastContentUri;
+        }
+
+        public Bitmap getLastBitmap() {
+            return mCaptureOnlyBitmap;
+        }
+
+        private void capture(boolean captureOnly) {
+            mPreviewing = false;
+            mCaptureOnlyBitmap = null;
+
+            final int latchedOrientation = ImageManager.roundOrientation(mLastOrientation + 90);
+
+            Boolean recordLocation = mPreferences.getBoolean("pref_camera_recordlocation_key", false);
+            Location loc = recordLocation ? getCurrentLocation() : null;
+            // Quality 75 has visible artifacts, and quality 90 looks great but the files begin to
+            // get large. 85 is a good compromise between the two.
+            mParameters.set("jpeg-quality", 85);
+            mParameters.set("rotation", latchedOrientation);
+
+            mParameters.remove("gps-latitude");
+            mParameters.remove("gps-longitude");
+            mParameters.remove("gps-altitude");
+            mParameters.remove("gps-timestamp");
+
+            if (loc != null) {
+                double lat = loc.getLatitude();
+                double lon = loc.getLongitude();
+                boolean hasLatLon = (lat != 0.0d) || (lon != 0.0d);
+
+                if (hasLatLon) {
+                    String latString = String.valueOf(lat);
+                    String lonString = String.valueOf(lon);
+                    mParameters.set("gps-latitude",  latString);
+                    mParameters.set("gps-longitude", lonString);
+                    if (loc.hasAltitude())
+                        mParameters.set("gps-altitude",  String.valueOf(loc.getAltitude()));
+                    if (loc.getTime() != 0) {
+                        // Location.getTime() is UTC in milliseconds.
+                        // gps-timestamp is UTC in seconds.
+                        long utcTimeSeconds = loc.getTime() / 1000;
+                        mParameters.set("gps-timestamp", String.valueOf(utcTimeSeconds));
+                    }
+                } else {
+                    loc = null;
+                }
+            }
+
+            mCameraDevice.setParameters(mParameters);
+
+            mCameraDevice.takePicture(mShutterCallback, mRawPictureCallback, new JpegPictureCallback(loc));
+            // Prepare the sound to play in shutter callback.
+            if (mClickSound != null) {
+                mClickSound.seekTo(0);
+            }
+        }
+
+        public void onSnap() {
+            if (DEBUG_TIME_OPERATIONS) mCaptureStartTime = System.currentTimeMillis();
+
+            // If we are already in the middle of taking a snapshot then we should just save
+            // the image after we have returned from the camera service.
+            if (mStatus == SNAPSHOT_IN_PROGRESS || mStatus == SNAPSHOT_COMPLETED) {
+                mKeepAndRestartPreview = true;
+                mHandler.sendEmptyMessage(RESTART_PREVIEW);
+                return;
+            }
+
+            // Don't check the filesystem here, we can't afford the latency. Instead, check the
+            // cached value which was calculated when the preview was restarted.
+            if (mPicturesRemaining < 1) {
+                updateStorageHint(mPicturesRemaining);
+                return;
+            }
+
+            mStatus = SNAPSHOT_IN_PROGRESS;
+
+            mKeepAndRestartPreview = true;
+
+            boolean getContentAction = isImageCaptureIntent();
+            if (getContentAction) {
+                mImageCapture.initiate(true);
+            } else {
+                mImageCapture.initiate(false);
+            }
+        }
+    }
+
+    private void setLastPictureThumb(byte[] data, Uri uri) {
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inSampleSize = 16;
+
+        Bitmap lastPictureThumb = BitmapFactory.decodeByteArray(data, 0, data.length, options);
+
+        setLastPictureThumb(lastPictureThumb, uri);
+    }
+
+    private void setLastPictureThumb(Bitmap lastPictureThumb, Uri uri) {
+
+        final int PADDING_WIDTH = 2;
+        final int PADDING_HEIGHT = 2;
+        LayoutParams layoutParams = mLastPictureButton.getLayoutParams();
+        // Make the mini-thumbnail size smaller than the button size so that the image corners
+        // don't peek out from the rounded corners of the frame_thumbnail graphic:
+        final int miniThumbWidth = layoutParams.width - 2 * PADDING_WIDTH;
+        final int miniThumbHeight = layoutParams.height - 2 * PADDING_HEIGHT;
+
+        lastPictureThumb = ImageManager.extractMiniThumb(lastPictureThumb,
+                miniThumbWidth, miniThumbHeight);
+
+        Drawable[] vignetteLayers = new Drawable[2];
+        vignetteLayers[1] = getResources().getDrawable(R.drawable.frame_thumbnail);
+        if (mThumbnails == null) {
+            mThumbnails = new Drawable[2];
+            mThumbnails[1] = new BitmapDrawable(lastPictureThumb);
+            vignetteLayers[0] = mThumbnails[1];
+        } else {
+            mThumbnails[0] = mThumbnails[1];
+            mThumbnails[1] = new BitmapDrawable(lastPictureThumb);
+            mThumbnailTransition = new TransitionDrawable(mThumbnails);
+            mShouldTransitionThumbnails = true;
+            vignetteLayers[0] = mThumbnailTransition;
+        }
+
+        mVignette = new LayerDrawable(vignetteLayers);
+        mVignette.setLayerInset(0, PADDING_WIDTH, PADDING_HEIGHT,
+                PADDING_WIDTH, PADDING_HEIGHT);
+        mLastPictureButton.setImageDrawable(mVignette);
+
+        if (mLastPictureButton.getVisibility() != View.VISIBLE) {
+            mShouldShowLastPictureButton = true;
+        }
+        mLastPictureThumb = lastPictureThumb;
+        mLastPictureUri = uri;
+    }
+
+    static private String createName(long dateTaken) {
+        return DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken).toString();
+    }
+
+    static public Matrix GetDisplayMatrix(Bitmap b, ImageView v) {
+        Matrix m = new Matrix();
+        float bw = (float)b.getWidth();
+        float bh = (float)b.getHeight();
+        float vw = (float)v.getWidth();
+        float vh = (float)v.getHeight();
+        float scale, x, y;
+        if (bw*vh > vw*bh) {
+            scale = vh / bh;
+            x = (vw - scale*bw)*0.5F;
+            y = 0;
+        } else {
+            scale = vw / bw;
+            x = 0;
+            y = (vh - scale*bh)*0.5F;
+        }
+        m.setScale(scale, scale, 0.5F, 0.5F);
+        m.postTranslate(x, y);
+        return m;
+    }
+
+    private void postAfterKeep(final Runnable r) {
+        Resources res = getResources();
+
+        if (mSavingProgress != null) {
+            mSavingProgress = ProgressDialog.show(this, res.getString(R.string.savingImage),
+                    res.getString(R.string.wait));
+        }
+
+        Message msg = mHandler.obtainMessage(KEEP);
+        msg.obj = r;
+        msg.sendToTarget();
+    }
+
+    /** Called with the activity is first created. */
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        // To reduce startup time, we open camera device in another thread.
+        // We make sure the camera is opened at the end of onCreate.
+        Thread openCameraThread = new Thread(new Runnable() {
+            public void run() {
+                mCameraDevice = android.hardware.Camera.open();
+            }
+        });
+        openCameraThread.start();
+
+        // To reduce startup time, we run some service creation code in another thread.
+        // We make sure the services are loaded at the end of onCreate().
+        Thread loadServiceThread = new Thread(new Runnable() {
+            public void run() {
+                mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
+                mOrientationListener = new OrientationEventListener(Camera.this) {
+                    public void onOrientationChanged(int orientation) {
+                        mLastOrientation = orientation;
+                    }
+                };
+            }
+        });
+        loadServiceThread.start();
+
+        mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+        mContentResolver = getContentResolver();
+
+        Window win = getWindow();
+        win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        setContentView(R.layout.camera);
+
+        mSurfaceView = (VideoPreview) findViewById(R.id.camera_preview);
+
+        // don't set mSurfaceHolder here. We have it set ONLY within
+        // surfaceCreated / surfaceDestroyed, other parts of the code
+        // assume that when it is set, the surface is also set.
+        SurfaceHolder holder = mSurfaceView.getHolder();
+        holder.addCallback(this);
+        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+
+        mBlackout = findViewById(R.id.blackout);
+
+        if (!isImageCaptureIntent())  {
+            mLastPictureButton = (ImageView) findViewById(R.id.last_picture_button);
+            mLastPictureButton.setOnClickListener(this);
+            loadLastThumb();
+        }
+
+        mShutterButton = (ShutterButton) findViewById(R.id.shutter_button);
+        mShutterButton.setOnShutterButtonListener(this);
+
+        try {
+            mClickSound = new MediaPlayer();
+            AssetFileDescriptor afd = getResources().openRawResourceFd(R.raw.camera_click);
+
+            mClickSound.setDataSource(afd.getFileDescriptor(),
+                             afd.getStartOffset(),
+                             afd.getLength());
+
+            if (mClickSound != null) {
+                mClickSound.setAudioStreamType(AudioManager.STREAM_ALARM);
+                mClickSound.prepare();
+            }
+        } catch (Exception ex) {
+            Log.w(TAG, "Couldn't create click sound", ex);
+        }
+
+        mFocusIndicator = findViewById(R.id.focus_indicator);
+        mFocusBlinkAnimation = AnimationUtils.loadAnimation(this, R.anim.auto_focus_blink);
+        mFocusBlinkAnimation.setRepeatCount(Animation.INFINITE);
+        mFocusBlinkAnimation.setRepeatMode(Animation.REVERSE);
+
+        // We load the post_picture_panel layout only if it is needed.
+        if (isImageCaptureIntent()) {
+            ViewGroup cameraView = (ViewGroup)findViewById(R.id.camera);
+            getLayoutInflater().inflate(R.layout.post_picture_panel,
+                                        cameraView);
+            mPostCaptureAlert = findViewById(R.id.post_picture_panel);
+        }
+
+        // Make sure the services are loaded.
+        try {
+            openCameraThread.join();
+            loadServiceThread.join();
+        } catch (InterruptedException ex) {
+        }
+
+        ImageManager.ensureOSXCompatibleFolder();
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        Thread t = new Thread(new Runnable() {
+            public void run() {
+                final boolean storageOK = calculatePicturesRemaining() > 0;
+
+                if (!storageOK) {
+                    mHandler.post(new Runnable() {
+                        public void run() {
+                            updateStorageHint(mPicturesRemaining);
+                        }
+                    });
+                }
+            }
+        });
+        t.start();
+    }
+
+    public void onClick(View v) {
+        switch (v.getId()) {
+        case R.id.last_picture_button:
+            viewLastImage();
+            break;
+        case R.id.attach:
+            doAttach();
+            break;
+        case R.id.cancel:
+            doCancel();
+        }
+    }
+
+    private void doAttach() {
+        Bitmap bitmap = mImageCapture.getLastBitmap();
+        mCaptureObject.setDone(true);
+
+        String cropValue = null;
+        Uri saveUri = null;
+
+        Bundle myExtras = getIntent().getExtras();
+        if (myExtras != null) {
+            saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
+            cropValue = myExtras.getString("crop");
+        }
+
+
+        if (cropValue == null) {
+            /*
+             * First handle the no crop case -- just return the value.  If the caller
+             * specifies a "save uri" then write the data to it's stream.  Otherwise,
+             * pass back a scaled down version of the bitmap directly in the extras.
+             */
+            if (saveUri != null) {
+                OutputStream outputStream = null;
+                try {
+                    outputStream = mContentResolver.openOutputStream(saveUri);
+                    bitmap.compress(Bitmap.CompressFormat.JPEG, 75, outputStream);
+                    outputStream.close();
+
+                    setResult(RESULT_OK);
+                    finish();
+                } catch (IOException ex) {
+                    //
+                } finally {
+                    if (outputStream != null) {
+                        try {
+                            outputStream.close();
+                        } catch (IOException ex) {
+
+                        }
+                    }
+                }
+            } else {
+                float scale = .5F;
+                Matrix m = new Matrix();
+                m.setScale(scale, scale);
+
+                bitmap = Bitmap.createBitmap(bitmap, 0, 0,
+                        bitmap.getWidth(),
+                        bitmap.getHeight(),
+                        m, true);
+
+                setResult(RESULT_OK, new Intent("inline-data").putExtra("data", bitmap));
+                finish();
+            }
+        }
+        else {
+            /*
+             * Save the image to a temp file and invoke the cropper
+             */
+            Uri tempUri = null;
+            FileOutputStream tempStream = null;
+            try {
+                File path = getFileStreamPath(sTempCropFilename);
+                path.delete();
+                tempStream = openFileOutput(sTempCropFilename, 0);
+                bitmap.compress(Bitmap.CompressFormat.JPEG, 75, tempStream);
+                tempStream.close();
+                tempUri = Uri.fromFile(path);
+            } catch (FileNotFoundException ex) {
+                setResult(Activity.RESULT_CANCELED);
+                finish();
+                return;
+            } catch (IOException ex) {
+                setResult(Activity.RESULT_CANCELED);
+                finish();
+                return;
+            } finally {
+                if (tempStream != null) {
+                    try {
+                        tempStream.close();
+                    } catch (IOException ex) {
+
+                    }
+                }
+            }
+
+            Bundle newExtras = new Bundle();
+            if (cropValue.equals("circle"))
+                newExtras.putString("circleCrop", "true");
+            if (saveUri != null)
+                newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, saveUri);
+            else
+                newExtras.putBoolean("return-data", true);
+
+            Intent cropIntent = new Intent();
+            cropIntent.setClass(Camera.this, CropImage.class);
+            cropIntent.setData(tempUri);
+            cropIntent.putExtras(newExtras);
+
+            startActivityForResult(cropIntent, CROP_MSG);
+        }
+    }
+
+    private void doCancel() {
+        setResult(RESULT_CANCELED, new Intent());
+        finish();
+    }
+
+    public void onShutterButtonFocus(ShutterButton button, boolean pressed) {
+        switch (button.getId()) {
+            case R.id.shutter_button:
+                doFocus(pressed);
+                break;
+        }
+    }
+
+    public void onShutterButtonClick(ShutterButton button) {
+        switch (button.getId()) {
+            case R.id.shutter_button:
+                doSnap(false);
+                break;
+        }
+    }
+
+    private void updateStorageHint() {
+      updateStorageHint(MenuHelper.calculatePicturesRemaining());
+    }
+
+    private OnScreenHint mStorageHint;
+
+    private void updateStorageHint(int remaining) {
+        String noStorageText = null;
+
+        if (remaining == MenuHelper.NO_STORAGE_ERROR) {
+            String state = Environment.getExternalStorageState();
+            if (state == Environment.MEDIA_CHECKING) {
+                noStorageText = getString(R.string.preparing_sd);
+            } else {
+                noStorageText = getString(R.string.no_storage);
+            }
+        } else if (remaining < 1) {
+            noStorageText = getString(R.string.not_enough_space);
+        }
+
+        if (noStorageText != null) {
+            if (mStorageHint == null) {
+                mStorageHint = OnScreenHint.makeText(this, noStorageText);
+            } else {
+                mStorageHint.setText(noStorageText);
+            }
+            mStorageHint.show();
+        } else if (mStorageHint != null) {
+            mStorageHint.cancel();
+            mStorageHint = null;
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
+
+        mPausing = false;
+        mOrientationListener.enable();
+
+        // install an intent filter to receive SD card related events.
+        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING);
+        intentFilter.addDataScheme("file");
+        registerReceiver(mReceiver, intentFilter);
+        mDidRegister = true;
+
+        mImageCapture = new ImageCapture();
+
+        restartPreview();
+
+        if (mPreferences.getBoolean("pref_camera_recordlocation_key", false))
+            startReceivingLocationUpdates();
+
+        updateFocusIndicator();
+
+        try {
+            mFocusToneGenerator = new ToneGenerator(AudioManager.STREAM_SYSTEM, FOCUS_BEEP_VOLUME);
+        } catch (RuntimeException e) {
+            Log.w(TAG, "Exception caught while creating local tone generator: " + e);
+            mFocusToneGenerator = null;
+        }
+
+        mBlackout.setVisibility(View.GONE);
+    }
+
+    private ImageManager.DataLocation dataLocation() {
+        return ImageManager.DataLocation.EXTERNAL;
+    }
+
+    private static final int BUFSIZE = 4096;
+
+    // Stores the thumbnail and URI of last-picture-taken to SD card, so we can
+    // load it the next time the Camera app starts.
+    private void storeLastThumb() {
+        if (mLastPictureUri != null && mLastPictureThumb != null) {
+            try {
+                FileOutputStream f = new FileOutputStream(ImageManager.getLastThumbPath());
+                try {
+                    BufferedOutputStream b = new BufferedOutputStream(f, BUFSIZE);
+                    try {
+                        DataOutputStream d = new DataOutputStream(b);
+                        try {
+                            d.writeUTF(mLastPictureUri.toString());
+                            mLastPictureThumb.compress(Bitmap.CompressFormat.PNG, 100, d);
+                        } finally {
+                            d.close();
+                            b = null;
+                            f = null;
+                        }
+                    } finally {
+                        if (b != null) {
+                            b.close();
+                            f = null;
+                        }
+                    }
+                } finally {
+                    if (f != null) {
+                        f.close();
+                    }
+                }
+            } catch (IOException e) {
+            }
+        }
+    }
+
+    // Loads the thumbnail and URI of last-picture-taken from SD card.
+    private void loadLastThumb() {
+        try {
+            FileInputStream f = new FileInputStream(ImageManager.getLastThumbPath());
+            try {
+                BufferedInputStream b = new BufferedInputStream(f, BUFSIZE);
+                try {
+                    DataInputStream d = new DataInputStream(b);
+                    try {
+                        Uri lastUri = Uri.parse(d.readUTF());
+                        Bitmap lastThumb = BitmapFactory.decodeStream(d);
+                        setLastPictureThumb(lastThumb, lastUri);
+                    } finally {
+                        d.close();
+                        b = null;
+                        f = null;
+                    }
+                } finally {
+                    if (b != null) {
+                        b.close();
+                        f = null;
+                    }
+                }
+            } finally {
+                if (f != null) {
+                    f.close();
+                }
+            }
+        } catch (IOException e) {
+        }
+    }
+
+    @Override
+    public void onStop() {
+        keep();
+        stopPreview();
+        closeCamera();
+        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
+        super.onStop();
+    }
+
+    @Override
+    protected void onPause() {
+        keep();
+
+        mPausing = true;
+        mOrientationListener.disable();
+
+        stopPreview();
+
+        if (!mImageCapture.mCapturing) {
+            closeCamera();
+        }
+        if (mDidRegister) {
+            unregisterReceiver(mReceiver);
+            mDidRegister = false;
+        }
+        stopReceivingLocationUpdates();
+
+        if (mFocusToneGenerator != null) {
+            mFocusToneGenerator.release();
+            mFocusToneGenerator = null;
+        }
+
+        storeLastThumb();
+        if (mStorageHint != null) {
+            mStorageHint.cancel();
+            mStorageHint = null;
+        }
+        super.onPause();
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        switch (requestCode) {
+            case CROP_MSG: {
+                Intent intent = new Intent();
+                if (data != null) {
+                    Bundle extras = data.getExtras();
+                    if (extras != null) {
+                        intent.putExtras(extras);
+                    }
+                }
+                setResult(resultCode, intent);
+                finish();
+
+                File path = getFileStreamPath(sTempCropFilename);
+                path.delete();
+
+                break;
+            }
+        }
+    }
+
+    private void autoFocus() {
+        updateFocusIndicator();
+        if (!mIsFocusing) {
+            if (mCameraDevice != null) {
+                mIsFocusing = true;
+                mIsFocused = false;
+                mCameraDevice.autoFocus(mAutoFocusCallback);
+                if (DEBUG_TIME_OPERATIONS) {
+                    mFocusStartTime = System.currentTimeMillis();
+                }
+            }
+        }
+    }
+
+    private void clearFocus() {
+        mIsFocusing = false;
+        mIsFocused = false;
+        mIsFocusButtonPressed = false;
+    }
+
+    private void updateFocusIndicator() {
+        mHandler.post(new Runnable() {
+            public void run() {
+                if (mIsFocusing || !mIsFocusButtonPressed) {
+                    mFocusIndicator.setVisibility(View.GONE);
+                    mFocusIndicator.clearAnimation();
+                } else {
+                    if (mIsFocused) {
+                        mFocusIndicator.setVisibility(View.VISIBLE);
+                        mFocusIndicator.clearAnimation();
+                    } else {
+                        mFocusIndicator.setVisibility(View.VISIBLE);
+                        mFocusIndicator.startAnimation(mFocusBlinkAnimation);
+                    }
+                }
+            }
+        });
+    }
+
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_BACK:
+                if (mStatus == SNAPSHOT_IN_PROGRESS) {
+                    // ignore backs while we're taking a picture
+                    return true;
+                }
+                break;
+            case KeyEvent.KEYCODE_FOCUS:
+                if (event.getRepeatCount() == 0) {
+                    doFocus(true);
+                }
+                return true;
+            case KeyEvent.KEYCODE_CAMERA:
+                if (event.getRepeatCount() == 0) {
+                    doSnap(false);
+                }
+                return true;
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+                // If we get a dpad center event without any focused view, move the
+                // focus to the shutter button and press it.
+                if (event.getRepeatCount() == 0) {
+                    // Start auto-focus immediately to reduce shutter lag. After the shutter button
+                    // gets the focus, doFocus() will be called again but it is fine.
+                    doFocus(true);
+                    if (mShutterButton.isInTouchMode()) {
+                        mShutterButton.requestFocusFromTouch();
+                    } else {
+                        mShutterButton.requestFocus();
+                    }
+                    mShutterButton.setPressed(true);
+                }
+                return true;
+        }
+
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_FOCUS:
+                doFocus(false);
+                return true;
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    private void doSnap(boolean needAutofocus) {
+        // The camera operates in focus-priority mode, meaning that we take a picture
+        // when focusing completes, and only if it completes successfully. If the user
+        // has half-pressed the shutter and already locked focus, we can take the photo
+        // right away, otherwise we need to start AF.
+        if (mIsFocused || !mPreviewing) {
+            // doesn't get set until the idler runs
+            if (mCaptureObject != null) {
+                mCaptureObject.onSnap();
+            }
+            clearFocus();
+            updateFocusIndicator();
+        } else {
+            // Half pressing the shutter (i.e. the focus button event) will already have
+            // requested AF for us, so just request capture on focus here. If AF has
+            // already failed, we don't want to trigger it again.
+            mCaptureOnFocus = true;
+            if (needAutofocus && !mIsFocusButtonPressed) {
+                // But we do need to start AF for DPAD_CENTER
+                autoFocus();
+            }
+        }
+    }
+
+    private void doFocus(boolean pressed) {
+        if (pressed) {
+            mIsFocusButtonPressed = true;
+            mCaptureOnFocus = false;
+            if (mPreviewing) {
+                autoFocus();
+            } else if (mCaptureObject != null) {
+                // Save and restart preview
+                mCaptureObject.onSnap();
+            }
+        } else {
+            clearFocus();
+            updateFocusIndicator();
+        }
+    }
+
+    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
+        // if we're creating the surface, start the preview as well.
+        boolean preview = holder.isCreating();
+        setViewFinder(w, h, preview);
+        mCaptureObject = mImageCapture;
+    }
+
+    public void surfaceCreated(SurfaceHolder holder) {
+        mSurfaceHolder = holder;
+    }
+
+    public void surfaceDestroyed(SurfaceHolder holder) {
+        stopPreview();
+        mSurfaceHolder = null;
+    }
+
+    private void closeCamera() {
+        if (mCameraDevice != null) {
+            mCameraDevice.release();
+            mCameraDevice = null;
+            mPreviewing = false;
+        }
+    }
+
+    private boolean ensureCameraDevice() {
+        if (mCameraDevice == null) {
+            mCameraDevice = android.hardware.Camera.open();
+        }
+        return mCameraDevice != null;
+    }
+    
+    private boolean isLastPictureValid() {
+        boolean isValid = true;
+        if (mLastPictureUri == null)  return false;
+        try {
+            mContentResolver.openFileDescriptor(mLastPictureUri, "r").close();
+        }
+        catch (Exception ex) {
+            isValid = false;
+            Log.e(TAG, ex.toString());
+        }
+        return isValid;
+    }
+    
+    private void updateLastImage() {
+        ImageManager.IImageList list = ImageManager.instance().allImages(
+            this,
+            mContentResolver,
+            dataLocation(),
+            ImageManager.INCLUDE_IMAGES,
+            ImageManager.SORT_ASCENDING,
+            ImageManager.CAMERA_IMAGE_BUCKET_ID);
+        int count = list.getCount();
+        if (count > 0) {
+            ImageManager.IImage image = list.getImageAt(count-1);
+            mLastPictureUri = image.fullSizeImageUri();
+            Log.v(TAG, "updateLastImage: count="+ count + 
+                ", lastPictureUri="+mLastPictureUri);
+            setLastPictureThumb(image.miniThumbBitmap(), mLastPictureUri);
+        } else {
+            mLastPictureUri = null;
+        }
+        list.deactivate();
+    }
+
+    private void restartPreview() {
+        VideoPreview surfaceView = mSurfaceView;
+
+        // make sure the surfaceview fills the whole screen when previewing
+        surfaceView.setAspectRatio(VideoPreview.DONT_CARE);
+        setViewFinder(mOriginalViewFinderWidth, mOriginalViewFinderHeight, true);
+        mStatus = IDLE;
+
+        // Calculate this in advance of each shot so we don't add to shutter latency. It's true that
+        // someone else could write to the SD card in the mean time and fill it, but that could have
+        // happened between the shutter press and saving the JPEG too.
+        // TODO: The best longterm solution is to write a reserve file of maximum JPEG size, always
+        // let the user take a picture, and delete that file if needed to save the new photo.
+        calculatePicturesRemaining();
+
+        if (!isLastPictureValid()) {
+            updateLastImage();
+        }
+    
+        if (mShouldShowLastPictureButton) {
+            mShouldShowLastPictureButton = false;
+            mLastPictureButton.setVisibility(View.VISIBLE);
+            Animation a = mShowLastPictureButtonAnimation;
+            a.setDuration(500);
+            mLastPictureButton.setAnimation(a);
+        }
+
+        if (mShouldTransitionThumbnails) {
+            mShouldTransitionThumbnails = false;
+            mThumbnailTransition.startTransition(500);
+        }
+    }
+
+    private void setViewFinder(int w, int h, boolean startPreview) {
+        if (mPausing)
+            return;
+
+        if (mPreviewing &&
+                w == mViewFinderWidth &&
+                h == mViewFinderHeight) {
+            return;
+        }
+
+        if (!ensureCameraDevice())
+            return;
+
+        if (mSurfaceHolder == null)
+            return;
+
+        if (isFinishing())
+            return;
+
+        if (mPausing)
+            return;
+
+        // remember view finder size
+        mViewFinderWidth = w;
+        mViewFinderHeight = h;
+        if (mOriginalViewFinderHeight == 0) {
+            mOriginalViewFinderWidth = w;
+            mOriginalViewFinderHeight = h;
+        }
+
+        if (startPreview == false)
+            return;
+
+        /*
+         * start the preview if we're asked to...
+         */
+
+        // we want to start the preview and we're previewing already,
+        // stop the preview first (this will blank the screen).
+        if (mPreviewing)
+            stopPreview();
+
+        // this blanks the screen if the surface changed, no-op otherwise
+        try {
+            mCameraDevice.setPreviewDisplay(mSurfaceHolder);
+        } catch (IOException exception) {
+            mCameraDevice.release();
+            mCameraDevice = null;
+            // TODO: add more exception handling logic here
+            return;
+        }
+
+        // request the preview size, the hardware may not honor it,
+        // if we depended on it we would have to query the size again
+        mParameters = mCameraDevice.getParameters();
+        mParameters.setPreviewSize(w, h);
+        try {
+            mCameraDevice.setParameters(mParameters);
+        } catch (IllegalArgumentException e) {
+            // Ignore this error, it happens in the simulator.
+        }
+
+
+        final long wallTimeStart = SystemClock.elapsedRealtime();
+        final long threadTimeStart = Debug.threadCpuTimeNanos();
+
+        final Object watchDogSync = new Object();
+        Thread watchDog = new Thread(new Runnable() {
+            public void run() {
+                int next_warning = 1;
+                while (true) {
+                    try {
+                        synchronized (watchDogSync) {
+                            watchDogSync.wait(1000);
+                        }
+                    } catch (InterruptedException ex) {
+                        //
+                    }
+                    if (mPreviewing)
+                        break;
+
+                    int delay = (int) (SystemClock.elapsedRealtime() - wallTimeStart) / 1000;
+                    if (delay >= next_warning) {
+                        if (delay < 120) {
+                            Log.e(TAG, "preview hasn't started yet in " + delay + " seconds");
+                        } else {
+                            Log.e(TAG, "preview hasn't started yet in " + (delay / 60) + " minutes");
+                        }
+                        if (next_warning < 60) {
+                            next_warning <<= 1;
+                            if (next_warning == 16) {
+                                next_warning = 15;
+                            }
+                        } else {
+                            next_warning += 60;
+                        }
+                    }
+                }
+            }
+        });
+
+        watchDog.start();
+
+        if (Config.LOGV)
+            Log.v(TAG, "calling mCameraDevice.startPreview");
+        try {
+            mCameraDevice.startPreview();
+        } catch (Throwable e) {
+            // TODO: change Throwable to IOException once android.hardware.Camera.startPreview
+            // properly declares that it throws IOException.
+        }
+        mPreviewing = true;
+
+        synchronized (watchDogSync) {
+            watchDogSync.notify();
+        }
+
+        long threadTimeEnd = Debug.threadCpuTimeNanos();
+        long wallTimeEnd = SystemClock.elapsedRealtime();
+        if ((wallTimeEnd - wallTimeStart) > 3000) {
+            Log.w(TAG, "startPreview() to " + (wallTimeEnd - wallTimeStart) + " ms. Thread time was"
+                    + (threadTimeEnd - threadTimeStart) / 1000000 + " ms.");
+        }
+    }
+
+    private void stopPreview() {
+        if (mCameraDevice != null && mPreviewing) {
+            mCameraDevice.stopPreview();
+        }
+        mPreviewing = false;
+    }
+
+    void gotoGallery() {
+        MenuHelper.gotoCameraImageGallery(this);
+    }
+
+    private void viewLastImage() {
+        Uri targetUri = mLastPictureUri;
+        if (targetUri != null && isLastPictureValid()) {
+            targetUri = targetUri.buildUpon().
+                appendQueryParameter("bucketId", ImageManager.CAMERA_IMAGE_BUCKET_ID).build();
+            Intent intent = new Intent(Intent.ACTION_VIEW, targetUri);
+            intent.putExtra(MediaStore.EXTRA_SCREEN_ORIENTATION,
+                    ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+            intent.putExtra(MediaStore.EXTRA_FULL_SCREEN, true);
+            intent.putExtra(MediaStore.EXTRA_SHOW_ACTION_ICONS, true);
+            intent.putExtra("com.android.camera.ReviewMode", true);
+
+            try {
+                startActivity(intent);
+            } catch (android.content.ActivityNotFoundException ex) {
+                // ignore.
+            }
+        } else {
+            Log.e(TAG, "Can't view last image.");
+        }
+    }
+
+    void keep() {
+        cancelSavingNotification();
+        if (mCaptureObject != null) {
+            mCaptureObject.dismissFreezeFrame(true);
+        }
+    };
+
+    void toss() {
+        cancelSavingNotification();
+        if (mCaptureObject != null) {
+            mCaptureObject.cancelSave();
+        }
+    };
+
+    private ImageManager.IImage getImageForURI(Uri uri) {
+        ImageManager.IImageList list = ImageManager.instance().allImages(
+                this,
+                mContentResolver,
+                dataLocation(),
+                ImageManager.INCLUDE_IMAGES,
+                ImageManager.SORT_ASCENDING);
+        ImageManager.IImage image = list.getImageForUri(uri);
+        list.deactivate();
+        return image;
+    }
+
+
+    private void startReceivingLocationUpdates() {
+        if (mLocationManager != null) {
+            try {
+                mLocationManager.requestLocationUpdates(
+                        LocationManager.NETWORK_PROVIDER,
+                        1000,
+                        0F,
+                        mLocationListeners[1]);
+            } catch (java.lang.SecurityException ex) {
+                // ok
+            } catch (IllegalArgumentException ex) {
+                if (Config.LOGD) {
+                    Log.d(TAG, "provider does not exist " + ex.getMessage());
+                }
+            }
+            try {
+                mLocationManager.requestLocationUpdates(
+                        LocationManager.GPS_PROVIDER,
+                        1000,
+                        0F,
+                        mLocationListeners[0]);
+            } catch (java.lang.SecurityException ex) {
+                // ok
+            } catch (IllegalArgumentException ex) {
+                if (Config.LOGD) {
+                    Log.d(TAG, "provider does not exist " + ex.getMessage());
+                }
+            }
+        }
+    }
+
+    private void stopReceivingLocationUpdates() {
+        if (mLocationManager != null) {
+            for (int i = 0; i < mLocationListeners.length; i++) {
+                try {
+                    mLocationManager.removeUpdates(mLocationListeners[i]);
+                } catch (Exception ex) {
+                    // ok
+                }
+            }
+        }
+    }
+
+    private Location getCurrentLocation() {
+        Location l = null;
+
+        // go in best to worst order
+        for (int i = 0; i < mLocationListeners.length; i++) {
+            l = mLocationListeners[i].current();
+            if (l != null)
+                break;
+        }
+
+        return l;
+    }
+
+    @Override
+    public void onOptionsMenuClosed(Menu menu) {
+        super.onOptionsMenuClosed(menu);
+        if (mImageSavingItem && !mMenuSelectionMade) {
+            // save the image if we presented the "advanced" menu
+            // which happens if "menu" is pressed while in
+            // SNAPSHOT_IN_PROGRESS  or SNAPSHOT_COMPLETED modes
+            keep();
+            mHandler.sendEmptyMessage(RESTART_PREVIEW);
+        }
+    }
+
+    @Override
+    public boolean onMenuOpened(int featureId, Menu menu) {
+        if (featureId == Window.FEATURE_OPTIONS_PANEL) {
+            if (mStatus == SNAPSHOT_IN_PROGRESS) {
+                cancelAutomaticPreviewRestart();
+                mMenuSelectionMade = false;
+            }
+        }
+        return super.onMenuOpened(featureId, menu);
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        super.onPrepareOptionsMenu(menu);
+
+        mMenuSelectionMade = false;
+
+        for (int i = 1; i <= MenuHelper.MENU_ITEM_MAX; i++) {
+            if (i != MenuHelper.GENERIC_ITEM) {
+                menu.setGroupVisible(i, false);
+            }
+        }
+
+        if (mStatus == SNAPSHOT_IN_PROGRESS || mStatus == SNAPSHOT_COMPLETED) {
+            menu.setGroupVisible(MenuHelper.IMAGE_SAVING_ITEM, true);
+            mImageSavingItem = true;
+        } else {
+            menu.setGroupVisible(MenuHelper.IMAGE_MODE_ITEM, true);
+            mImageSavingItem = false;
+        }
+
+        if (mCaptureObject != null)
+            mCaptureObject.cancelAutoDismiss();
+
+        return true;
+    }
+
+    private void cancelAutomaticPreviewRestart() {
+        mKeepAndRestartPreview = false;
+        mHandler.removeMessages(RESTART_PREVIEW);
+    }
+
+    private boolean isImageCaptureIntent() {
+        String action = getIntent().getAction();
+        return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action));
+    }
+
+    private void showPostCaptureAlert() {
+        if (isImageCaptureIntent()) {
+            mPostCaptureAlert.setVisibility(View.VISIBLE);
+            int[] pickIds = {R.id.attach, R.id.cancel};
+            for(int id : pickIds) {
+                View view = mPostCaptureAlert.findViewById(id);
+                view.setOnClickListener(this);
+                Animation animation = new AlphaAnimation(0F, 1F);
+                animation.setDuration(500);
+                view.setAnimation(animation);
+            }
+        }
+    }
+
+    private void hidePostCaptureAlert() {
+        if (isImageCaptureIntent()) {
+            mPostCaptureAlert.setVisibility(View.INVISIBLE);
+        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+
+        if (isImageCaptureIntent()) {
+            // No options menu for attach mode.
+            return false;
+        } else {
+            addBaseMenuItems(menu);
+            MenuHelper.addImageMenuItems(
+                    menu,
+                    MenuHelper.INCLUDE_ALL & ~MenuHelper.INCLUDE_ROTATE_MENU,
+                    true,
+                    Camera.this,
+                    mHandler,
+
+                    // Handler for deletion
+                    new Runnable() {
+                        public void run() {
+                            if (mCaptureObject != null) {
+                                mCaptureObject.cancelSave();
+                                Uri uri = mCaptureObject.getLastCaptureUri();
+                                if (uri != null) {
+                                    mContentResolver.delete(uri, null, null);
+                                }
+                            }
+                        }
+                    },
+                    new MenuHelper.MenuInvoker() {
+                        public void run(final MenuHelper.MenuCallback cb) {
+                            mMenuSelectionMade = true;
+                            postAfterKeep(new Runnable() {
+                                public void run() {
+                                    cb.run(mSelectedImageGetter.getCurrentImageUri(), mSelectedImageGetter.getCurrentImage());
+                                    if (mCaptureObject != null)
+                                        mCaptureObject.dismissFreezeFrame(true);
+                                }
+                            });
+                        }
+                    });
+
+            MenuItem gallery = menu.add(MenuHelper.IMAGE_SAVING_ITEM, MENU_SAVE_GALLERY_PHOTO, 0, R.string.camera_gallery_photos_text).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+                public boolean onMenuItemClick(MenuItem item) {
+                    postAfterKeep(new Runnable() {
+                        public void run() {
+                            gotoGallery();
+                        }
+                    });
+                    return true;
+                }
+            });
+            gallery.setIcon(android.R.drawable.ic_menu_gallery);
+        }
+        return true;
+    }
+
+    SelectedImageGetter mSelectedImageGetter =
+        new SelectedImageGetter() {
+            public ImageManager.IImage getCurrentImage() {
+                return getImageForURI(getCurrentImageUri());
+            }
+            public Uri getCurrentImageUri() {
+                keep();
+                return mCaptureObject.getLastCaptureUri();
+            }
+        };
+
+    private int calculatePicturesRemaining() {
+        mPicturesRemaining = MenuHelper.calculatePicturesRemaining();
+        return mPicturesRemaining;
+    }
+
+    private void addBaseMenuItems(Menu menu) {
+        MenuHelper.addSwitchModeMenuItem(menu, this, true);
+        {
+            MenuItem gallery = menu.add(MenuHelper.IMAGE_MODE_ITEM, MENU_GALLERY_PHOTOS, 0, R.string.camera_gallery_photos_text).setOnMenuItemClickListener(new OnMenuItemClickListener() {
+                public boolean onMenuItemClick(MenuItem item) {
+                    gotoGallery();
+                    return true;
+                }
+            });
+            gallery.setIcon(android.R.drawable.ic_menu_gallery);
+            mGalleryItems.add(gallery);
+        }
+        {
+            MenuItem gallery = menu.add(MenuHelper.VIDEO_MODE_ITEM, MENU_GALLERY_VIDEOS, 0, R.string.camera_gallery_photos_text).setOnMenuItemClickListener(new OnMenuItemClickListener() {
+                public boolean onMenuItemClick(MenuItem item) {
+                    gotoGallery();
+                    return true;
+                }
+            });
+            gallery.setIcon(android.R.drawable.ic_menu_gallery);
+            mGalleryItems.add(gallery);
+        }
+
+        MenuItem item = menu.add(MenuHelper.GENERIC_ITEM, MENU_SETTINGS, 0, R.string.settings).setOnMenuItemClickListener(new OnMenuItemClickListener() {
+            public boolean onMenuItemClick(MenuItem item) {
+                Intent intent = new Intent();
+                intent.setClass(Camera.this, CameraSettings.class);
+                startActivity(intent);
+                return true;
+            }
+        });
+        item.setIcon(android.R.drawable.ic_menu_preferences);
+    }
+}
+
diff --git a/src/com/android/camera/CameraButtonIntentReceiver.java b/src/com/android/camera/CameraButtonIntentReceiver.java
new file mode 100644
index 0000000..5e4d3c3
--- /dev/null
+++ b/src/com/android/camera/CameraButtonIntentReceiver.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2007 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.camera;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.BroadcastReceiver;
+import android.view.KeyEvent;
+
+public class CameraButtonIntentReceiver extends BroadcastReceiver {
+    public CameraButtonIntentReceiver() {
+    }
+    
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+        
+        if (event == null) {
+            return;
+        }
+        
+        Intent i = new Intent(Intent.ACTION_MAIN);
+        i.setClass(context, Camera.class);
+        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        context.startActivity(i);
+    }
+}
diff --git a/src/com/android/camera/CameraSettings.java b/src/com/android/camera/CameraSettings.java
new file mode 100644
index 0000000..0145d64
--- /dev/null
+++ b/src/com/android/camera/CameraSettings.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2007 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.camera;
+
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.os.Bundle;
+import android.preference.ListPreference;
+import android.preference.PreferenceActivity;
+
+/**
+ *  CameraSettings
+ */
+public class CameraSettings extends PreferenceActivity
+    implements OnSharedPreferenceChangeListener
+{
+    public static final String KEY_VIDEO_QUALITY = "pref_camera_videoquality_key";
+    public static final boolean DEFAULT_VIDEO_QUALITY_VALUE = true;
+
+    private ListPreference mVideoQuality;
+
+    public CameraSettings()
+    {
+    }
+
+    /** Called with the activity is first created. */
+    @Override
+    public void onCreate(Bundle icicle)
+    {
+        super.onCreate(icicle);
+        addPreferencesFromResource(R.xml.camera_preferences);
+
+        initUI();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        updateVideoQuality();
+    }
+
+    private void initUI() {
+        mVideoQuality = (ListPreference) findPreference(KEY_VIDEO_QUALITY);
+        getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
+    }
+
+    private void updateVideoQuality() {
+        boolean vidQualityValue = getBooleanPreference(mVideoQuality, DEFAULT_VIDEO_QUALITY_VALUE);
+        int vidQualityIndex = vidQualityValue ? 1 : 0;
+        String[] vidQualities =
+            getResources().getStringArray(R.array.pref_camera_videoquality_entries);
+        String vidQuality = vidQualities[vidQualityIndex];
+        mVideoQuality.setSummary(vidQuality);
+    }
+
+    private static int getIntPreference(ListPreference preference, int defaultValue) {
+        String s = preference.getValue();
+        int result = defaultValue;
+        try {
+            result = Integer.parseInt(s);
+        } catch (NumberFormatException e) {
+            // Ignore, result is already the default value.
+        }
+        return result;
+    }
+
+    private boolean getBooleanPreference(ListPreference preference, boolean defaultValue) {
+        return getIntPreference(preference, defaultValue ? 1 : 0) != 0;
+    }
+
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+            String key) {
+           if (key.equals(KEY_VIDEO_QUALITY)) {
+               updateVideoQuality();
+           }
+
+    }
+}
+
diff --git a/src/com/android/camera/CameraThread.java b/src/com/android/camera/CameraThread.java
new file mode 100644
index 0000000..ba888bf
--- /dev/null
+++ b/src/com/android/camera/CameraThread.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2007 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.camera;
+
+import android.os.Process;
+
+public class CameraThread {
+    private Thread mThread;
+    private int mTid;
+    private boolean mTidSet;
+    private boolean mFinished;
+
+    synchronized private void setTid(int tid) {
+        mTid = tid;
+        mTidSet = true;
+        CameraThread.this.notifyAll();
+    }
+    
+    synchronized private void setFinished() {
+        mFinished = true;
+    }
+    
+    public CameraThread(final Runnable r) {
+        Runnable wrapper = new Runnable() {
+            public void run() {
+                setTid(Process.myTid());
+                try {
+                    r.run();
+                } finally {
+                    setFinished();
+                }
+            }
+        };
+
+        mThread = new Thread(wrapper);
+    }
+    
+    synchronized public void start() {
+        mThread.start();
+    }
+    
+    synchronized public void setName(String name) {
+        mThread.setName(name);
+    }
+    
+    public void join() {
+        try {
+            mThread.join();
+        } catch (InterruptedException ex) {
+            // ok?
+        }
+    }
+    
+    public long getId() {
+        return mThread.getId();
+    }
+    
+    public Thread realThread() {
+        return mThread;
+    }
+    
+    synchronized public void setPriority(int androidOsPriority) {
+        while (!mTidSet) {
+            try {
+                CameraThread.this.wait();
+            } catch (InterruptedException ex) {
+                // ok, try again
+            }
+        }
+        if (!mFinished)
+            Process.setThreadPriority(mTid, androidOsPriority);
+    }
+    
+    synchronized public void toBackground() {
+        setPriority(Process.THREAD_PRIORITY_BACKGROUND);
+    }
+    
+    synchronized public void toForeground() {
+        setPriority(Process.THREAD_PRIORITY_FOREGROUND);
+    }
+}
diff --git a/src/com/android/camera/CropImage.java b/src/com/android/camera/CropImage.java
new file mode 100644
index 0000000..cefaf83
--- /dev/null
+++ b/src/com/android/camera/CropImage.java
@@ -0,0 +1,802 @@
+/*
+ * Copyright (C) 2007 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.camera;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.media.FaceDetector;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.MediaStore;
+import android.util.AttributeSet;
+import android.util.Config;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.Window;
+import android.widget.Toast;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+
+public class CropImage extends Activity {
+    private static final String TAG = "CropImage";
+    private ProgressDialog mFaceDetectionDialog = null;
+    private ProgressDialog mSavingProgressDialog = null;
+    private ImageManager.IImageList mAllImages;
+    private Bitmap.CompressFormat mSaveFormat = Bitmap.CompressFormat.JPEG; // only used with mSaveUri
+    private Uri mSaveUri = null;
+    private int mAspectX, mAspectY;
+    private int mOutputX, mOutputY;
+    private boolean mDoFaceDetection = true;
+    private boolean mCircleCrop = false;
+    private boolean mWaitingToPick;
+    private boolean mScale;
+    private boolean mSaving;
+    private boolean mScaleUp = true;
+
+    CropImageView mImageView;
+    ContentResolver mContentResolver;
+
+    Bitmap mBitmap;
+    Bitmap mCroppedImage;
+    HighlightView mCrop;
+
+    ImageManager.IImage mImage;
+
+    public CropImage() {
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        return super.onCreateOptionsMenu(menu);
+    }
+
+    static public class CropImageView extends ImageViewTouchBase {
+        ArrayList<HighlightView> mHighlightViews = new ArrayList<HighlightView>();
+        HighlightView mMotionHighlightView = null;
+        float mLastX, mLastY;
+        int mMotionEdge;
+
+        public CropImageView(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected boolean doesScrolling() {
+            return false;
+        }
+
+        @Override
+        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+            super.onLayout(changed, left, top, right, bottom);
+            if (mBitmapDisplayed != null) {
+                for (HighlightView hv : mHighlightViews) {
+                    hv.mMatrix.set(getImageMatrix());
+                    hv.invalidate();
+                    if (hv.mIsFocused) {
+                        centerBasedOnHighlightView(hv);
+                    }
+                }
+            }
+        }
+
+        public CropImageView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        protected void zoomTo(float scale, float centerX, float centerY) {
+            super.zoomTo(scale, centerX, centerY);
+            for (HighlightView hv : mHighlightViews) {
+                hv.mMatrix.set(getImageMatrix());
+                hv.invalidate();
+            }
+        }
+
+        protected void zoomIn() {
+            super.zoomIn();
+            for (HighlightView hv : mHighlightViews) {
+                hv.mMatrix.set(getImageMatrix());
+                hv.invalidate();
+            }
+        }
+
+        protected void zoomOut() {
+            super.zoomOut();
+            for (HighlightView hv : mHighlightViews) {
+                hv.mMatrix.set(getImageMatrix());
+                hv.invalidate();
+            }
+        }
+
+
+        @Override
+        protected boolean usePerfectFitBitmap() {
+            return false;
+        }
+
+        @Override
+        protected void postTranslate(float deltaX, float deltaY) {
+            super.postTranslate(deltaX, deltaY);
+            for (int i = 0; i < mHighlightViews.size(); i++) {
+                HighlightView hv = mHighlightViews.get(i);
+                hv.mMatrix.postTranslate(deltaX, deltaY);
+                hv.invalidate();
+            }
+        }
+
+        private void recomputeFocus(MotionEvent event) {
+            for (int i = 0; i < mHighlightViews.size(); i++) {
+                HighlightView hv = mHighlightViews.get(i);
+                hv.setFocus(false);
+                hv.invalidate();
+            }
+
+            for (int i = 0; i < mHighlightViews.size(); i++) {
+                HighlightView hv = mHighlightViews.get(i);
+                int edge = hv.getHit(event.getX(), event.getY());
+                if (edge != HighlightView.GROW_NONE) {
+                    if (!hv.hasFocus()) {
+                        hv.setFocus(true);
+                        hv.invalidate();
+                    }
+                    break;
+                }
+            }
+            invalidate();
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent event) {
+            CropImage cropImage = (CropImage)mContext;
+            if (cropImage.mSaving)
+                return false;
+
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    if (cropImage.mWaitingToPick) {
+                        recomputeFocus(event);
+                    } else {
+                        for (int i = 0; i < mHighlightViews.size(); i++) {
+                            HighlightView hv = mHighlightViews.get(i);
+                            int edge = hv.getHit(event.getX(), event.getY());
+                            if (edge != HighlightView.GROW_NONE) {
+                                mMotionEdge = edge;
+                                mMotionHighlightView = hv;
+                                mLastX = event.getX();
+                                mLastY = event.getY();
+                                mMotionHighlightView.setMode(edge == HighlightView.MOVE
+                                        ? HighlightView.ModifyMode.Move
+                                                : HighlightView.ModifyMode.Grow);
+                                break;
+                            }
+                        }
+                    }
+                    break;
+                case MotionEvent.ACTION_UP:
+                    if (cropImage.mWaitingToPick) {
+                        for (int i = 0; i < mHighlightViews.size(); i++) {
+                            HighlightView hv = mHighlightViews.get(i);
+                            if (hv.hasFocus()) {
+                                cropImage.mCrop = hv;
+                                for (int j = 0; j < mHighlightViews.size(); j++) {
+                                    if (j == i)
+                                        continue;
+                                    mHighlightViews.get(j).setHidden(true);
+                                }
+                                centerBasedOnHighlightView(hv);
+                                ((CropImage)mContext).mWaitingToPick = false;
+                                return true;
+                            }
+                        }
+                    } else if (mMotionHighlightView != null) {
+                        centerBasedOnHighlightView(mMotionHighlightView);
+                        mMotionHighlightView.setMode(HighlightView.ModifyMode.None);
+                    }
+                    mMotionHighlightView = null;
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    if (cropImage.mWaitingToPick) {
+                        recomputeFocus(event);
+                    } else if (mMotionHighlightView != null) {
+                        mMotionHighlightView.handleMotion(mMotionEdge, event.getX()-mLastX, event.getY()-mLastY);
+                        mLastX = event.getX();
+                        mLastY = event.getY();
+
+                        if (true) {
+                            // This section of code is optional.  It has some user
+                            // benefit in that moving the crop rectangle against
+                            // the edge of the screen causes scrolling but it means
+                            // that the crop rectangle is no longer fixed under
+                            // the user's finger.
+                            ensureVisible(mMotionHighlightView);
+                        }
+                    }
+                    break;
+            }
+
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_UP:
+                    center(true, true, true);
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    // if we're not zoomed then there's no point in even allowing
+                    // the user to move the image around.  This call to center
+                    // puts it back to the normalized location (with false meaning
+                    // don't animate).
+                    if (getScale() == 1F)
+                        center(true, true, false);
+                    break;
+            }
+
+            return true;
+        }
+
+        private void ensureVisible(HighlightView hv) {
+            Rect r = hv.mDrawRect;
+
+            int panDeltaX1 = Math.max(0, mLeft - r.left);
+            int panDeltaX2 = Math.min(0, mRight - r.right);
+
+            int panDeltaY1 = Math.max(0, mTop - r.top);
+            int panDeltaY2 = Math.min(0, mBottom - r.bottom);
+
+            int panDeltaX = panDeltaX1 != 0 ? panDeltaX1 : panDeltaX2;
+            int panDeltaY = panDeltaY1 != 0 ? panDeltaY1 : panDeltaY2;
+
+            if (panDeltaX != 0 || panDeltaY != 0)
+                panBy(panDeltaX, panDeltaY);
+        }
+
+        private void centerBasedOnHighlightView(HighlightView hv) {
+            Rect drawRect = hv.mDrawRect;
+
+            float width = drawRect.width();
+            float height = drawRect.height();
+
+            float thisWidth = getWidth();
+            float thisHeight = getHeight();
+
+            float z1 = thisWidth / width * .6F;
+            float z2 = thisHeight / height * .6F;
+
+            float zoom = Math.min(z1, z2);
+            zoom = zoom * this.getScale();
+            zoom = Math.max(1F, zoom);
+
+            if ((Math.abs(zoom - getScale()) / zoom) > .1) {
+                float [] coordinates = new float[] { hv.mCropRect.centerX(), hv.mCropRect.centerY() };
+                getImageMatrix().mapPoints(coordinates);
+                zoomTo(zoom, coordinates[0], coordinates[1], 300F);
+            }
+
+            ensureVisible(hv);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            for (int i = 0; i < mHighlightViews.size(); i++) {
+                mHighlightViews.get(i).draw(canvas);
+            }
+        }
+
+        public HighlightView get(int i) {
+            return mHighlightViews.get(i);
+        }
+
+        public int size() {
+            return mHighlightViews.size();
+        }
+
+        public void add(HighlightView hv) {
+            mHighlightViews.add(hv);
+            invalidate();
+        }
+    }
+
+    private void fillCanvas(int width, int height, Canvas c) {
+        Paint paint = new Paint();
+        paint.setColor(0x00000000);  // pure alpha
+        paint.setStyle(android.graphics.Paint.Style.FILL);
+        paint.setAntiAlias(true);
+        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+        c.drawRect(0F, 0F, width, height, paint);
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        mContentResolver = getContentResolver();
+
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        setContentView(R.layout.cropimage);
+
+        mImageView = (CropImageView) findViewById(R.id.image);
+
+        MenuHelper.showStorageToast(this);
+
+        try {
+            android.content.Intent intent = getIntent();
+            Bundle extras = intent.getExtras();
+            if (Config.LOGV)
+                Log.v(TAG, "extras are " + extras);
+            if (extras != null) {
+                for (String s: extras.keySet()) {
+                    if (Config.LOGV)
+                        Log.v(TAG, "" + s + " >>> " + extras.get(s));
+                }
+                if (extras.getString("circleCrop") != null) {
+                    mCircleCrop = true;
+                    mAspectX = 1;
+                    mAspectY = 1;
+                }
+                mSaveUri = (Uri) extras.getParcelable(MediaStore.EXTRA_OUTPUT);
+                if (mSaveUri != null) {
+                    String compressFormatString = extras.getString("outputFormat");
+                    if (compressFormatString != null)
+                        mSaveFormat = Bitmap.CompressFormat.valueOf(compressFormatString);
+                }
+                mBitmap = (Bitmap) extras.getParcelable("data");
+                mAspectX = extras.getInt("aspectX");
+                mAspectY = extras.getInt("aspectY");
+                mOutputX = extras.getInt("outputX");
+                mOutputY = extras.getInt("outputY");
+                mScale = extras.getBoolean("scale", true);
+                mScaleUp = extras.getBoolean("scaleUpIfNeeded", true);
+                mDoFaceDetection = extras.containsKey("noFaceDetection") ? !extras.getBoolean("noFaceDetection") : true;
+            }
+
+            if (mBitmap == null) {
+                Uri target = intent.getData();
+                mAllImages = ImageManager.makeImageList(target, CropImage.this, ImageManager.SORT_ASCENDING);
+                mImage = mAllImages.getImageForUri(target);
+                if(mImage != null) {
+                    // don't read in really large bitmaps.  max out at 1000.
+                    // TODO when saving the resulting bitmap use the decode/crop/encode
+                    // api so we don't lose any resolution
+                    mBitmap = mImage.thumbBitmap();
+                    if (Config.LOGV)
+                        Log.v(TAG, "thumbBitmap returned " + mBitmap);
+                }
+            }
+
+            if (mBitmap == null) {
+                finish();
+                return;
+            }
+
+            mHandler.postDelayed(new Runnable() {
+                public void run() {
+                    if (isFinishing()) {
+                        return;
+                    }
+                    mFaceDetectionDialog = ProgressDialog.show(CropImage.this,
+                            null,
+                            getResources().getString(R.string.runningFaceDetection),
+                            true, false);
+                    mImageView.setImageBitmapResetBase(mBitmap, true, true);
+                    if (mImageView.getScale() == 1F)
+                        mImageView.center(true, true, false);
+
+                    new Thread(new Runnable() {
+                        public void run() {
+                            final Bitmap b = mImage != null ? mImage.fullSizeBitmap(500) : mBitmap;
+                            if (Config.LOGV)
+                                Log.v(TAG, "back from mImage.fullSizeBitmap(500) with bitmap of size " + b.getWidth() + " / " + b.getHeight());
+                            mHandler.post(new Runnable() {
+                                public void run() {
+                                    if (b != mBitmap && b != null) {
+                                        mBitmap = b;
+                                        mImageView.setImageBitmapResetBase(b, true, false);
+                                    }
+                                    if (mImageView.getScale() == 1F)
+                                        mImageView.center(true, true, false);
+
+                                   new Thread(mRunFaceDetection).start();
+                                }
+                            });
+                        }
+                    }).start();
+                }}, 100);
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to load bitmap", e);
+            finish();
+        }
+
+        findViewById(R.id.discard).setOnClickListener(new android.view.View.OnClickListener() {
+            public void onClick(View v) {
+                setResult(RESULT_CANCELED);
+                finish();
+            }
+        });
+
+        findViewById(R.id.save).setOnClickListener(new android.view.View.OnClickListener() {
+            public void onClick(View v) {
+                // TODO this code needs to change to use the decode/crop/encode single
+                // step api so that we don't require that the whole (possibly large) bitmap
+                // doesn't have to be read into memory
+                mSaving = true;
+                if (mCroppedImage == null) {
+                    if (mCrop == null) {
+                        if (Config.LOGV)
+                            Log.v(TAG, "no cropped image...");
+                        return;
+                    }
+
+                    Rect r = mCrop.getCropRect();
+
+                    int width  = r.width();
+                    int height = r.height();
+
+                    // if we're circle cropping we'll want alpha which is the third param here
+                    mCroppedImage = Bitmap.createBitmap(width, height,
+                            mCircleCrop ?
+                                    Bitmap.Config.ARGB_8888 :
+                                    Bitmap.Config.RGB_565);
+                    Canvas c1 = new Canvas(mCroppedImage);
+                    c1.drawBitmap(mBitmap, r, new Rect(0, 0, width, height), null);
+
+                    if (mCircleCrop) {
+                        // OK, so what's all this about?
+                        // Bitmaps are inherently rectangular but we want to return something
+                        // that's basically a circle.  So we fill in the area around the circle
+                        // with alpha.  Note the all important PortDuff.Mode.CLEAR.
+                        Canvas c = new Canvas (mCroppedImage);
+                        android.graphics.Path p = new android.graphics.Path();
+                        p.addCircle(width/2F, height/2F, width/2F, android.graphics.Path.Direction.CW);
+                        c.clipPath(p, Region.Op.DIFFERENCE);
+
+                        fillCanvas(width, height, c);
+                    }
+                }
+
+                /* If the output is required to a specific size then scale or fill */
+                if (mOutputX != 0 && mOutputY != 0) {
+
+                    if (mScale) {
+
+                        /* Scale the image to the required dimensions */
+                        mCroppedImage = ImageLoader.transform(new Matrix(),
+                                mCroppedImage, mOutputX, mOutputY, mScaleUp);
+                    } else {
+
+                        /* Don't scale the image crop it to the size requested.
+                         * Create an new image with the cropped image in the center and
+                         * the extra space filled.
+                         */
+
+                        /* Don't scale the image but instead fill it so it's the required dimension */
+                        Bitmap b = Bitmap.createBitmap(mOutputX, mOutputY, Bitmap.Config.RGB_565);
+                        Canvas c1 = new Canvas(b);
+
+                        /* Draw the cropped bitmap in the center */
+                        Rect r = mCrop.getCropRect();
+                        int left = (mOutputX / 2) - (r.width() / 2);
+                        int top = (mOutputY / 2) - (r.width() / 2);
+                        c1.drawBitmap(mBitmap, r, new Rect(left, top, left
+                                + r.width(), top + r.height()), null);
+
+                        /* Set the cropped bitmap as the new bitmap */
+                        mCroppedImage = b;
+                    }
+                }
+
+                Bundle myExtras = getIntent().getExtras();
+                if (myExtras != null && (myExtras.getParcelable("data") != null || myExtras.getBoolean("return-data"))) {
+                    Bundle extras = new Bundle();
+                    extras.putParcelable("data", mCroppedImage);
+                    setResult(RESULT_OK,
+                            (new Intent()).setAction("inline-data").putExtras(extras));
+                    finish();
+                } else {
+                    if (!isFinishing()) {
+                        mSavingProgressDialog = ProgressDialog.show(CropImage.this,
+                                null,
+                                getResources().getString(R.string.savingImage),
+                                true, true);
+                    }
+                    Runnable r = new Runnable() {
+                        public void run() {
+                            if (mSaveUri != null) {
+                                OutputStream outputStream = null;
+                                try {
+                                    String scheme = mSaveUri.getScheme();
+                                    if (scheme.equals("file")) {
+                                        outputStream = new FileOutputStream(mSaveUri.toString().substring(scheme.length()+":/".length()));
+                                    } else {
+                                        outputStream = mContentResolver.openOutputStream(mSaveUri);
+                                    }
+                                    if (outputStream != null)
+                                        mCroppedImage.compress(mSaveFormat, 75, outputStream);
+
+                                } catch (IOException ex) {
+                                    if (Config.LOGV)
+                                        Log.v(TAG, "got IOException " + ex);
+                                } finally {
+                                    if (outputStream != null)  {
+                                        try {
+                                            outputStream.close();
+                                        } catch (IOException ex) {
+
+                                        }
+                                    }
+                                }
+                                Bundle extras = new Bundle();
+                                setResult(RESULT_OK,
+                                        (new Intent())
+                                                .setAction(mSaveUri.toString())
+                                                .putExtras(extras));
+                            } else {
+                                Bundle extras = new Bundle();
+                                extras.putString("rect",  mCrop.getCropRect().toString());
+
+                                // here we decide whether to create a new image or
+                                // modify the existing image
+                                if (false) {
+                                    /*
+                                    // this is the "modify" case
+                                    ImageManager.IGetBoolean_cancelable cancelable =
+                                        mImage.saveImageContents(mCroppedImage, null, null, null, mImage.getDateTaken(), 0, false);
+                                    boolean didSave = cancelable.get();
+                                    extras.putString("thumb1uri", mImage.thumbUri().toString());
+                                    setResult(RESULT_OK,
+                                            (new Intent()).setAction(mImage.fullSizeImageUri().toString())
+                                                    .putExtras(extras));
+                                    */
+                                } else {
+                                    // this is the "new image" case
+                                    java.io.File oldPath = new java.io.File(mImage.getDataPath());
+                                    java.io.File directory = new java.io.File(oldPath.getParent());
+
+                                    int x = 0;
+                                    String fileName = oldPath.getName();
+                                    fileName = fileName.substring(0, fileName.lastIndexOf("."));
+
+                                    while (true) {
+                                        x += 1;
+                                        String candidate = directory.toString() + "/" + fileName + "-" + x + ".jpg";
+                                        if (Config.LOGV)
+                                            Log.v(TAG, "candidate is " + candidate);
+                                        boolean exists = (new java.io.File(candidate)).exists();
+                                        if (!exists)
+                                            break;
+                                    }
+
+                                    try {
+                                        Uri newUri = ImageManager.instance().addImage(
+                                                CropImage.this,
+                                                getContentResolver(),
+                                                mImage.getTitle(),
+                                                mImage.getDescription(),
+                                                mImage.getDateTaken(),
+                                                null,    // TODO this null is going to cause us to lose the location (gps)
+                                                0,       // TODO this is going to cause the orientation to reset
+                                                directory.toString(),
+                                                fileName + "-" + x + ".jpg");
+
+                                        ImageManager.IAddImage_cancelable cancelable = ImageManager.instance().storeImage(
+                                                newUri,
+                                                CropImage.this,
+                                                getContentResolver(),
+                                                0, // TODO fix this orientation
+                                                mCroppedImage,
+                                                null);
+
+                                        cancelable.get();
+                                        setResult(RESULT_OK,
+                                                (new Intent()).setAction(newUri.toString())
+                                                .putExtras(extras));
+                                    } catch (Exception ex) {
+                                        // basically ignore this or put up
+                                        // some ui saying we failed
+                                    }
+                                }
+                            }
+                            finish();
+                        }
+                    };
+                    Thread t = new Thread(r);
+                    t.start();
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+    }
+
+    Handler mHandler = new Handler();
+
+    Runnable mRunFaceDetection = new Runnable() {
+        float mScale = 1F;
+        RectF mUnion = null;
+        Matrix mImageMatrix;
+        FaceDetector.Face[] mFaces = new FaceDetector.Face[3];
+        int mNumFaces;
+
+        private void handleFace(FaceDetector.Face f) {
+            PointF midPoint = new PointF();
+
+            int r = ((int)(f.eyesDistance() * mScale)) * 2 ;
+            f.getMidPoint(midPoint);
+            midPoint.x *= mScale;
+            midPoint.y *= mScale;
+
+            int midX = (int) midPoint.x;
+            int midY = (int) midPoint.y;
+
+            HighlightView hv = makeHighlightView();
+
+            int width = mBitmap.getWidth();
+            int height = mBitmap.getHeight();
+
+            Rect imageRect = new Rect(0, 0, width, height);
+
+            RectF faceRect = new RectF(midX, midY, midX, midY);
+            faceRect.inset(-r, -r);
+            if (faceRect.left < 0)
+                faceRect.inset(-faceRect.left, -faceRect.left);
+
+            if (faceRect.top < 0)
+                faceRect.inset(-faceRect.top, -faceRect.top);
+
+            if (faceRect.right > imageRect.right)
+                faceRect.inset(faceRect.right - imageRect.right, faceRect.right - imageRect.right);
+
+            if (faceRect.bottom > imageRect.bottom)
+                faceRect.inset(faceRect.bottom - imageRect.bottom, faceRect.bottom - imageRect.bottom);
+
+            hv.setup(mImageMatrix, imageRect, faceRect, mCircleCrop, mAspectX != 0 && mAspectY != 0);
+
+            if (mUnion == null) {
+                mUnion = new RectF(faceRect);
+            } else {
+                mUnion.union(faceRect);
+            }
+
+            mImageView.add(hv);
+        }
+
+        private HighlightView makeHighlightView() {
+            return new HighlightView(mImageView);
+        }
+
+        private void makeDefault() {
+            HighlightView hv = makeHighlightView();
+
+            int width = mBitmap.getWidth();
+            int height = mBitmap.getHeight();
+
+            Rect imageRect = new Rect(0, 0, width, height);
+
+            // make the default size about 4/5 of the width or height
+            int cropWidth = Math.min(width, height) * 4 / 5;
+            int cropHeight = cropWidth;
+
+            if (mAspectX != 0 && mAspectY != 0) {
+                if (mAspectX > mAspectY) {
+                    cropHeight = cropWidth * mAspectY / mAspectX;
+//                    Log.v(TAG, "adjusted cropHeight to " + cropHeight);
+                } else {
+                    cropWidth = cropHeight * mAspectX / mAspectY;
+//                    Log.v(TAG, "adjusted cropWidth to " + cropWidth);
+                }
+            }
+
+            int x = (width - cropWidth) / 2;
+            int y = (height - cropHeight) / 2;
+
+            RectF cropRect = new RectF(x, y, x + cropWidth, y + cropHeight);
+            hv.setup(mImageMatrix, imageRect, cropRect, mCircleCrop, mAspectX != 0 && mAspectY != 0);
+            mImageView.add(hv);
+        }
+
+        private Bitmap prepareBitmap() {
+            if (mBitmap == null)
+                return null;
+
+            // scale the image down for faster face detection
+            // 256 pixels wide is enough.
+            if (mBitmap.getWidth() > 256) {
+                mScale = 256.0F / (float) mBitmap.getWidth();
+            }
+            Matrix matrix = new Matrix();
+            matrix.setScale(mScale, mScale);
+            Bitmap faceBitmap = Bitmap.createBitmap(mBitmap, 0, 0, mBitmap
+                    .getWidth(), mBitmap.getHeight(), matrix, true);
+            return faceBitmap;
+        }
+
+        public void run() {
+            mImageMatrix = mImageView.getImageMatrix();
+            Bitmap faceBitmap = prepareBitmap();
+
+            mScale = 1.0F / mScale;
+            if (faceBitmap != null && mDoFaceDetection) {
+                FaceDetector detector = new FaceDetector(faceBitmap.getWidth(),
+                    faceBitmap.getHeight(), mFaces.length);
+                mNumFaces = detector.findFaces(faceBitmap, mFaces);
+                if (Config.LOGV)
+                    Log.v(TAG, "numFaces is " + mNumFaces);
+            }
+            mHandler.post(new Runnable() {
+                public void run() {
+                    mWaitingToPick = mNumFaces > 1;
+                    if (mNumFaces > 0) {
+                        for (int i = 0; i < mNumFaces; i++) {
+                            handleFace(mFaces[i]);
+                        }
+                    } else {
+                        makeDefault();
+                    }
+                    mImageView.invalidate();
+                    if (mImageView.mHighlightViews.size() == 1) {
+                        mCrop = mImageView.mHighlightViews.get(0);
+                        mCrop.setFocus(true);
+                    }
+
+                    closeProgressDialog();
+
+                    if (mNumFaces > 1) {
+                        Toast t = Toast.makeText(CropImage.this, R.string.multiface_crop_help, Toast.LENGTH_SHORT);
+                        t.show();
+                    }
+                }
+            });
+
+        }
+    };
+
+    @Override
+    public void onStop() {
+        closeProgressDialog();
+        super.onStop();
+        if (mAllImages != null)
+            mAllImages.deactivate();
+    }
+
+    private synchronized void closeProgressDialog() {
+        if (mFaceDetectionDialog != null) {
+            mFaceDetectionDialog.dismiss();
+            mFaceDetectionDialog = null;
+        }
+        if (mSavingProgressDialog != null) {
+            mSavingProgressDialog.dismiss();
+            mSavingProgressDialog = null;
+        }
+    }
+}
diff --git a/src/com/android/camera/DrmWallpaper.java b/src/com/android/camera/DrmWallpaper.java
new file mode 100644
index 0000000..10f33dc
--- /dev/null
+++ b/src/com/android/camera/DrmWallpaper.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2007 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.camera;
+
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+
+/**
+ * Wallpaper picker for DRM images. This just redirects to the standard pick action.
+ */
+public class DrmWallpaper extends Wallpaper {
+
+    protected void formatIntent(Intent intent) {
+        super.formatIntent(intent);
+        intent.putExtra("pick-drm", true);
+    }
+
+}
diff --git a/src/com/android/camera/ErrorScreen.java b/src/com/android/camera/ErrorScreen.java
new file mode 100644
index 0000000..1018eb7
--- /dev/null
+++ b/src/com/android/camera/ErrorScreen.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2006 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.camera;
+
+import android.content.Intent;
+import android.app.Activity;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.os.Bundle;
+import android.widget.TextView;
+
+
+/**
+ *
+ */
+public class ErrorScreen extends Activity
+{
+    int mError;
+    boolean mLogoutOnExit;
+    boolean mReconnectOnExit;
+    Handler mHandler = new Handler();
+    
+    Runnable mCloseScreenCallback = new Runnable() {
+        public void run() {
+            finish();
+        }               
+    };
+    
+    @Override public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        resolveIntent();
+        
+        String errMsg = null;
+        
+        // PENDING: resourcify error messages!
+        
+        switch (mError) {
+            default:
+                errMsg = "You need to setup your Picassa Web account first.";
+            break;
+        }
+        
+        TextView tv = new TextView(this);
+        tv.setText(errMsg);        
+        setContentView(tv);
+    }
+    
+    @Override public void onResume() {
+        super.onResume();
+        
+        mHandler.postAtTime(mCloseScreenCallback,
+                SystemClock.uptimeMillis() + 5000);
+
+    } 
+    
+    @Override public void onStop() {
+        super.onStop();
+        mHandler.removeCallbacks(mCloseScreenCallback);        
+//        startNextActivity();
+    }
+    
+    void resolveIntent() {
+        Intent intent = getIntent();        
+        mError = intent.getIntExtra("error", mError);
+
+        mLogoutOnExit = intent.getBooleanExtra("logout", mLogoutOnExit);
+        mReconnectOnExit = intent.getBooleanExtra("reconnect", mReconnectOnExit);
+    }
+    
+//    void startNextActivity() {
+//        GTalkApp app = GTalkApp.getInstance();
+//
+//        if (mLogoutOnExit) {
+//            app.logout();
+//        }
+//        else if (mReconnectOnExit) {
+//            app.showLogin(false);
+//        }
+//    }
+}
diff --git a/src/com/android/camera/ExifInterface.java b/src/com/android/camera/ExifInterface.java
new file mode 100644
index 0000000..2db021a
--- /dev/null
+++ b/src/com/android/camera/ExifInterface.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2007 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.camera;
+
+import java.util.HashMap;
+import java.util.Iterator;
+
+import android.util.Config;
+import android.util.Log;
+
+// Wrapper for native Exif library
+
+public class ExifInterface {
+
+    private String mFilename;
+
+    // Constants used for the Orientation Exif tag.
+    static final int ORIENTATION_UNDEFINED = 0;
+    static final int ORIENTATION_NORMAL = 1;
+    static final int ORIENTATION_FLIP_HORIZONTAL = 2;   // left right reversed mirror
+    static final int ORIENTATION_ROTATE_180 = 3;
+    static final int ORIENTATION_FLIP_VERTICAL = 4;     // upside down mirror
+    static final int ORIENTATION_TRANSPOSE = 5;         // flipped about top-left <--> bottom-right axis
+    static final int ORIENTATION_ROTATE_90 = 6;         // rotate 90 cw to right it
+    static final int ORIENTATION_TRANSVERSE = 7;        // flipped about top-right <--> bottom-left axis
+    static final int ORIENTATION_ROTATE_270 = 8;        // rotate 270 to right it
+
+    // The Exif tag names
+    static final String TAG_ORIENTATION = "Orientation";
+    static final String TAG_DATE_TIME_ORIGINAL = "DateTimeOriginal";
+    static final String TAG_MAKE = "Make";
+    static final String TAG_MODEL = "Model";
+    static final String TAG_FLASH = "Flash";
+    static final String TAG_IMAGE_WIDTH = "ImageWidth";
+    static final String TAG_IMAGE_LENGTH = "ImageLength";
+
+    static final String TAG_GPS_LATITUDE = "GPSLatitude";
+    static final String TAG_GPS_LONGITUDE = "GPSLongitude";
+    
+    static final String TAG_GPS_LATITUDE_REF = "GPSLatitudeRef";
+    static final String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef";
+
+    private boolean mSavedAttributes = false;
+    private boolean mHasThumbnail = false;
+    private HashMap<String, String> mCachedAttributes = null;
+
+    static {
+        System.loadLibrary("exif");
+    }
+
+    public ExifInterface(String fileName) {
+        mFilename = fileName;
+    }
+
+    /**
+     * Given a HashMap of Exif tags and associated values, an Exif section in the JPG file
+     * is created and loaded with the tag data. saveAttributes() is expensive because it involves
+     * copying all the JPG data from one file to another and deleting the old file and renaming the other.
+     * It's best to collect all the attributes to write and make a single call rather than multiple
+     *  calls for each attribute. You must call "commitChanges()" at some point to commit the changes.
+     */
+    public void saveAttributes(HashMap<String, String> attributes) {
+        // format of string passed to native C code:
+        // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
+        // example: "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
+        StringBuilder sb = new StringBuilder();
+        int size = attributes.size();
+        if (attributes.containsKey("hasThumbnail")) {
+            --size;
+        }
+        sb.append(size + " ");
+        Iterator keyIterator = attributes.keySet().iterator();
+        while (keyIterator.hasNext()) {
+            String key = (String)keyIterator.next();
+            if (key.equals("hasThumbnail")) {
+                continue;       // this is a fake attribute not saved as an exif tag
+            }
+            String val = (String)attributes.get(key);
+            sb.append(key + "=");
+            sb.append(val.length() + " ");
+            sb.append(val);
+        }
+        String s = sb.toString();
+        if (android.util.Config.LOGV)
+            android.util.Log.v("camera", "saving exif data: " + s);
+        saveAttributesNative(mFilename, s);
+        mSavedAttributes = true;
+    }
+
+    /**
+     * Returns a HashMap loaded with the Exif attributes of the file. The key is the standard
+     * tag name and the value is the tag's value: e.g. Model -> Nikon. Numeric values are
+     * returned as strings.
+     */
+    public HashMap<String, String> getAttributes() {
+        if (mCachedAttributes != null) {
+            return mCachedAttributes;
+        }
+        // format of string passed from native C code:
+        // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
+        // example: "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
+        mCachedAttributes = new HashMap<String, String>();
+
+        String attrStr = getAttributesNative(mFilename);
+
+        // get count
+        int ptr = attrStr.indexOf(' ');
+        int count = Integer.parseInt(attrStr.substring(0, ptr));
+        ++ptr;  // skip past the space between item count and the rest of the attributes
+
+        for (int i = 0; i < count; i++) {
+            // extract the attribute name
+            int equalPos = attrStr.indexOf('=', ptr);
+            String attrName = attrStr.substring(ptr, equalPos);
+            ptr = equalPos + 1;     // skip past =
+
+            // extract the attribute value length
+            int lenPos = attrStr.indexOf(' ', ptr);
+            int attrLen = Integer.parseInt(attrStr.substring(ptr, lenPos));
+            ptr = lenPos + 1;       // skip pas the space
+
+            // extract the attribute value
+            String attrValue = attrStr.substring(ptr, ptr + attrLen);
+            ptr += attrLen;
+
+            if (attrName.equals("hasThumbnail")) {
+                mHasThumbnail = attrValue.equalsIgnoreCase("true");
+            } else {
+                mCachedAttributes.put(attrName, attrValue);
+            }
+        }
+        return mCachedAttributes;
+    }
+
+    /**
+     * Given a numerical orientation, return a human-readable string describing the orientation.
+     */
+    static public String orientationToString(int orientation) {
+        // TODO: this function needs to be localized and use string resource ids rather than strings
+        String orientationString;
+        switch (orientation) {
+            case ORIENTATION_NORMAL:            orientationString = "Normal";   break;
+            case ORIENTATION_FLIP_HORIZONTAL:   orientationString = "Flipped horizontal";   break;
+            case ORIENTATION_ROTATE_180:        orientationString = "Rotated 180 degrees";   break;
+            case ORIENTATION_FLIP_VERTICAL:     orientationString = "Upside down mirror";   break;
+            case ORIENTATION_TRANSPOSE:         orientationString = "Transposed";   break;
+            case ORIENTATION_ROTATE_90:         orientationString = "Rotated 90 degrees";   break;
+            case ORIENTATION_TRANSVERSE:        orientationString = "Transversed";   break;
+            case ORIENTATION_ROTATE_270:        orientationString = "Rotated 270 degrees";   break;
+            default:                            orientationString = "Undefined";   break;
+        }
+        return orientationString;
+    }
+
+    /**
+     * Copies the thumbnail data out of the filename and puts it in the Exif data associated
+     * with the file used to create this object. You must call "commitChanges()" at some point
+     * to commit the changes.
+     */
+    public boolean appendThumbnail(String thumbnailFileName) {
+        if (!mSavedAttributes) {
+            throw new RuntimeException("Must call saveAttributes before calling appendThumbnail");
+        }
+        mHasThumbnail = appendThumbnailNative(mFilename, thumbnailFileName);
+        return mHasThumbnail;
+    }
+
+    /**
+     * Saves the changes (added Exif tags, added thumbnail) to the JPG file. You have to call
+     * saveAttributes() before committing the changes.
+     */
+    public void commitChanges() {
+        if (!mSavedAttributes) {
+            throw new RuntimeException("Must call saveAttributes before calling commitChanges");
+        }
+        commitChangesNative(mFilename);
+    }
+
+    public boolean hasThumbnail() {
+        if (!mSavedAttributes) {
+            getAttributes();
+        }
+        return mHasThumbnail;
+    }
+
+    public byte[] getThumbnail() {
+        return getThumbnailNative(mFilename);
+    }
+
+    static public String convertRationalLatLonToDecimalString(String rationalString, String ref, boolean usePositiveNegative) {
+        try {
+            String [] parts = rationalString.split(",");
+
+            String [] pair;
+            pair = parts[0].split("/");
+            int degrees = (int) (Float.parseFloat(pair[0].trim()) / Float.parseFloat(pair[1].trim()));
+
+            pair = parts[1].split("/");
+            int minutes = (int) ((Float.parseFloat(pair[0].trim()) / Float.parseFloat(pair[1].trim())));
+
+            pair = parts[2].split("/");
+            float seconds = Float.parseFloat(pair[0].trim()) / Float.parseFloat(pair[1].trim());
+
+            float result = degrees + (minutes/60F) + (seconds/(60F*60F));
+            
+            String preliminaryResult = String.valueOf(result);
+            if (usePositiveNegative) {
+                String neg = (ref.equals("S") || ref.equals("E")) ? "-" : "";
+                return neg + preliminaryResult;
+            } else {
+                return preliminaryResult + String.valueOf((char)186) + " " + ref; 
+            }
+        } catch (Exception ex) {
+            // if for whatever reason we can't parse the lat long then return null
+            return null;
+        }
+    }
+        
+    static public String makeLatLongString(double d) {
+        d = Math.abs(d);
+        
+        int degrees = (int) d;
+        
+        double remainder = d - (double)degrees;
+        int minutes = (int) (remainder * 60D);
+        int seconds = (int) (((remainder * 60D) - minutes) * 60D * 1000D);  // really seconds * 1000
+        
+        String retVal = degrees + "/1," + minutes + "/1," + (int)seconds + "/1000";
+        return retVal;
+    }
+    
+    static public String makeLatStringRef(double lat) {
+        return lat >= 0D ? "N" : "S";
+    }
+    
+    static public String makeLonStringRef(double lon) {
+        return lon >= 0D ? "W" : "E";
+    }
+
+    private native boolean appendThumbnailNative(String fileName, String thumbnailFileName);
+
+    private native void saveAttributesNative(String fileName, String compressedAttributes);
+
+    private native String getAttributesNative(String fileName);
+
+    private native void commitChangesNative(String fileName);
+
+    private native byte[] getThumbnailNative(String fileName);
+}
diff --git a/src/com/android/camera/GalleryPicker.java b/src/com/android/camera/GalleryPicker.java
new file mode 100644
index 0000000..9c687c8
--- /dev/null
+++ b/src/com/android/camera/GalleryPicker.java
@@ -0,0 +1,728 @@
+/*
+ * Copyright (C) 2007 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.camera;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.StatFs;
+import android.preference.PreferenceManager;
+import android.provider.MediaStore.Images;
+import android.util.Config;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.MenuItem.OnMenuItemClickListener;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.GridView;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+public class GalleryPicker extends Activity {
+    static private final String TAG = "GalleryPicker";
+
+    private View mNoImagesView;
+    GridView mGridView;
+    Drawable mFrameGalleryMask;
+    Drawable mCellOutline;
+    Drawable mVideoOverlay;
+
+    BroadcastReceiver mReceiver;
+    GalleryPickerAdapter mAdapter;
+
+    Dialog mMediaScanningDialog;
+
+    SharedPreferences mPrefs;
+
+    boolean mPausing = false;
+
+    private static long LOW_STORAGE_THRESHOLD = 1024 * 1024 * 2;
+
+    public GalleryPicker() {
+    }
+
+    private void rebake(boolean unmounted, boolean scanning) {
+        if (mMediaScanningDialog != null) {
+            mMediaScanningDialog.cancel();
+            mMediaScanningDialog = null;
+        }
+        if (scanning) {
+            mMediaScanningDialog = ProgressDialog.show(
+                    this,
+                    null,
+                    getResources().getString(R.string.wait),
+                    true,
+                    true);
+        }
+        if (mAdapter != null) {
+            mAdapter.notifyDataSetChanged();
+            mAdapter.init(!unmounted && !scanning);
+        }
+
+        if (!unmounted) {
+            // Warn the user if space is getting low
+            Thread t = new Thread(new Runnable() {
+                public void run() {
+
+                    // Check available space only if we are writable
+                    if (ImageManager.hasStorage()) {
+                        String storageDirectory = Environment.getExternalStorageDirectory().toString();
+                        StatFs stat = new StatFs(storageDirectory);
+                        long remaining = (long)stat.getAvailableBlocks() * (long)stat.getBlockSize();
+                        if (remaining < LOW_STORAGE_THRESHOLD) {
+
+                            mHandler.post(new Runnable() {
+                                public void run() {
+                                    Toast.makeText(GalleryPicker.this.getApplicationContext(),
+                                        R.string.not_enough_space, 5000).show();
+                                }
+                            });
+                        }
+                    }
+                }
+            });
+            t.start();
+        }
+
+        // If we just have zero or one folder, open it. (We shouldn't have just one folder
+        // any more, but we can have zero folders.)
+        mNoImagesView.setVisibility(View.GONE);
+        if (!scanning) {
+            int numItems = mAdapter.mItems.size();
+            if (numItems == 0) {
+                mNoImagesView.setVisibility(View.VISIBLE);
+            } else if (numItems == 1) {
+                mAdapter.mItems.get(0).launch(this);
+                finish();
+                return;
+            }
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+
+        setContentView(R.layout.gallerypicker);
+
+        mNoImagesView = findViewById(R.id.no_images);
+        mGridView = (GridView) findViewById(R.id.albums);
+        mGridView.setSelector(android.R.color.transparent);
+
+        mReceiver = new BroadcastReceiver() {
+
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (Config.LOGV) Log.v(TAG, "onReceiveIntent " + intent.getAction());
+                String action = intent.getAction();
+                if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
+                    // SD card available
+                    // TODO put up a "please wait" message
+                    // TODO also listen for the media scanner finished message
+                } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
+                    // SD card unavailable
+                    if (Config.LOGV) Log.v(TAG, "sd card no longer available");
+                    Toast.makeText(GalleryPicker.this, getResources().getString(R.string.wait), 5000);
+                    rebake(true, false);
+                } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
+                    Toast.makeText(GalleryPicker.this, getResources().getString(R.string.wait), 5000);
+                    rebake(false, true);
+                } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
+                    if (Config.LOGV)
+                        Log.v(TAG, "rebake because of ACTION_MEDIA_SCANNER_FINISHED");
+                    rebake(false, false);
+                } else if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
+                    if (Config.LOGV)
+                        Log.v(TAG, "rebake because of ACTION_MEDIA_EJECT");
+                    rebake(true, false);
+                }
+            }
+        };
+
+        mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                launchFolderGallery(position);
+            }
+        });
+        mGridView.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
+            public void onCreateContextMenu(ContextMenu menu, View v, final ContextMenu.ContextMenuInfo menuInfo) {
+                int position = ((AdapterContextMenuInfo)menuInfo).position;
+                menu.setHeaderTitle(mAdapter.baseTitleForPosition(position));
+                if ((mAdapter.getIncludeMediaTypes(position) & ImageManager.INCLUDE_IMAGES) != 0) {
+                    menu.add(0, 207, 0, R.string.slide_show)
+                    .setOnMenuItemClickListener(new OnMenuItemClickListener() {
+                        public boolean onMenuItemClick(MenuItem item) {
+                            AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo;
+                            int position = info.position;
+
+                            Uri targetUri;
+                            synchronized (mAdapter.mItems) {
+                                if (position < 0 || position >= mAdapter.mItems.size()) {
+                                    return true;
+                                }
+                                // the mFirstImageUris list includes the "all" uri
+                                targetUri = mAdapter.mItems.get(position).mFirstImageUri;
+                            }
+                            if (targetUri != null && position > 0) {
+                                targetUri = targetUri.buildUpon().appendQueryParameter("bucketId",
+                                        mAdapter.mItems.get(info.position).mId).build();
+                            }
+    //                      Log.v(TAG, "URI to launch slideshow " + targetUri);
+                            Intent intent = new Intent(Intent.ACTION_VIEW, targetUri);
+                            intent.putExtra("slideshow", true);
+                            startActivity(intent);
+                            return true;
+                        }
+                    });
+                }
+                menu.add(0, 208, 0, R.string.view)
+                .setOnMenuItemClickListener(new OnMenuItemClickListener() {
+                    public boolean onMenuItemClick(MenuItem item) {
+                        AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo;
+                        launchFolderGallery(info.position);
+                        return true;
+                    }
+                });
+            }
+        });
+ 
+        ImageManager.ensureOSXCompatibleFolder();
+    }
+
+    private void launchFolderGallery(int position) {
+        mAdapter.mItems.get(position).launch(this);
+    }
+
+    class ItemInfo {
+        Bitmap bitmap;
+        int count;
+    }
+
+    static class Item implements Comparable<Item>{
+        // The type is also used as the sort order
+        public final static int TYPE_NONE = -1;
+        public final static int TYPE_ALL_IMAGES = 0;
+        public final static int TYPE_ALL_VIDEOS = 1;
+        public final static int TYPE_CAMERA_IMAGES = 2;
+        public final static int TYPE_CAMERA_VIDEOS = 3;
+        public final static int TYPE_NORMAL_FOLDERS = 4;
+
+        public int mType;
+        public String mId;
+        public String mName;
+        public Uri mFirstImageUri;
+        public ItemInfo mThumb;
+
+        public Item(int type, String id, String name) {
+            mType = type;
+            mId = id;
+            mName = name;
+        }
+
+        public boolean needsBucketId() {
+            return mType >= TYPE_CAMERA_IMAGES;
+        }
+
+        public void launch(Activity activity) {
+            android.net.Uri uri = Images.Media.INTERNAL_CONTENT_URI;
+            if (needsBucketId()) {
+                uri = uri.buildUpon().appendQueryParameter("bucketId",mId).build();
+            }
+            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+            intent.putExtra("windowTitle", mName);
+            intent.putExtra("mediaTypes", getIncludeMediaTypes());
+            activity.startActivity(intent);
+        }
+
+        public int getIncludeMediaTypes() {
+            return convertItemTypeToIncludedMediaType(mType);
+        }
+
+        public static int convertItemTypeToIncludedMediaType(int itemType) {
+            switch (itemType) {
+            case TYPE_ALL_IMAGES:
+            case TYPE_CAMERA_IMAGES:
+                return ImageManager.INCLUDE_IMAGES;
+            case TYPE_ALL_VIDEOS:
+            case TYPE_CAMERA_VIDEOS:
+                return ImageManager.INCLUDE_VIDEOS;
+            case TYPE_NORMAL_FOLDERS:
+            default:
+                return     ImageManager.INCLUDE_IMAGES | ImageManager.INCLUDE_VIDEOS;
+            }
+        }
+
+        public int getOverlay() {
+            switch (mType) {
+            case TYPE_ALL_IMAGES:
+            case TYPE_CAMERA_IMAGES:
+                return R.drawable.frame_overlay_gallery_camera;
+            case TYPE_ALL_VIDEOS:
+            case TYPE_CAMERA_VIDEOS:
+                return R.drawable.frame_overlay_gallery_video;
+            case TYPE_NORMAL_FOLDERS:
+                return R.drawable.frame_overlay_gallery_folder;
+            default:
+                return     -1;
+            }
+        }
+
+        // sort based on the sort order, then the case-insensitive display name, then the id.
+        public int compareTo(Item other) {
+            int x = mType - other.mType;
+            if (x == 0) {
+                x = mName.compareToIgnoreCase(other.mName);
+                if (x == 0) {
+                    x = mId.compareTo(other.mId);
+                }
+            }
+            return x;
+        }
+    }
+
+    class GalleryPickerAdapter extends BaseAdapter {
+        ArrayList<Item> mItems = new ArrayList<Item>();
+
+        boolean mDone = false;
+        CameraThread mWorkerThread;
+
+        public void init(boolean assumeMounted) {
+            mItems.clear();
+
+            ImageManager.IImageList images;
+            if (assumeMounted) {
+                images = ImageManager.instance().allImages(
+                        GalleryPicker.this,
+                        getContentResolver(),
+                        ImageManager.DataLocation.ALL,
+                        ImageManager.INCLUDE_IMAGES | ImageManager.INCLUDE_VIDEOS,
+                        ImageManager.SORT_DESCENDING);
+            } else {
+                images = ImageManager.instance().emptyImageList();
+            }
+
+            if (mWorkerThread != null) {
+                try {
+                    mDone = true;
+                    if (Config.LOGV)
+                        Log.v(TAG, "about to call join on thread " + mWorkerThread.getId());
+                    mWorkerThread.join();
+                } finally {
+                    mWorkerThread = null;
+                }
+            }
+
+            String cameraItem = ImageManager.CAMERA_IMAGE_BUCKET_ID;
+            final HashMap<String, String> hashMap = images.getBucketIds();
+            String cameraBucketId = null;
+            for (Map.Entry<String, String> entry: hashMap.entrySet()) {
+                String key = entry.getKey();
+                if (key == null) {
+                    continue;
+                }
+                if (key.equals(cameraItem)) {
+                    cameraBucketId = key;
+                } else {
+                    mItems.add(new Item(Item.TYPE_NORMAL_FOLDERS, key, entry.getValue()));
+                }
+            }
+            images.deactivate();
+            notifyDataSetInvalidated();
+
+            // Conditionally add all-images and all-videos folders.
+            addBucket(Item.TYPE_ALL_IMAGES, null,
+                    Item.TYPE_CAMERA_IMAGES, cameraBucketId, R.string.all_images);
+            addBucket(Item.TYPE_ALL_VIDEOS, null,
+                    Item.TYPE_CAMERA_VIDEOS, cameraBucketId, R.string.all_videos);
+
+            if (cameraBucketId != null) {
+                addBucket(Item.TYPE_CAMERA_IMAGES, cameraBucketId,
+                        R.string.gallery_camera_bucket_name);
+                addBucket(Item.TYPE_CAMERA_VIDEOS, cameraBucketId,
+                        R.string.gallery_camera_videos_bucket_name);
+            }
+
+            java.util.Collections.sort(mItems);
+
+            mDone = false;
+            mWorkerThread = new CameraThread(new Runnable() {
+                public void run() {
+                    try {
+                        // no images, nothing to do
+                        if (mItems.size() == 0)
+                            return;
+
+                        for (int i = 0; i < mItems.size() && !mDone; i++) {
+                            final Item item = mItems.get(i);
+                            ImageManager.IImageList list = createImageList(
+                                    item.getIncludeMediaTypes(), item.mId);
+                            try {
+                                if (mPausing) {
+                                    break;
+                                }
+                                if (list.getCount() > 0)
+                                    item.mFirstImageUri = list.getImageAt(0).fullSizeImageUri();
+
+                                final Bitmap b = makeMiniThumbBitmap(142, 142, list);
+                                final int pos = i;
+                                final int count = list.getCount();
+                                final Thread currentThread = Thread.currentThread();
+                                mHandler.post(new Runnable() {
+                                    public void run() {
+                                        if (mPausing || currentThread != mWorkerThread.realThread()) {
+                                            if (b != null) {
+                                                b.recycle();
+                                            }
+                                            return;
+                                        }
+
+                                        ItemInfo info = new ItemInfo();
+                                        info.bitmap = b;
+                                        info.count = count;
+                                        item.mThumb = info;
+
+                                        final GridView grid = GalleryPicker.this.mGridView;
+                                        final int firstVisible = grid.getFirstVisiblePosition();
+
+                                        // Minor optimization -- only notify if the specified position is visible
+                                        if ((pos >= firstVisible) && (pos < firstVisible + grid.getChildCount())) {
+                                            GalleryPickerAdapter.this.notifyDataSetChanged();
+                                        }
+                                    }
+                                });
+                            } finally {
+                                list.deactivate();
+                            }
+                        }
+                    } catch (Exception ex) {
+                        Log.e(TAG, "got exception generating collage views ", ex);
+                    }
+                }
+            });
+            mWorkerThread.start();
+            mWorkerThread.toBackground();
+        }
+
+        /**
+         * Add a bucket, but only if it's interesting.
+         * Interesting means non-empty and not duplicated by the
+         * corresponding camera bucket.
+         */
+        private void addBucket(int itemType, String bucketId,
+                int cameraItemType, String cameraBucketId,
+                int labelId) {
+            int itemCount = bucketItemCount(
+                    Item.convertItemTypeToIncludedMediaType(itemType), bucketId);
+            if (itemCount == 0) {
+                return; // Bucket is empty, so don't show it.
+            }
+            int cameraItemCount = 0;
+            if (cameraBucketId != null) {
+                cameraItemCount = bucketItemCount(
+                        Item.convertItemTypeToIncludedMediaType(cameraItemType), cameraBucketId);
+            }
+            if (cameraItemCount == itemCount) {
+                return; // Bucket is the same as the camera bucket, so don't show it.
+            }
+            mItems.add(new Item(itemType, bucketId, getResources().getString(labelId)));
+        }
+
+        /**
+         * Add a bucket, but only if it's interesting.
+         * Interesting means non-empty.
+         */
+        private void addBucket(int itemType, String bucketId,
+                int labelId) {
+            if (!isEmptyBucket(Item.convertItemTypeToIncludedMediaType(itemType), bucketId)) {
+                mItems.add(new Item(itemType, bucketId, getResources().getString(labelId)));
+            }
+        }
+
+        public int getCount() {
+            return mItems.size();
+        }
+
+        public Object getItem(int position) {
+            return null;
+        }
+
+        public long getItemId(int position) {
+            return position;
+        }
+
+        private String baseTitleForPosition(int position) {
+            return mItems.get(position).mName;
+        }
+
+        private int getIncludeMediaTypes(int position) {
+            return mItems.get(position).getIncludeMediaTypes();
+        }
+
+        public View getView(final int position, View convertView, ViewGroup parent) {
+            View v;
+
+            if (convertView == null) {
+                LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+                v = vi.inflate(R.layout.gallery_picker_item, null);
+            } else {
+                v = convertView;
+            }
+
+            TextView titleView = (TextView) v.findViewById(R.id.title);
+
+            GalleryPickerItem iv = (GalleryPickerItem) v.findViewById(R.id.thumbnail);
+            iv.setOverlay(mItems.get(position).getOverlay());
+            ItemInfo info = mItems.get(position).mThumb;
+            if (info != null) {
+                iv.setImageBitmap(info.bitmap);
+                String title = baseTitleForPosition(position) + " (" + info.count + ")";
+                titleView.setText(title);
+            } else {
+                iv.setImageResource(android.R.color.transparent);
+                titleView.setText(baseTitleForPosition(position));
+            }
+
+            return v;
+        }
+    };
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        mPausing = true;
+        unregisterReceiver(mReceiver);
+
+        // free up some ram
+        mAdapter = null;
+        mGridView.setAdapter(null);
+        System.gc();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mPausing = false;
+
+        mAdapter = new GalleryPickerAdapter();
+        mGridView.setAdapter(mAdapter);
+        setBackgrounds(getResources());
+
+        boolean scanning = ImageManager.isMediaScannerScanning(this);
+        rebake(false, scanning);
+
+        // install an intent filter to receive SD card related events.
+        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_EJECT);
+        intentFilter.addDataScheme("file");
+
+        registerReceiver(mReceiver, intentFilter);
+        MenuHelper.requestOrientation(this, mPrefs);
+    }
+
+
+
+    private void setBackgrounds(Resources r) {
+        mFrameGalleryMask = r.getDrawable(R.drawable.frame_gallery_preview_album_mask);
+
+        mCellOutline = r.getDrawable(android.R.drawable.gallery_thumb);
+        mVideoOverlay = r.getDrawable(R.drawable.ic_gallery_video_overlay);
+    }
+
+    Handler mHandler = new Handler();
+
+    private void placeImage(Bitmap image, Canvas c, Paint paint, int imageWidth, int widthPadding, int imageHeight, int heightPadding, int offsetX, int offsetY, int pos) {
+        int row = pos / 2;
+        int col = pos - (row * 2);
+
+        int xPos = (col * (imageWidth + widthPadding)) - offsetX;
+        int yPos = (row * (imageHeight + heightPadding)) - offsetY;
+
+        c.drawBitmap(image, xPos, yPos, paint);
+    }
+
+    private Bitmap makeMiniThumbBitmap(int width, int height, ImageManager.IImageList images) {
+        int count = images.getCount();
+        // We draw three different version of the folder image depending on the number of images in the folder.
+        //    For a single image, that image draws over the whole folder.
+        //    For two or three images, we draw the two most recent photos.
+        //    For four or more images, we draw four photos.
+        final int padding = 4;
+        int imageWidth = width;
+        int imageHeight = height;
+        int offsetWidth = 0;
+        int offsetHeight = 0;
+
+        imageWidth = (imageWidth - padding) / 2;     // 2 here because we show two images
+        imageHeight = (imageHeight - padding) / 2;   // per row and column
+
+        final Paint  p = new Paint();
+        final Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        final Canvas c = new Canvas(b);
+
+        final Matrix m = new Matrix();
+
+        // draw the whole canvas as transparent
+        p.setColor(0x00000000);
+        c.drawPaint(p);
+
+        // draw the mask normally
+        p.setColor(0xFFFFFFFF);
+        mFrameGalleryMask.setBounds(0, 0, width, height);
+        mFrameGalleryMask.draw(c);
+
+        Paint pdpaint = new Paint();
+        pdpaint.setXfermode(new android.graphics.PorterDuffXfermode(
+                                    android.graphics.PorterDuff.Mode.SRC_IN));
+
+        pdpaint.setStyle(Paint.Style.FILL);
+        c.drawRect(0, 0, width, height, pdpaint);
+
+        for (int i = 0; i < 4; i++) {
+            if (mPausing) {
+                return null;
+            }
+
+            Bitmap temp = null;
+            ImageManager.IImage image = i < count ? images.getImageAt(i) : null;
+
+            if (image != null) {
+                temp = image.miniThumbBitmap();
+            }
+
+            if (temp != null) {
+                if (ImageManager.isVideo(image)) {
+                    Bitmap newMap = temp.copy(temp.getConfig(), true);
+                    Canvas overlayCanvas = new Canvas(newMap);
+                    int overlayWidth = mVideoOverlay.getIntrinsicWidth();
+                    int overlayHeight = mVideoOverlay.getIntrinsicHeight();
+                    int left = (newMap.getWidth() - overlayWidth) / 2;
+                    int top = (newMap.getHeight() - overlayHeight) / 2;
+                    Rect newBounds = new Rect(left, top, left + overlayWidth, top + overlayHeight);
+                    mVideoOverlay.setBounds(newBounds);
+                    mVideoOverlay.draw(overlayCanvas);
+                    temp.recycle();
+                    temp = newMap;
+                }
+
+                Bitmap temp2 = ImageLoader.transform(m, temp, imageWidth, imageHeight, true);
+                if (temp2 != temp)
+                    temp.recycle();
+                temp = temp2;
+            }
+
+            Bitmap thumb = Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888);
+            Canvas tempCanvas = new Canvas(thumb);
+            if (temp != null)
+                tempCanvas.drawBitmap(temp, new Matrix(), new Paint());
+            mCellOutline.setBounds(0, 0, imageWidth, imageHeight);
+            mCellOutline.draw(tempCanvas);
+
+            placeImage(thumb, c, pdpaint, imageWidth, padding, imageHeight, padding, offsetWidth, offsetHeight, i);
+
+            thumb.recycle();
+
+            if (temp != null)
+                temp.recycle();
+        }
+
+        return b;
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+
+        MenuHelper.addCaptureMenuItems(menu, this);
+
+        menu.add(0, 0, 5, R.string.camerasettings)
+        .setOnMenuItemClickListener(new OnMenuItemClickListener() {
+            public boolean onMenuItemClick(MenuItem item) {
+                Intent preferences = new Intent();
+                preferences.setClass(GalleryPicker.this, GallerySettings.class);
+                startActivity(preferences);
+                return true;
+            }
+        })
+        .setAlphabeticShortcut('p')
+        .setIcon(android.R.drawable.ic_menu_preferences);
+
+        return true;
+    }
+
+    private boolean isEmptyBucket(int mediaTypes, String bucketId) {
+        // TODO: Find a more efficient way of calculating this
+        ImageManager.IImageList list = createImageList(mediaTypes, bucketId);
+        try {
+            return list.isEmpty();
+        }
+        finally {
+            list.deactivate();
+        }
+    }
+
+    private int bucketItemCount(int mediaTypes, String bucketId) {
+        // TODO: Find a more efficient way of calculating this
+        ImageManager.IImageList list = createImageList(mediaTypes, bucketId);
+        try {
+            return list.getCount();
+        }
+        finally {
+            list.deactivate();
+        }
+    }
+    private ImageManager.IImageList createImageList(int mediaTypes, String bucketId) {
+        return ImageManager.instance().allImages(
+                this,
+                getContentResolver(),
+                ImageManager.DataLocation.ALL,
+                mediaTypes,
+                ImageManager.SORT_DESCENDING,
+                bucketId);
+    }
+}
diff --git a/src/com/android/camera/GalleryPickerItem.java b/src/com/android/camera/GalleryPickerItem.java
new file mode 100644
index 0000000..c3b5df1
--- /dev/null
+++ b/src/com/android/camera/GalleryPickerItem.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2008 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.camera;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+public class GalleryPickerItem extends ImageView {
+
+    private Drawable mFrame;
+    private Rect mFrameBounds = new Rect();
+    private Drawable mOverlay;
+    
+    public GalleryPickerItem(Context context) {
+        this(context, null);
+    }
+    
+    public GalleryPickerItem(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+    
+    public GalleryPickerItem(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        
+        mFrame = getResources().getDrawable(R.drawable.frame_gallery_preview);
+        mFrame.setCallback(this);
+    }
+    
+    @Override
+    protected boolean verifyDrawable(Drawable who) {
+        return super.verifyDrawable(who) || (who == mFrame) || (who == mOverlay);
+    }
+    
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        if (mFrame != null) {
+            int[] drawableState = getDrawableState();
+            mFrame.setState(drawableState);
+        }
+    }
+    
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        final Rect frameBounds = mFrameBounds;
+        if (frameBounds.isEmpty()) {
+            final int w = getWidth();
+            final int h = getHeight();
+            
+            frameBounds.set(0, 0, w, h);
+            mFrame.setBounds(frameBounds);
+            if (mOverlay != null) {
+                mOverlay.setBounds(w - mOverlay.getIntrinsicWidth(), 
+                        h - mOverlay.getIntrinsicHeight(), w, h);
+            }
+        }
+        
+        mFrame.draw(canvas);
+        if (mOverlay != null) {
+            mOverlay.draw(canvas);
+        }
+    }
+    
+    
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        mFrameBounds.setEmpty();
+    }
+
+    public void setOverlay(int overlayId) {
+        if (overlayId >= 0) {
+            mOverlay = getResources().getDrawable(overlayId);
+            mFrameBounds.setEmpty();
+        } else {
+            mOverlay = null;
+        }
+    }
+}
diff --git a/src/com/android/camera/GallerySettings.java b/src/com/android/camera/GallerySettings.java
new file mode 100644
index 0000000..14cff3a
--- /dev/null
+++ b/src/com/android/camera/GallerySettings.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2007 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.camera;
+
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+
+/**
+ *  GallerySettings
+ */
+public class GallerySettings extends PreferenceActivity
+{
+    public GallerySettings()
+    {
+    }
+
+    /** Called with the activity is first created. */
+    @Override
+    public void onCreate(Bundle icicle)
+    {
+        super.onCreate(icicle);
+        addPreferencesFromResource(R.xml.gallery_preferences);
+    }
+}
+
diff --git a/src/com/android/camera/HighlightView.java b/src/com/android/camera/HighlightView.java
new file mode 100644
index 0000000..408beab
--- /dev/null
+++ b/src/com/android/camera/HighlightView.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2007 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.camera;
+
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.graphics.drawable.Drawable;
+import android.util.Config;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+
+
+public class HighlightView
+{
+    private static final String TAG = "CropImage";
+    View mContext;
+    Path mPath;
+    Rect mViewDrawingRect = new Rect();
+
+    int mMotionMode;
+    
+    public static final int GROW_NONE        = (1 << 0);
+    public static final int GROW_LEFT_EDGE   = (1 << 1);
+    public static final int GROW_RIGHT_EDGE  = (1 << 2);
+    public static final int GROW_TOP_EDGE    = (1 << 3);
+    public static final int GROW_BOTTOM_EDGE = (1 << 4);
+    public static final int MOVE             = (1 << 5);
+    
+    public HighlightView(View ctx) 
+    {
+        super();
+        mContext = ctx;
+        mPath = new Path();
+    }
+
+    private void initHighlightView() {
+        android.content.res.Resources resources = mContext.getResources();
+        mResizeDrawableWidth  = resources.getDrawable(R.drawable.camera_crop_width);
+        mResizeDrawableHeight = resources.getDrawable(R.drawable.camera_crop_height);
+        mResizeDrawableDiagonal = resources.getDrawable(R.drawable.indicator_autocrop);
+    }
+    
+    boolean mIsFocused;
+    boolean mHidden;
+    
+    public boolean hasFocus() {
+        return mIsFocused;
+    }
+    
+    public void setFocus(boolean f) {
+        mIsFocused = f;
+    }
+    
+    public void setHidden(boolean hidden) {
+        mHidden = hidden;
+    }
+    
+    protected void draw(Canvas canvas) {
+        if (mHidden)
+            return;
+        
+        canvas.save();
+        mPath.reset();
+        if (!hasFocus()) {
+            mOutlinePaint.setColor(0xFF000000);
+            canvas.drawRect(mDrawRect, mOutlinePaint);
+        } else {
+            mContext.getDrawingRect(mViewDrawingRect);
+            if (mCircle) {
+                float width  = mDrawRect.width()  - (getPaddingLeft() + getPaddingRight());
+                float height = mDrawRect.height() - (getPaddingTop()  + getPaddingBottom());
+                mPath.addCircle(
+                        mDrawRect.left + getPaddingLeft() + (width  / 2), 
+                        mDrawRect.top  + getPaddingTop()  + (height / 2),
+                        width / 2, 
+                        Path.Direction.CW);
+                mOutlinePaint.setColor(0xFFEF04D6);
+            } else {
+                mPath.addRect(new RectF(mDrawRect), Path.Direction.CW);
+                mOutlinePaint.setColor(0xFFFF8A00);
+            }
+            canvas.clipPath(mPath, Region.Op.DIFFERENCE);
+            canvas.drawRect(mViewDrawingRect, hasFocus() ? mFocusPaint : mNoFocusPaint);
+
+            canvas.restore();
+            canvas.drawPath(mPath, mOutlinePaint);
+
+            if (mMode == ModifyMode.Grow) {
+                if (mCircle) {
+                    int width  = mResizeDrawableDiagonal.getIntrinsicWidth();
+                    int height = mResizeDrawableDiagonal.getIntrinsicHeight();
+                    
+                    int d  = (int) Math.round(Math.cos(/*45deg*/Math.PI/4D) * (mDrawRect.width() / 2D));
+                    int x  = mDrawRect.left + (mDrawRect.width() / 2) + d - width/2;
+                    int y  = mDrawRect.top  + (mDrawRect.height() / 2) - d - height/2;
+                    mResizeDrawableDiagonal.setBounds(x, y, x + mResizeDrawableDiagonal.getIntrinsicWidth(), y + mResizeDrawableDiagonal.getIntrinsicHeight());
+                    mResizeDrawableDiagonal.draw(canvas);
+                } else {
+                    int left    = mDrawRect.left   + 1;
+                    int right   = mDrawRect.right  + 1;
+                    int top     = mDrawRect.top    + 4;
+                    int bottom  = mDrawRect.bottom + 3;
+
+                    int widthWidth   = mResizeDrawableWidth.getIntrinsicWidth() / 2;
+                    int widthHeight  = mResizeDrawableWidth.getIntrinsicHeight()/ 2;
+                    int heightHeight = mResizeDrawableHeight.getIntrinsicHeight()/ 2;
+                    int heightWidth  = mResizeDrawableHeight.getIntrinsicWidth()/ 2;
+
+                    int xMiddle = mDrawRect.left + ((mDrawRect.right  - mDrawRect.left) / 2);
+                    int yMiddle = mDrawRect.top  + ((mDrawRect.bottom - mDrawRect.top ) / 2);
+
+                    mResizeDrawableWidth.setBounds(left-widthWidth, yMiddle-widthHeight, left+widthWidth, yMiddle+widthHeight);
+                    mResizeDrawableWidth.draw(canvas);
+
+                    mResizeDrawableWidth.setBounds(right-widthWidth, yMiddle-widthHeight, right+widthWidth, yMiddle+widthHeight);
+                    mResizeDrawableWidth.draw(canvas);
+
+                    mResizeDrawableHeight.setBounds(xMiddle-heightWidth, top-heightHeight, xMiddle+heightWidth, top+heightHeight); 
+                    mResizeDrawableHeight.draw(canvas);
+
+                    mResizeDrawableHeight.setBounds(xMiddle-heightWidth, bottom-heightHeight, xMiddle+heightWidth, bottom+heightHeight); 
+                    mResizeDrawableHeight.draw(canvas);
+                }
+            }
+        }
+    
+    }
+    
+    float getPaddingTop() { return 0F; }
+    float getPaddingBottom() { return 0F; }
+    float getPaddingLeft() { return 0F; }
+    float getPaddingRight() { return 0F; }
+
+    public ModifyMode getMode() {
+        return mMode;
+    }
+    
+    public void setMode(ModifyMode mode)
+    {
+        if (mode != mMode) {
+            mMode = mode;
+            mContext.invalidate();
+        }
+    }
+    
+    public int getHit(float x, float y) {
+        Rect r = computeLayout();
+        final float hysteresis = 20F;
+        int retval = GROW_NONE;
+        
+        if (mCircle) {
+            float distX = x - r.centerX();
+            float distY = y - r.centerY();
+            int distanceFromCenter = (int) Math.sqrt(distX*distX + distY*distY);
+            int radius  = (int) (mDrawRect.width() - getPaddingLeft()) / 2;
+            int delta = distanceFromCenter - radius;
+            if (Math.abs(delta) <= hysteresis) {
+                if (Math.abs(distY) > Math.abs(distX)) {
+                    if (distY < 0)
+                        retval = GROW_TOP_EDGE;
+                    else
+                        retval = GROW_BOTTOM_EDGE;
+                } else {
+                    if (distX < 0)
+                        retval = GROW_LEFT_EDGE;
+                    else
+                        retval = GROW_RIGHT_EDGE;
+                }
+            } else if (distanceFromCenter < radius) {
+                retval = MOVE;
+            } else {
+                retval = GROW_NONE;
+            }
+//          Log.v(TAG, "radius: " + radius + "; touchRadius: " + distanceFromCenter + "; distX: " + distX + "; distY: " + distY + "; retval: " + retval); 
+        } else {
+            boolean verticalCheck = (y >= r.top - hysteresis) && (y < r.bottom + hysteresis);
+            boolean horizCheck = (x >= r.left - hysteresis) && (x < r.right + hysteresis);
+
+            if ((Math.abs(r.left - x)     < hysteresis)  &&  verticalCheck)
+                retval |= GROW_LEFT_EDGE;
+            if ((Math.abs(r.right - x)    < hysteresis)  &&  verticalCheck)
+                retval |= GROW_RIGHT_EDGE;
+            if ((Math.abs(r.top - y)      < hysteresis)  &&  horizCheck)
+                retval |= GROW_TOP_EDGE;
+            if ((Math.abs(r.bottom - y)   < hysteresis)  &&  horizCheck)
+                retval |= GROW_BOTTOM_EDGE;
+
+            if (retval == GROW_NONE && r.contains((int)x, (int)y))
+                retval = MOVE;
+        }
+        return retval;
+    }
+
+    void handleMotion(int edge, float dx, float dy) {
+        Rect r = computeLayout();
+        if (edge == GROW_NONE) {
+            return;
+        } else if (edge == MOVE) {
+            moveBy(dx * (mCropRect.width() / r.width()),
+                   dy * (mCropRect.height() / r.height()));
+        } else {
+            if (((GROW_LEFT_EDGE | GROW_RIGHT_EDGE) & edge) == 0)
+                dx = 0;
+
+            if (((GROW_TOP_EDGE | GROW_BOTTOM_EDGE) & edge) == 0)
+                dy = 0;
+
+            float xDelta = dx * (mCropRect.width() / r.width());
+            float yDelta = dy * (mCropRect.height() / r.height());
+            growBy((((edge & GROW_LEFT_EDGE) != 0) ? -1 : 1) * xDelta,
+                    (((edge & GROW_TOP_EDGE) != 0) ? -1 : 1) * yDelta);
+                   
+        }
+//        Log.v(TAG, "ratio is now " + this.mCropRect.width() / this.mCropRect.height());
+    }
+    
+    void moveBy(float dx, float dy) {
+        Rect invalRect = new Rect(mDrawRect);
+
+        mCropRect.offset(dx, dy);
+        mCropRect.offset(
+                Math.max(0, mImageRect.left - mCropRect.left), 
+                Math.max(0, mImageRect.top  - mCropRect.top));
+
+        mCropRect.offset(
+                Math.min(0, mImageRect.right  - mCropRect.right), 
+                Math.min(0, mImageRect.bottom - mCropRect.bottom));                
+
+        mDrawRect = computeLayout();
+        invalRect.union(mDrawRect);
+        invalRect.inset(-10, -10);
+        mContext.invalidate(invalRect);
+    }
+    
+    private void shift(RectF r, float dx, float dy) {
+        r.left   += dx;
+        r.right  += dx;
+        r.top    += dy;
+        r.bottom += dy;
+    }
+
+    void growBy(float dx, float dy) {
+//      Log.v(TAG, "growBy: " + dx + " " + dy + "; rect w/h is " + mCropRect.width() + " / " + mCropRect.height());
+        if (mMaintainAspectRatio) {
+            if (dx != 0) {
+                dy = dx / mInitialAspectRatio;
+            } else if (dy != 0) {
+                dx = dy * mInitialAspectRatio;
+            }
+        }
+
+        RectF r = new RectF(mCropRect);
+        if (dx > 0F && r.width() + 2 * dx > mImageRect.width()) {
+            float adjustment = (mImageRect.width() - r.width()) / 2F;
+            dx = adjustment;
+            if (mMaintainAspectRatio)
+                dy = dx / mInitialAspectRatio;
+        }
+        if (dy > 0F && r.height() + 2 * dy > mImageRect.height()) {
+            float adjustment = (mImageRect.height() - r.height()) / 2F;
+            dy = adjustment;
+            if (mMaintainAspectRatio)
+                dx = dy * mInitialAspectRatio;
+        }
+
+        r.inset(-dx, -dy);
+        
+        float widthCap = 25F;
+        if (r.width() < 25) {
+            r.inset(-(25F-r.width())/2F, 0F);
+        }
+        float heightCap = mMaintainAspectRatio ? (widthCap / mInitialAspectRatio) : widthCap;
+        if (r.height() < heightCap) {
+            r.inset(0F, -(heightCap-r.height())/2F);
+        }
+        
+        if (r.left < mImageRect.left) {
+            shift(r, mImageRect.left - r.left, 0F);
+        } else if (r.right > mImageRect.right) {
+            shift(r, -(r.right - mImageRect.right), 0);
+        }
+        if (r.top < mImageRect.top) {
+            shift(r, 0F, mImageRect.top - r.top);
+        } else if (r.bottom > mImageRect.bottom) {
+            shift(r, 0F, -(r.bottom - mImageRect.bottom));
+        }
+/*        
+        RectF rCandidate = new RectF(r);
+        r.intersect(mImageRect);
+        if (mMaintainAspectRatio) {
+            if (r.left != rCandidate.left) {
+                Log.v(TAG, "bail 1");
+                return;
+            }
+            if (r.right != rCandidate.right) {
+                Log.v(TAG, "bail 2");
+                return;
+            }
+            if (r.top != rCandidate.top) {
+                Log.v(TAG, "bail 3");
+                return;
+            }
+            if (r.bottom != rCandidate.bottom) {
+                Log.v(TAG, "bail 4");
+                return;
+            }
+        }
+*/        
+        mCropRect.set(r);
+        mDrawRect = computeLayout();
+        mContext.invalidate();
+    }
+    
+    public Rect getCropRect() {
+        return new Rect((int)mCropRect.left, (int)mCropRect.top, (int)mCropRect.right, (int)mCropRect.bottom);
+    }
+    
+    private Rect computeLayout() {
+        RectF r = new RectF(mCropRect.left, mCropRect.top, mCropRect.right, mCropRect.bottom);
+        mMatrix.mapRect(r);
+        return new Rect(Math.round(r.left), Math.round(r.top), Math.round(r.right), Math.round(r.bottom));
+    }
+    
+    public void invalidate() {
+        mDrawRect = computeLayout();
+    }
+    
+    public void setup(Matrix m, Rect imageRect, RectF cropRect, boolean circle, boolean maintainAspectRatio) {
+        if (Config.LOGV) Log.v(TAG, "setup... " + imageRect + "; " + cropRect + "; maintain " + maintainAspectRatio + "; circle " + circle);
+        if (circle)
+            maintainAspectRatio = true;
+        mMatrix = new Matrix(m);
+
+        mCropRect = cropRect;
+        mImageRect = new RectF(imageRect);
+        mMaintainAspectRatio = maintainAspectRatio;
+        mCircle = circle;
+
+        mInitialAspectRatio = mCropRect.width() / mCropRect.height();
+        mDrawRect = computeLayout();
+        
+        mFocusPaint.setARGB(125, 50, 50, 50);
+        mNoFocusPaint.setARGB(125, 50, 50, 50);
+        mOutlinePaint.setStrokeWidth(3F);
+        mOutlinePaint.setStyle(Paint.Style.STROKE);
+        mOutlinePaint.setAntiAlias(true);
+
+        mMode = ModifyMode.None;
+        initHighlightView();
+    }
+    
+    public void modify(int keyCode, long repeatCount)
+    {
+        float factor = Math.max(.01F, Math.min(.1F, repeatCount * .01F));  
+        float widthUnits = factor * (float)mContext.getWidth();
+        float heightUnits = widthUnits;
+
+        switch (keyCode)
+        {       
+        case KeyEvent.KEYCODE_DPAD_LEFT:
+            if (mMode == ModifyMode.Move)
+                moveBy(-widthUnits, 0);
+            else if (mMode == ModifyMode.Grow)
+                growBy(-widthUnits, 0);
+            break;
+        
+        case KeyEvent.KEYCODE_DPAD_RIGHT:
+            if (mMode == ModifyMode.Move)
+                moveBy(widthUnits, 0);
+            else if (mMode == ModifyMode.Grow)
+                growBy(widthUnits, 0);
+            break;
+    
+        case KeyEvent.KEYCODE_DPAD_UP:
+            if (mMode == ModifyMode.Move)
+                moveBy(0, -heightUnits);
+            else if (mMode == ModifyMode.Grow)
+                growBy(0, -heightUnits);
+            break;
+
+        case KeyEvent.KEYCODE_DPAD_DOWN:
+            if (mMode == ModifyMode.Move)
+                moveBy(0, heightUnits);
+            else if (mMode == ModifyMode.Grow)
+                growBy(0, heightUnits);
+            break;
+        }
+    }
+    
+    enum ModifyMode { None, Move,Grow };
+    
+    ModifyMode mMode = ModifyMode.None;
+    
+    Rect  mDrawRect;
+    RectF mImageRect;
+    RectF mCropRect;
+    Matrix mMatrix;
+
+    boolean mMaintainAspectRatio = false;
+    float mInitialAspectRatio;
+    boolean mCircle = false;
+    
+    Drawable mResizeDrawableWidth, mResizeDrawableHeight, mResizeDrawableDiagonal;
+
+    Paint mFocusPaint = new Paint();
+    Paint mNoFocusPaint = new Paint();
+    Paint mOutlinePaint = new Paint();
+}
diff --git a/src/com/android/camera/ImageGallery2.java b/src/com/android/camera/ImageGallery2.java
new file mode 100644
index 0000000..008eb21
--- /dev/null
+++ b/src/com/android/camera/ImageGallery2.java
@@ -0,0 +1,1848 @@
+/*
+ * Copyright (C) 2007 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.camera;
+
+import android.content.BroadcastReceiver;
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.util.AttributeSet;
+import android.util.Config;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.GestureDetector;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.Window;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.preference.PreferenceManager;
+import android.provider.MediaStore;
+import android.widget.Scroller;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+import com.android.camera.ImageManager.IImage;
+
+public class ImageGallery2 extends Activity {
+    private static final String TAG = "ImageGallery2";
+    private ImageManager.IImageList mAllImages;
+    private int mInclusion;
+    private boolean mSortAscending = false;
+    private View mNoImagesView;
+    public final static int CROP_MSG = 2;
+    public final static int VIEW_MSG = 3;
+    private static final String INSTANCE_STATE_TAG = "scrollY";
+
+    private Dialog mMediaScanningDialog;
+
+    private MenuItem mSlideShowItem;
+    private SharedPreferences mPrefs;
+    private long mVideoSizeLimit = Long.MAX_VALUE;
+
+    public ImageGallery2() {
+    }
+
+    BroadcastReceiver mReceiver = null;
+
+    Handler mHandler = new Handler();
+    boolean mLayoutComplete;
+    boolean mPausing = false;
+    boolean mStopThumbnailChecking = false;
+
+    CameraThread mThumbnailCheckThread;
+    GridViewSpecial mGvs;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        if (Config.LOGV) Log.v(TAG, "onCreate");
+        super.onCreate(icicle);
+
+        mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+
+        requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);  // must be called before setContentView()
+        setContentView(R.layout.image_gallery_2);
+
+        getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_gallery_title);
+        if (Config.LOGV)
+            Log.v(TAG, "findView... " + findViewById(R.id.loading_indicator));
+
+        mGvs = (GridViewSpecial) findViewById(R.id.grid);
+        mGvs.requestFocus();
+
+        if (isPickIntent()) {
+            mVideoSizeLimit = getIntent().getLongExtra(
+                    MediaStore.EXTRA_SIZE_LIMIT, Long.MAX_VALUE);
+            mGvs.mVideoSizeLimit = mVideoSizeLimit;
+        } else {
+            mVideoSizeLimit = Long.MAX_VALUE;
+            mGvs.mVideoSizeLimit = mVideoSizeLimit;
+            mGvs.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
+                public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+                    if (mSelectedImageGetter.getCurrentImage() == null)
+                        return;
+
+                    boolean isImage = ImageManager.isImage(mSelectedImageGetter.getCurrentImage());
+                    if (isImage) {
+                        menu.add(0, 0, 0, R.string.view).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+                            public boolean onMenuItemClick(MenuItem item) {
+                                mGvs.onSelect(mGvs.mCurrentSelection);
+                                return true;
+                            }
+                        });
+                    }
+
+                    menu.setHeaderTitle(isImage ? R.string.context_menu_header
+                            : R.string.video_context_menu_header);
+                    if ((mInclusion & (ImageManager.INCLUDE_IMAGES | ImageManager.INCLUDE_VIDEOS)) != 0) {
+                        MenuHelper.MenuItemsResult r = MenuHelper.addImageMenuItems(
+                                menu,
+                                MenuHelper.INCLUDE_ALL,
+                                isImage,
+                                ImageGallery2.this,
+                                mHandler,
+                                mDeletePhotoRunnable,
+                                new MenuHelper.MenuInvoker() {
+                                    public void run(MenuHelper.MenuCallback cb) {
+                                        cb.run(mSelectedImageGetter.getCurrentImageUri(), mSelectedImageGetter.getCurrentImage());
+
+                                        mGvs.clearCache();
+                                        mGvs.invalidate();
+                                        mGvs.requestLayout();
+                                        mGvs.start();
+                                        mNoImagesView.setVisibility(mAllImages.getCount() > 0 ? View.GONE : View.VISIBLE);
+                                    }
+                                });
+                        if (r != null)
+                            r.gettingReadyToOpen(menu, mSelectedImageGetter.getCurrentImage());
+
+                        if (isImage) {
+                            addSlideShowMenu(menu, 1000);
+                        }
+                    }
+                }
+            });
+        }
+    }
+
+    private MenuItem addSlideShowMenu(Menu menu, int position) {
+        return menu.add(0, 207, position, R.string.slide_show)
+        .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+            public boolean onMenuItemClick(MenuItem item) {
+                ImageManager.IImage img = mSelectedImageGetter.getCurrentImage();
+                if (img == null) {
+                    img = mAllImages.getImageAt(0);
+                    if (img == null) {
+                        return true;
+                    }
+                }
+                Uri targetUri = img.fullSizeImageUri();
+                Uri thisUri = getIntent().getData();
+                if (thisUri != null) {
+                    String bucket = thisUri.getQueryParameter("bucketId");
+                    if (bucket != null) {
+                        targetUri = targetUri.buildUpon().appendQueryParameter("bucketId", bucket).build();
+                    }
+                }
+                Intent intent = new Intent(Intent.ACTION_VIEW, targetUri);
+                intent.putExtra("slideshow", true);
+                startActivity(intent);
+                return true;
+            }
+        })
+        .setIcon(android.R.drawable.ic_menu_slideshow);
+    }
+
+    private Runnable mDeletePhotoRunnable = new Runnable() {
+        public void run() {
+            mGvs.clearCache();
+            IImage currentImage = mSelectedImageGetter.getCurrentImage();
+            if (currentImage != null) {
+                mAllImages.removeImage(currentImage);
+            }
+            mGvs.invalidate();
+            mGvs.requestLayout();
+            mGvs.start();
+            mNoImagesView.setVisibility(mAllImages.isEmpty() ? View.VISIBLE : View.GONE);
+        }
+    };
+
+    private SelectedImageGetter mSelectedImageGetter = new SelectedImageGetter() {
+        public Uri getCurrentImageUri() {
+            ImageManager.IImage image = getCurrentImage();
+            if (image != null)
+                return image.fullSizeImageUri();
+            else
+                return null;
+        }
+        public ImageManager.IImage getCurrentImage() {
+            int currentSelection = mGvs.mCurrentSelection;
+            if (currentSelection < 0 || currentSelection >= mAllImages.getCount())
+                return null;
+            else
+                return mAllImages.getImageAt(currentSelection);
+        }
+    };
+
+    @Override
+    public void onConfigurationChanged(android.content.res.Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mTargetScroll = mGvs.getScrollY();
+    }
+
+    private Runnable mLongPressCallback = new Runnable() {
+        public void run() {
+            mGvs.select(-2, false);
+            mGvs.showContextMenu();
+        }
+    };
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
+            mGvs.select(-2, false);
+            // The keyUp doesn't get called when the longpress menu comes up. We only get here when the user
+            // lets go of the center key before the longpress menu comes up.
+            mHandler.removeCallbacks(mLongPressCallback);
+
+            // open the photo
+            if (mSelectedImageGetter.getCurrentImage() != null) {
+                mGvs.onSelect(mGvs.mCurrentSelection);
+            }
+            return true;
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        boolean handled = true;
+        int sel = mGvs.mCurrentSelection;
+        int columns = mGvs.mCurrentSpec.mColumns;
+        int count = mAllImages.getCount();
+        boolean pressed = false;
+        if (mGvs.mShowSelection) {
+            switch (keyCode) {
+                case KeyEvent.KEYCODE_DPAD_RIGHT:
+                    if (sel != count && (sel % columns < columns - 1)) {
+                        sel += 1;
+                    }
+                    break;
+                case KeyEvent.KEYCODE_DPAD_LEFT:
+                    if (sel > 0 && (sel % columns != 0)) {
+                        sel -= 1;
+                    }
+                    break;
+                case KeyEvent.KEYCODE_DPAD_UP:
+                    if ((sel / columns) != 0) {
+                        sel -= columns;
+                    }
+                    break;
+                case KeyEvent.KEYCODE_DPAD_DOWN:
+                    if ((sel / columns) != (sel+columns / columns)) {
+                        sel = Math.min(count-1, sel + columns);
+                    }
+                    break;
+                case KeyEvent.KEYCODE_DPAD_CENTER:
+                    pressed = true;
+                    mHandler.postDelayed(mLongPressCallback, ViewConfiguration.getLongPressTimeout());
+                    break;
+                case KeyEvent.KEYCODE_DEL:
+                    MenuHelper.deleteImage(this, mDeletePhotoRunnable,
+                            mSelectedImageGetter.getCurrentImage());
+                    break;
+                default:
+                    handled = false;
+                    break;
+            }
+        } else {
+            switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+            case KeyEvent.KEYCODE_DPAD_UP:
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+                int [] range = new int[2];
+                GridViewSpecial.ImageBlockManager ibm = mGvs.mImageBlockManager;
+                if (ibm != null) {
+                    mGvs.mImageBlockManager.getVisibleRange(range);
+                    int topPos = range[0];
+                    android.graphics.Rect r = mGvs.getRectForPosition(topPos);
+                    if (r.top < mGvs.getScrollY())
+                        topPos += columns;
+                    topPos = Math.min(count - 1, topPos);
+                    sel = topPos;
+                }
+                break;
+            default:
+                handled = false;
+                break;
+            }
+        }
+        if (handled) {
+            mGvs.select(sel, pressed);
+            return true;
+        }
+        else
+            return super.onKeyDown(keyCode, event);
+    }
+
+    private boolean isPickIntent() {
+        String action = getIntent().getAction();
+        return (Intent.ACTION_PICK.equals(action) || Intent.ACTION_GET_CONTENT.equals(action));
+    }
+
+    private void launchCropperOrFinish(ImageManager.IImage img) {
+        Bundle myExtras = getIntent().getExtras();
+
+        if (MenuHelper.getImageFileSize(img) > mVideoSizeLimit) {
+
+            DialogInterface.OnClickListener buttonListener =
+                    new DialogInterface.OnClickListener() {
+                public void onClick(DialogInterface dialog, int which) {
+                    dialog.dismiss();
+                }
+            };
+            new AlertDialog.Builder(this)
+                    .setIcon(android.R.drawable.ic_dialog_info)
+                    .setTitle(R.string.file_info_title)
+                    .setMessage(R.string.video_exceed_mms_limit)
+                    .setNeutralButton(R.string.details_ok, buttonListener)
+                    .show();
+            return;
+        }
+
+        String cropValue = myExtras != null ? myExtras.getString("crop") : null;
+        if (cropValue != null) {
+            Bundle newExtras = new Bundle();
+            if (cropValue.equals("circle"))
+                newExtras.putString("circleCrop", "true");
+
+            Intent cropIntent = new Intent();
+            cropIntent.setData(img.fullSizeImageUri());
+            cropIntent.setClass(this, CropImage.class);
+            cropIntent.putExtras(newExtras);
+
+            /* pass through any extras that were passed in */
+            cropIntent.putExtras(myExtras);
+            if (Config.LOGV) Log.v(TAG, "startSubActivity " + cropIntent);
+            startActivityForResult(cropIntent, CROP_MSG);
+        } else {
+            Intent result = new Intent(null, img.fullSizeImageUri());
+            if (myExtras != null && myExtras.getString("return-data") != null) {
+                Bitmap bitmap = img.fullSizeBitmap(1000);
+                if (bitmap != null)
+                    result.putExtra("data", bitmap);
+            }
+            setResult(RESULT_OK, result);
+            finish();
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (Config.LOGV)
+            Log.v(TAG, "onActivityResult: " + requestCode + "; resultCode is " + resultCode + "; data is " + data);
+        switch (requestCode) {
+            case MenuHelper.RESULT_COMMON_MENU_CROP: {
+                if (resultCode == RESULT_OK) {
+                    // The CropImage activity passes back the Uri of the cropped image as
+                    // the Action rather than the Data.
+                    Uri dataUri = Uri.parse(data.getAction());
+                    rebake(false,false);
+                    IImage image = mAllImages.getImageForUri(dataUri);
+                    if (image != null ) {
+                        int rowId = image.getRow();
+                        mGvs.select(rowId, false);
+                    }
+                }
+                break;
+            }
+            case CROP_MSG: {
+                if (Config.LOGV) Log.v(TAG, "onActivityResult " + data);
+                if (resultCode == RESULT_OK) {
+                    setResult(resultCode, data);
+                    finish();
+                }
+                break;
+            }
+            case VIEW_MSG: {
+                if (Config.LOGV)
+                    Log.v(TAG, "got VIEW_MSG with " + data);
+                ImageManager.IImage img = mAllImages.getImageForUri(data.getData());
+                launchCropperOrFinish(img);
+                break;
+            }
+        }
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        mPausing = true;
+        stopCheckingThumbnails();
+        mGvs.onPause();
+
+        if (mReceiver != null) {
+            unregisterReceiver(mReceiver);
+            mReceiver = null;
+        }
+        // Now that we've paused the threads that are using the cursor it is safe
+        // to deactivate it.
+        mAllImages.deactivate();
+    }
+
+    private void rebake(boolean unmounted, boolean scanning) {
+        stopCheckingThumbnails();
+        mGvs.clearCache();
+        if (mAllImages != null) {
+            mAllImages.deactivate();
+            mAllImages = null;
+        }
+        if (mMediaScanningDialog != null) {
+            mMediaScanningDialog.cancel();
+            mMediaScanningDialog = null;
+        }
+        if (scanning) {
+            mMediaScanningDialog = ProgressDialog.show(
+                    this,
+                    null,
+                    getResources().getString(R.string.wait),
+                    true,
+                    true);
+            mAllImages = ImageManager.instance().emptyImageList();
+        } else {
+            mAllImages = allImages(!unmounted);
+            if (Config.LOGV)
+                Log.v(TAG, "mAllImages is now " + mAllImages);
+            mGvs.init(mHandler);
+            mGvs.start();
+            mGvs.requestLayout();
+            checkThumbnails();
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle state) {
+        super.onSaveInstanceState(state);
+        mTargetScroll = mGvs.getScrollY();
+        state.putInt(INSTANCE_STATE_TAG, mTargetScroll);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle state) {
+        super.onRestoreInstanceState(state);
+        mTargetScroll = state.getInt(INSTANCE_STATE_TAG, 0);
+    }
+
+    int mTargetScroll;
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        try {
+            mGvs.setSizeChoice(Integer.parseInt(mPrefs.getString("pref_gallery_size_key", "1")), mTargetScroll);
+
+            String sortOrder = mPrefs.getString("pref_gallery_sort_key", null);
+            if (sortOrder != null) {
+                mSortAscending = sortOrder.equals("ascending");
+            }
+        } catch (Exception ex) {
+
+        }
+        mPausing = false;
+
+        // install an intent filter to receive SD card related events.
+        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_EJECT);
+        intentFilter.addDataScheme("file");
+
+        mReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (Config.LOGV) Log.v(TAG, "onReceiveIntent " + intent.getAction());
+                String action = intent.getAction();
+                if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
+                    // SD card available
+                    // TODO put up a "please wait" message
+                    // TODO also listen for the media scanner finished message
+                } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
+                    // SD card unavailable
+                    if (Config.LOGV) Log.v(TAG, "sd card no longer available");
+                    Toast.makeText(ImageGallery2.this, getResources().getString(R.string.wait), 5000);
+                    rebake(true, false);
+                } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
+                    Toast.makeText(ImageGallery2.this, getResources().getString(R.string.wait), 5000);
+                    rebake(false, true);
+                } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
+                    if (Config.LOGV)
+                        Log.v(TAG, "rebake because of ACTION_MEDIA_SCANNER_FINISHED");
+                    rebake(false, false);
+                } else if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
+                    if (Config.LOGV)
+                        Log.v(TAG, "rebake because of ACTION_MEDIA_EJECT");
+                    rebake(true, false);
+                }
+            }
+        };
+        registerReceiver(mReceiver, intentFilter);
+
+        MenuHelper.requestOrientation(this, mPrefs);
+
+        rebake(false, ImageManager.isMediaScannerScanning(this));
+    }
+
+    private void stopCheckingThumbnails() {
+        mStopThumbnailChecking = true;
+        if (mThumbnailCheckThread != null) {
+            mThumbnailCheckThread.join();
+        }
+        mStopThumbnailChecking = false;
+    }
+
+    private void checkThumbnails() {
+        final long startTime = System.currentTimeMillis();
+        final long t1 = System.currentTimeMillis();
+        mThumbnailCheckThread = new CameraThread(new Runnable() {
+            public void run() {
+                android.content.res.Resources resources = getResources();
+                final TextView progressTextView = (TextView) findViewById(R.id.loading_text);
+                final String progressTextFormatString = resources.getString(R.string.loading_progress_format_string);
+
+                PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
+                PowerManager.WakeLock mWakeLock =
+                    pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK,
+                                   "ImageGallery2.checkThumbnails");
+                mWakeLock.acquire();
+                ImageManager.IImageList.ThumbCheckCallback r = new ImageManager.IImageList.ThumbCheckCallback() {
+                    boolean mDidSetProgress = false;
+
+                    public boolean checking(final int count, final int maxCount) {
+                        if (mStopThumbnailChecking) {
+                            return false;
+                        }
+
+                        if (!mLayoutComplete) {
+                            return true;
+                        }
+
+                        if (!mDidSetProgress) {
+                            mHandler.post(new Runnable() {
+                                public void run() {
+                                    findViewById(R.id.loading_indicator).setVisibility(View.VISIBLE);
+                                }
+                            });
+                            mDidSetProgress = true;
+                        }
+                        mGvs.postInvalidate();
+
+                        if (System.currentTimeMillis() - startTime > 1000) {
+                            mHandler.post(new Runnable() {
+                                public void run() {
+                                    String s = String.format(progressTextFormatString, maxCount - count);
+                                    progressTextView.setText(s);
+                                }
+                            });
+                        }
+
+                        return !mPausing;
+                    }
+                };
+                ImageManager.IImageList imageList = allImages(true);
+                imageList.checkThumbnails(r, imageList.getCount());
+                mWakeLock.release();
+                mThumbnailCheckThread = null;
+                mHandler.post(new Runnable() {
+                    public void run() {
+                        findViewById(R.id.loading_indicator).setVisibility(View.GONE);
+                    }
+                });
+                long t2 = System.currentTimeMillis();
+                if (Config.LOGV)
+                    Log.v(TAG, "check thumbnails thread finishing; took " + (t2-t1));
+            }
+        });
+
+        mThumbnailCheckThread.setName("check_thumbnails");
+        mThumbnailCheckThread.start();
+        mThumbnailCheckThread.toBackground();
+
+        ImageManager.IImageList list = allImages(true);
+        mNoImagesView.setVisibility(list.getCount() > 0 ? View.GONE : View.VISIBLE);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(android.view.Menu menu) {
+        MenuItem item;
+        if (! isPickIntent()) {
+            MenuHelper.addCaptureMenuItems(menu, this);
+            if ((mInclusion & ImageManager.INCLUDE_IMAGES) != 0) {
+                mSlideShowItem = addSlideShowMenu(menu, 5);
+
+            }
+        }
+
+        item = menu.add(0, 0, 1000, R.string.camerasettings);
+        item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+            public boolean onMenuItemClick(MenuItem item) {
+                Intent preferences = new Intent();
+                preferences.setClass(ImageGallery2.this, GallerySettings.class);
+                startActivity(preferences);
+                return true;
+            }
+        });
+        item.setAlphabeticShortcut('p');
+        item.setIcon(android.R.drawable.ic_menu_preferences);
+
+        return true;
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu(android.view.Menu menu) {
+        if ((mInclusion & ImageManager.INCLUDE_IMAGES) != 0) {
+            boolean videoSelected = isVideoSelected();
+            // TODO: Only enable slide show if there is at least one image in the folder.
+            if (mSlideShowItem != null) {
+                mSlideShowItem.setEnabled(!videoSelected);
+            }
+        }
+
+        return true;
+    }
+
+    private boolean isImageSelected() {
+        IImage image = mSelectedImageGetter.getCurrentImage();
+        return (image != null) && ImageManager.isImage(image);
+    }
+
+    private boolean isVideoSelected() {
+        IImage image = mSelectedImageGetter.getCurrentImage();
+        return (image != null) && ImageManager.isVideo(image);
+    }
+
+    private synchronized ImageManager.IImageList allImages(boolean assumeMounted) {
+        if (mAllImages == null) {
+            mNoImagesView = findViewById(R.id.no_images);
+
+            mInclusion = ImageManager.INCLUDE_IMAGES | ImageManager.INCLUDE_VIDEOS;
+
+            Intent intent = getIntent();
+            if (intent != null) {
+                String type = intent.resolveType(this);
+                if (Config.LOGV)
+                    Log.v(TAG, "allImages... type is " + type);
+                TextView leftText = (TextView) findViewById(R.id.left_text);
+                if (type != null) {
+                    if (type.equals("vnd.android.cursor.dir/image") || type.equals("image/*")) {
+                        mInclusion = ImageManager.INCLUDE_IMAGES;
+                        if (isPickIntent())
+                            leftText.setText(R.string.pick_photos_gallery_title);
+                        else
+                            leftText.setText(R.string.photos_gallery_title);
+                    }
+                    if (type.equals("vnd.android.cursor.dir/video") || type.equals("video/*")) {
+                        mInclusion = ImageManager.INCLUDE_VIDEOS;
+                        if (isPickIntent())
+                            leftText.setText(R.string.pick_videos_gallery_title);
+                        else
+                            leftText.setText(R.string.videos_gallery_title);
+                    }
+                }
+                Bundle extras = intent.getExtras();
+                String title = extras!= null ? extras.getString("windowTitle") : null;
+                if (title != null && title.length() > 0) {
+                    leftText.setText(title);
+                }
+
+                if (extras != null) {
+                    mInclusion = (ImageManager.INCLUDE_IMAGES | ImageManager.INCLUDE_VIDEOS)
+                        & extras.getInt("mediaTypes", mInclusion);
+                }
+
+                if (extras != null && extras.getBoolean("pick-drm")) {
+                    Log.d(TAG, "pick-drm is true");
+                    mInclusion = ImageManager.INCLUDE_DRM_IMAGES;
+                }
+            }
+            if (Config.LOGV)
+                Log.v(TAG, "computing images... mSortAscending is " + mSortAscending
+                        + "; assumeMounted is " + assumeMounted);
+            Uri uri = getIntent().getData();
+            if (!assumeMounted) {
+                mAllImages = ImageManager.instance().emptyImageList();
+            } else {
+                mAllImages = ImageManager.instance().allImages(
+                        ImageGallery2.this,
+                        getContentResolver(),
+                        ImageManager.DataLocation.NONE,
+                        mInclusion,
+                        mSortAscending ? ImageManager.SORT_ASCENDING : ImageManager.SORT_DESCENDING,
+                        uri != null ? uri.getQueryParameter("bucketId") : null);
+            }
+        }
+        return mAllImages;
+    }
+
+    public static class GridViewSpecial extends View {
+        private ImageGallery2 mGallery;
+        private Paint   mGridViewPaint = new Paint();
+
+        private ImageBlockManager mImageBlockManager;
+        private Handler mHandler;
+
+        private LayoutSpec mCurrentSpec;
+        private boolean mShowSelection = false;
+        private int mCurrentSelection = -1;
+        private boolean mCurrentSelectionPressed;
+
+        private boolean mDirectionBiasDown = true;
+        private final static boolean sDump = false;
+
+        private long mVideoSizeLimit;
+
+        class LayoutSpec {
+            LayoutSpec(int cols, int w, int h, int leftEdgePadding, int rightEdgePadding, int intercellSpacing) {
+                mColumns = cols;
+                mCellWidth = w;
+                mCellHeight = h;
+                mLeftEdgePadding = leftEdgePadding;
+                mRightEdgePadding = rightEdgePadding;
+                mCellSpacing = intercellSpacing;
+            }
+            int mColumns;
+            int mCellWidth, mCellHeight;
+            int mLeftEdgePadding, mRightEdgePadding;
+            int mCellSpacing;
+        };
+
+        private LayoutSpec [] mCellSizeChoices = new LayoutSpec[] {
+                new LayoutSpec(0, 67, 67, 14, 14, 8),
+                new LayoutSpec(0, 92, 92, 14, 14, 8),
+        };
+        private int mSizeChoice = 1;
+
+        // Use a number like 100 or 200 here to allow the user to
+        // overshoot the start (top) or end (bottom) of the gallery.
+        // After overshooting the gallery will animate back to the
+        // appropriate location.
+        private int mMaxOvershoot = 0; // 100;
+        private int mMaxScrollY;
+        private int mMinScrollY;
+
+        private boolean mFling = true;
+        private Scroller mScroller = null;
+
+        private GestureDetector mGestureDetector;
+
+        public void dump() {
+            if (Config.LOGV){
+                Log.v(TAG, "mSizeChoice is " + mCellSizeChoices[mSizeChoice]);
+                Log.v(TAG, "mCurrentSpec.width / mCellHeight are " + mCurrentSpec.mCellWidth + " / " + mCurrentSpec.mCellHeight);
+            }
+            mImageBlockManager.dump();
+        }
+
+        private void init(Context context) {
+            mGridViewPaint.setColor(0xFF000000);
+            mGallery = (ImageGallery2) context;
+
+            setVerticalScrollBarEnabled(true);
+            initializeScrollbars(context.obtainStyledAttributes(android.R.styleable.View));
+
+            mGestureDetector = new GestureDetector(context, new SimpleOnGestureListener() {
+                @Override
+                public boolean onDown(MotionEvent e) {
+                    if (mScroller != null && !mScroller.isFinished()) {
+                        mScroller.forceFinished(true);
+                        return false;
+                    }
+
+                    int pos = computeSelectedIndex(e);
+                    if (pos >= 0 && pos < mGallery.mAllImages.getCount()) {
+                        select(pos, true);
+                    } else {
+                        select(-1, false);
+                    }
+                    if (mImageBlockManager != null)
+                        mImageBlockManager.repaintSelection(mCurrentSelection);
+                    invalidate();
+                    return true;
+                }
+
+                @Override
+                public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+                    final float maxVelocity = 2500;
+                    if (velocityY > maxVelocity)
+                        velocityY = maxVelocity;
+                    else if (velocityY < -maxVelocity)
+                        velocityY = -maxVelocity;
+
+                    select(-1, false);
+                    if (mFling) {
+                        mScroller = new Scroller(getContext());
+                        mScroller.fling(0, mScrollY, 0, -(int)velocityY, 0, 0, 0, mMaxScrollY);
+                        computeScroll();
+                    }
+                    return true;
+                }
+
+                @Override
+                public void onLongPress(MotionEvent e) {
+                    performLongClick();
+                }
+
+                @Override
+                public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+                    select(-1, false);
+                    scrollBy(0, (int)distanceY);
+                    invalidate();
+                    return true;
+                }
+
+                @Override
+                public void onShowPress(MotionEvent e) {
+                    super.onShowPress(e);
+                }
+
+                @Override
+                public boolean onSingleTapUp(MotionEvent e) {
+                    select(mCurrentSelection, false);
+                    int index = computeSelectedIndex(e);
+                    if (index >= 0 && index < mGallery.mAllImages.getCount()) {
+                        onSelect(index);
+                        return true;
+                    }
+                    return false;
+                }
+            });
+//          mGestureDetector.setIsLongpressEnabled(false);
+        }
+
+        public GridViewSpecial(Context context, AttributeSet attrs, int defStyle) {
+            super(context, attrs, defStyle);
+            init(context);
+        }
+
+        public GridViewSpecial(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            init(context);
+        }
+
+        public GridViewSpecial(Context context) {
+            super(context);
+            init(context);
+        }
+
+        @Override
+        protected int computeVerticalScrollRange() {
+            return mMaxScrollY + getHeight();
+        }
+
+        public void setSizeChoice(int choice, int scrollY) {
+            mSizeChoice = choice;
+            clearCache();
+            scrollTo(0, scrollY);
+            requestLayout();
+            invalidate();
+        }
+
+        /**
+         *
+         * @param newSel -2 means use old selection, -1 means remove selection
+         * @param newPressed
+         */
+        public void select(int newSel, boolean newPressed) {
+            if (newSel == -2) {
+                newSel = mCurrentSelection;
+            }
+            int oldSel = mCurrentSelection;
+            if ((oldSel == newSel) && (mCurrentSelectionPressed == newPressed))
+                return;
+
+            mShowSelection = (newSel != -1);
+            mCurrentSelection = newSel;
+            mCurrentSelectionPressed = newPressed;
+            if (mImageBlockManager != null) {
+                mImageBlockManager.repaintSelection(oldSel);
+                mImageBlockManager.repaintSelection(newSel);
+            }
+
+            if (newSel != -1)
+                ensureVisible(newSel);
+        }
+
+        private void ensureVisible(int pos) {
+            android.graphics.Rect r = getRectForPosition(pos);
+            int top = getScrollY();
+            int bot = top + getHeight();
+
+            if (r.bottom > bot) {
+                mScroller = new Scroller(getContext());
+                mScroller.startScroll(mScrollX, mScrollY, 0, r.bottom - getHeight() - mScrollY, 200);
+                computeScroll();
+            } else if (r.top < top) {
+                mScroller = new Scroller(getContext());
+                mScroller.startScroll(mScrollX, mScrollY, 0, r.top - mScrollY, 200);
+                computeScroll();
+            }
+            invalidate();
+        }
+
+        public void start() {
+            if (mGallery.mLayoutComplete) {
+                if (mImageBlockManager == null) {
+                    mImageBlockManager = new ImageBlockManager();
+                    mImageBlockManager.moveDataWindow(true, true);
+                }
+            }
+        }
+
+        public void onPause() {
+            mScroller = null;
+            if (mImageBlockManager != null) {
+                mImageBlockManager.onPause();
+                mImageBlockManager = null;
+            }
+        }
+
+        public void clearCache() {
+            if (mImageBlockManager != null) {
+                mImageBlockManager.onPause();
+                mImageBlockManager = null;
+            }
+        }
+
+
+        @Override
+        public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+            super.onLayout(changed, left, top, right, bottom);
+
+            if (mGallery.isFinishing() || mGallery.mPausing) {
+                return;
+            }
+
+            clearCache();
+
+            mCurrentSpec = mCellSizeChoices[mSizeChoice];
+            int oldColumnCount = mCurrentSpec.mColumns;
+
+            int width = right - left;
+            mCurrentSpec.mColumns = 1;
+            width -= mCurrentSpec.mCellWidth;
+            mCurrentSpec.mColumns += width / (mCurrentSpec.mCellWidth + mCurrentSpec.mCellSpacing);
+
+            mCurrentSpec.mLeftEdgePadding = ((right - left) - ((mCurrentSpec.mColumns - 1) * mCurrentSpec.mCellSpacing) - (mCurrentSpec.mColumns * mCurrentSpec.mCellWidth)) / 2;
+            mCurrentSpec.mRightEdgePadding = mCurrentSpec.mLeftEdgePadding;
+
+            int rows = (mGallery.mAllImages.getCount() + mCurrentSpec.mColumns - 1) / mCurrentSpec.mColumns;
+            mMaxScrollY = mCurrentSpec.mCellSpacing + (rows * (mCurrentSpec.mCellSpacing + mCurrentSpec.mCellHeight)) - (bottom - top) + mMaxOvershoot;
+            mMinScrollY = 0 - mMaxOvershoot;
+
+            mGallery.mLayoutComplete = true;
+
+            start();
+
+            if (mGallery.mSortAscending && mGallery.mTargetScroll == 0) {
+                scrollTo(0, mMaxScrollY - mMaxOvershoot);
+            } else {
+                if (oldColumnCount != 0) {
+                    int y = mGallery.mTargetScroll * oldColumnCount / mCurrentSpec.mColumns;
+                    Log.v(TAG, "target was " + mGallery.mTargetScroll + " now " + y);
+                    scrollTo(0, y);
+                }
+            }
+        }
+
+        Bitmap scaleTo(int width, int height, Bitmap b) {
+            Matrix m = new Matrix();
+            m.setScale((float)width/64F, (float)height/64F);
+            Bitmap b2 = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, false);
+            if (b2 != b)
+                b.recycle();
+            return b2;
+        }
+
+        private class ImageBlockManager {
+            private ImageLoader mLoader;
+            private int mBlockCacheFirstBlockNumber = 0;
+
+            // mBlockCache is an array with a starting point which is not necessaryily
+            // zero.  The first element of the array is indicated by mBlockCacheStartOffset.
+            private int mBlockCacheStartOffset = 0;
+            private ImageBlock [] mBlockCache;
+
+            private static final int sRowsPerPage    = 6;   // should compute this
+
+            private static final int sPagesPreCache  = 2;
+            private static final int sPagesPostCache = 2;
+
+            private int mWorkCounter = 0;
+            private boolean mDone = false;
+
+            private Thread mWorkerThread;
+            private Bitmap mMissingImageThumbnailBitmap;
+            private Bitmap mMissingVideoThumbnailBitmap;
+
+            private Drawable mVideoOverlay;
+            private Drawable mVideoMmsErrorOverlay;
+
+            public void dump() {
+                synchronized (ImageBlockManager.this) {
+                    StringBuilder line1 = new StringBuilder();
+                    StringBuilder line2 = new StringBuilder();
+                    if (Config.LOGV)
+                        Log.v(TAG, ">>> mBlockCacheFirstBlockNumber: " + mBlockCacheFirstBlockNumber + " " + mBlockCacheStartOffset);
+                    for (int i = 0; i < mBlockCache.length; i++) {
+                        int index = (mBlockCacheStartOffset + i) % mBlockCache.length;
+                        ImageBlock block = mBlockCache[index];
+                        block.dump(line1, line2);
+                    }
+                    if (Config.LOGV){
+                        Log.v(TAG, line1.toString());
+                        Log.v(TAG, line2.toString());
+                    }
+                }
+            }
+
+            ImageBlockManager() {
+                mLoader = new ImageLoader(mHandler, 1);
+
+                mBlockCache = new ImageBlock[sRowsPerPage * (sPagesPreCache + sPagesPostCache + 1)];
+                for (int i = 0; i < mBlockCache.length; i++) {
+                    mBlockCache[i] = new ImageBlock();
+                }
+
+                mWorkerThread = new Thread(new Runnable() {
+                    public void run() {
+                        while (true) {
+                            int workCounter;
+                            synchronized (ImageBlockManager.this) {
+                                workCounter = mWorkCounter;
+                            }
+                            if (mDone) {
+                                if (Config.LOGV)
+                                    Log.v(TAG, "stopping the loader here " + Thread.currentThread().getName());
+                                if (mLoader != null) {
+                                    mLoader.stop();
+                                }
+                                if (mBlockCache != null) {
+                                    for (int i = 0; i < mBlockCache.length; i++) {
+                                        ImageBlock block = mBlockCache[i];
+                                        if (block != null) {
+                                            block.recycleBitmaps();
+                                            mBlockCache[i] = null;
+                                        }
+                                    }
+                                }
+                                mBlockCache = null;
+                                mBlockCacheStartOffset = 0;
+                                mBlockCacheFirstBlockNumber = 0;
+
+                                break;
+                            }
+
+                            loadNext();
+
+                            synchronized (ImageBlockManager.this) {
+                                if ((workCounter == mWorkCounter) && (! mDone)) {
+                                    try {
+                                        ImageBlockManager.this.wait();
+                                    } catch (InterruptedException ex) {
+                                    }
+                                }
+                            }
+                        }
+                    }
+                });
+                mWorkerThread.setName("image-block-manager");
+                mWorkerThread.start();
+            }
+
+            // Create this bitmap lazily, and only once for all the ImageBlocks to use
+            public Bitmap getErrorBitmap(ImageManager.IImage image) {
+                if (ImageManager.isImage(image)) {
+                    if (mMissingImageThumbnailBitmap == null) {
+                        mMissingImageThumbnailBitmap = BitmapFactory.decodeResource(GridViewSpecial.this.getResources(),
+                                R.drawable.ic_missing_thumbnail_picture);
+                    }
+                    return mMissingImageThumbnailBitmap;
+                } else {
+                    if (mMissingVideoThumbnailBitmap == null) {
+                        mMissingVideoThumbnailBitmap = BitmapFactory.decodeResource(GridViewSpecial.this.getResources(),
+                                R.drawable.ic_missing_thumbnail_video);
+                    }
+                    return mMissingVideoThumbnailBitmap;
+                }
+            }
+
+            private ImageBlock getBlockForPos(int pos) {
+                synchronized (ImageBlockManager.this) {
+                    int blockNumber = pos / mCurrentSpec.mColumns;
+                    int delta = blockNumber - mBlockCacheFirstBlockNumber;
+                    if (delta >= 0 && delta < mBlockCache.length) {
+                        int index = (mBlockCacheStartOffset + delta) % mBlockCache.length;
+                        ImageBlock b = mBlockCache[index];
+                        return b;
+                    }
+                }
+                return null;
+            }
+
+            private void repaintSelection(int pos) {
+                synchronized (ImageBlockManager.this) {
+                    ImageBlock b = getBlockForPos(pos);
+                    if (b != null) {
+                        b.repaintSelection();
+                    }
+                }
+            }
+
+            private void onPause() {
+                synchronized (ImageBlockManager.this) {
+                    mDone = true;
+                    ImageBlockManager.this.notify();
+                }
+                if (mWorkerThread != null) {
+                    try {
+                        mWorkerThread.join();
+                        mWorkerThread = null;
+                    } catch (InterruptedException ex) {
+                        //
+                    }
+                }
+                Log.v(TAG, "/ImageBlockManager.onPause");
+            }
+
+            private void getVisibleRange(int [] range) {
+                // try to work around a possible bug in the VM wherein this appears to be null
+                try {
+                    synchronized (ImageBlockManager.this) {
+                        int blockLength = mBlockCache.length;
+                        boolean lookingForStart = true;
+                        ImageBlock prevBlock = null;
+                        for (int i = 0; i < blockLength; i++) {
+                            int index = (mBlockCacheStartOffset + i) % blockLength;
+                            ImageBlock block = mBlockCache[index];
+                            if (lookingForStart) {
+                                if (block.mIsVisible) {
+                                    range[0] = block.mBlockNumber * mCurrentSpec.mColumns;
+                                    lookingForStart = false;
+                                }
+                            } else {
+                                if (!block.mIsVisible || i == blockLength - 1) {
+                                    range[1] = (prevBlock.mBlockNumber * mCurrentSpec.mColumns) + mCurrentSpec.mColumns - 1;
+                                    break;
+                                }
+                            }
+                            prevBlock = block;
+                        }
+                    }
+                } catch (NullPointerException ex) {
+                    Log.e(TAG, "this is somewhat null, what up?");
+                    range[0] = range[1] = 0;
+                }
+            }
+
+            private void loadNext() {
+                final int blockHeight = (mCurrentSpec.mCellSpacing + mCurrentSpec.mCellHeight);
+
+                final int firstVisBlock = Math.max(0, (mScrollY - mCurrentSpec.mCellSpacing) / blockHeight);
+                final int lastVisBlock  = (mScrollY - mCurrentSpec.mCellSpacing + getHeight()) / blockHeight;
+
+//              Log.v(TAG, "firstVisBlock == " + firstVisBlock + "; lastVisBlock == " + lastVisBlock);
+
+                synchronized (ImageBlockManager.this) {
+                    ImageBlock [] blocks = mBlockCache;
+                    int numBlocks = blocks.length;
+                    if (mDirectionBiasDown) {
+                        int first = (mBlockCacheStartOffset + (firstVisBlock - mBlockCacheFirstBlockNumber)) % blocks.length;
+                        for (int i = 0; i < numBlocks; i++) {
+                            int j = first + i;
+                            if (j >= numBlocks)
+                                j -= numBlocks;
+                            ImageBlock b = blocks[j];
+                            if (b.startLoading() > 0)
+                                break;
+                        }
+                    } else {
+                        int first = (mBlockCacheStartOffset + (lastVisBlock - mBlockCacheFirstBlockNumber)) % blocks.length;
+                        for (int i = 0; i < numBlocks; i++) {
+                            int j = first - i;
+                            if (j < 0)
+                                j += numBlocks;
+                            ImageBlock b = blocks[j];
+                            if (b.startLoading() > 0)
+                                break;
+                        }
+                    }
+                    if (sDump)
+                        this.dump();
+                }
+            }
+
+            private void moveDataWindow(boolean directionBiasDown, boolean forceRefresh) {
+                final int blockHeight = (mCurrentSpec.mCellSpacing + mCurrentSpec.mCellHeight);
+
+                final int firstVisBlock = (mScrollY - mCurrentSpec.mCellSpacing) / blockHeight;
+                final int lastVisBlock  = (mScrollY - mCurrentSpec.mCellSpacing + getHeight()) / blockHeight;
+
+                final int preCache = sPagesPreCache;
+                final int startBlock = Math.max(0, firstVisBlock - (preCache * sRowsPerPage));
+
+//              Log.v(TAG, "moveDataWindow directionBiasDown == " + directionBiasDown + "; preCache is " + preCache);
+                synchronized (ImageBlockManager.this) {
+                    boolean any = false;
+                    ImageBlock [] blocks = mBlockCache;
+                    int numBlocks = blocks.length;
+
+                    int delta = startBlock - mBlockCacheFirstBlockNumber;
+
+                    mBlockCacheFirstBlockNumber = startBlock;
+                    if (Math.abs(delta) > numBlocks || forceRefresh) {
+                        for (int i = 0; i < numBlocks; i++) {
+                            int blockNum = startBlock + i;
+                            blocks[i].setStart(blockNum);
+                            any = true;
+                        }
+                        mBlockCacheStartOffset = 0;
+                    } else if (delta > 0) {
+                        mBlockCacheStartOffset += delta;
+                        if (mBlockCacheStartOffset >= numBlocks)
+                            mBlockCacheStartOffset -= numBlocks;
+
+                        for (int i = delta; i > 0; i--) {
+                            int index = (mBlockCacheStartOffset + numBlocks - i) % numBlocks;
+                            int blockNum = mBlockCacheFirstBlockNumber + numBlocks - i;
+                            blocks[index].setStart(blockNum);
+                            any = true;
+                        }
+                    } else if (delta < 0) {
+                        mBlockCacheStartOffset += delta;
+                        if (mBlockCacheStartOffset < 0)
+                            mBlockCacheStartOffset += numBlocks;
+
+                        for (int i = 0; i < -delta; i++) {
+                            int index = (mBlockCacheStartOffset + i) % numBlocks;
+                            int blockNum = mBlockCacheFirstBlockNumber + i;
+                            blocks[index].setStart(blockNum);
+                            any = true;
+                        }
+                    }
+
+                    for (int i = 0; i < numBlocks; i++) {
+                        int index = (mBlockCacheStartOffset + i) % numBlocks;
+                        ImageBlock block = blocks[index];
+                        int blockNum = block.mBlockNumber; // mBlockCacheFirstBlockNumber + i;
+                        boolean isVis = blockNum >= firstVisBlock && blockNum <= lastVisBlock;
+//                      Log.v(TAG, "blockNum " + blockNum + " setting vis to " + isVis);
+                        block.setVisibility(isVis);
+                    }
+
+                    if (sDump)
+                        mImageBlockManager.dump();
+
+                    if (any) {
+                        ImageBlockManager.this.notify();
+                        mWorkCounter += 1;
+                    }
+                }
+                if (sDump)
+                    dump();
+            }
+
+            private void check() {
+                ImageBlock [] blocks = mBlockCache;
+                int blockLength = blocks.length;
+
+                // check the results
+                for (int i = 0; i < blockLength; i++) {
+                    int index = (mBlockCacheStartOffset + i) % blockLength;
+                    if (blocks[index].mBlockNumber != mBlockCacheFirstBlockNumber + i) {
+                        if (blocks[index].mBlockNumber != -1)
+                            Log.e(TAG, "at " + i + " block cache corrupted; found " + blocks[index].mBlockNumber + " but wanted " + (mBlockCacheFirstBlockNumber + i) + "; offset is " + mBlockCacheStartOffset);
+                    }
+                }
+                if (true) {
+                    StringBuilder sb  = new StringBuilder();
+                    for (int i = 0; i < blockLength; i++) {
+                        int index = (mBlockCacheStartOffset + i) % blockLength;
+                        ImageBlock b = blocks[index];
+                        if (b.mRequestedMask != 0)
+                            sb.append("X");
+                        else
+                            sb.append(String.valueOf(b.mBlockNumber) + " ");
+                    }
+                    if (Config.LOGV)
+                        Log.v(TAG, "moveDataWindow " + sb.toString());
+                }
+            }
+
+            void doDraw(Canvas canvas) {
+                synchronized (ImageBlockManager.this) {
+                    ImageBlockManager.ImageBlock [] blocks = mBlockCache;
+                    int blockCount = 0;
+
+                    if (blocks[0] == null) {
+                        return;
+                    }
+
+                    final int thisHeight = getHeight();
+                    final int thisWidth  = getWidth();
+                    final int height = blocks[0].mBitmap.getHeight();
+                    final int scrollPos = mScrollY;
+
+                    int currentBlock = (scrollPos < 0) ? ((scrollPos-height+1) / height) : (scrollPos / height);
+
+                    while (true) {
+                        final int yPos = currentBlock * height;
+                        if (yPos >= scrollPos + thisHeight)
+                            break;
+
+                        if (currentBlock < 0) {
+                            canvas.drawRect(0, yPos, thisWidth, 0, mGridViewPaint);
+                            currentBlock += 1;
+                            continue;
+                        }
+                        int effectiveOffset = (mBlockCacheStartOffset + (currentBlock++ - mBlockCacheFirstBlockNumber)) % blocks.length;
+                        if (effectiveOffset < 0 || effectiveOffset >= blocks.length) {
+                            break;
+                        }
+
+                        ImageBlock block = blocks[effectiveOffset];
+                        if (block == null) {
+                            break;
+                        }
+                        synchronized (block) {
+                            Bitmap b = block.mBitmap;
+                            if (b == null) {
+                                break;
+                            }
+                            canvas.drawBitmap(b, 0, yPos, mGridViewPaint);
+                            blockCount += 1;
+                        }
+                    }
+                }
+            }
+
+            int blockHeight() {
+                return mCurrentSpec.mCellSpacing + mCurrentSpec.mCellHeight;
+            }
+
+            private class ImageBlock {
+                Drawable mCellOutline;
+                Bitmap mBitmap = Bitmap.createBitmap(getWidth(), blockHeight(),
+                        Bitmap.Config.RGB_565);;
+                Canvas mCanvas = new Canvas(mBitmap);
+                Paint mPaint = new Paint();
+
+                int     mBlockNumber;
+                int     mRequestedMask;   // columns which have been requested to the loader
+                int     mCompletedMask;   // columns which have been completed from the loader
+                boolean mIsVisible;
+
+                public void dump(StringBuilder line1, StringBuilder line2) {
+                    synchronized (ImageBlock.this) {
+//                        Log.v(TAG, "block " + mBlockNumber + " isVis == " + mIsVisible);
+                        line2.append(mCompletedMask != 0xF ? 'L' : '_');
+                        line1.append(mIsVisible ? 'V' : ' ');
+                    }
+                }
+
+                ImageBlock() {
+                    mPaint.setTextSize(14F);
+                    mPaint.setStyle(Paint.Style.FILL);
+
+                    mBlockNumber = -1;
+                    mCellOutline = GridViewSpecial.this.getResources().getDrawable(android.R.drawable.gallery_thumb);
+                }
+
+                private void recycleBitmaps() {
+                    synchronized (ImageBlock.this) {
+                        mBitmap.recycle();
+                        mBitmap = null;
+                    }
+                }
+
+                private void cancelExistingRequests() {
+                    synchronized (ImageBlock.this) {
+                        for (int i = 0; i < mCurrentSpec.mColumns; i++) {
+                            int mask = (1 << i);
+                            if ((mRequestedMask & mask) != 0) {
+                                int pos = (mBlockNumber * mCurrentSpec.mColumns) + i;
+                                if (mLoader.cancel(mGallery.mAllImages.getImageAt(pos))) {
+                                    mRequestedMask &= ~mask;
+                                }
+                            }
+                        }
+                    }
+                }
+
+                private void setStart(final int blockNumber) {
+                    synchronized (ImageBlock.this) {
+                        if (blockNumber == mBlockNumber)
+                            return;
+
+                        cancelExistingRequests();
+
+                        mBlockNumber = blockNumber;
+                        mRequestedMask = 0;
+                        mCompletedMask = 0;
+                        mCanvas.drawColor(0xFF000000);
+                        mPaint.setColor(0xFFDDDDDD);
+                        int imageNumber = blockNumber * mCurrentSpec.mColumns;
+                        int lastImageNumber = mGallery.mAllImages.getCount() - 1;
+
+                        int spacing = mCurrentSpec.mCellSpacing;
+                        int leftSpacing = mCurrentSpec.mLeftEdgePadding;
+
+                        final int yPos = spacing;
+
+                        for (int col = 0; col < mCurrentSpec.mColumns; col++) {
+                            if (imageNumber++ >= lastImageNumber)
+                                break;
+                            final int xPos = leftSpacing + (col * (mCurrentSpec.mCellWidth + spacing));
+                            mCanvas.drawRect(xPos, yPos, xPos+mCurrentSpec.mCellWidth, yPos+mCurrentSpec.mCellHeight, mPaint);
+                            paintSel(0, xPos, yPos);
+                        }
+                    }
+                }
+
+                private boolean setVisibility(boolean isVis) {
+                    synchronized (ImageBlock.this) {
+                        boolean retval = mIsVisible != isVis;
+                        mIsVisible = isVis;
+                        return retval;
+                    }
+                }
+
+                private int startLoading() {
+                    synchronized (ImageBlock.this) {
+                        final int startRow = mBlockNumber;
+                        int count = mGallery.mAllImages.getCount();
+
+                        if (startRow == -1)
+                            return 0;
+
+                        if ((startRow * mCurrentSpec.mColumns) >= count) {
+                            return 0;
+                        }
+
+                        int retVal = 0;
+                        int base = (mBlockNumber * mCurrentSpec.mColumns);
+                        for (int col = 0; col < mCurrentSpec.mColumns; col++) {
+                            if ((mCompletedMask & (1 << col)) != 0) {
+                                continue;
+                            }
+
+                            int spacing = mCurrentSpec.mCellSpacing;
+                            int leftSpacing = mCurrentSpec.mLeftEdgePadding;
+                            final int yPos = spacing;
+                            final int xPos = leftSpacing + (col * (mCurrentSpec.mCellWidth + spacing));
+
+                            int pos = base + col;
+                            if (pos >= count)
+                                break;
+
+                            ImageManager.IImage image = mGallery.mAllImages.getImageAt(pos);
+                            if (image != null) {
+//                              Log.v(TAG, "calling loadImage " + (base + col));
+                                loadImage(base, col, image, xPos, yPos);
+                                retVal += 1;
+                            }
+                        }
+                        return retVal;
+
+                    }
+                }
+
+                Bitmap resizeBitmap(Bitmap b) {
+                    // assume they're both square for now
+                    if (b == null || (b.getWidth() == mCurrentSpec.mCellWidth && b.getHeight() == mCurrentSpec.mCellHeight)) {
+                        return b;
+                    }
+                    float scale = (float) mCurrentSpec.mCellWidth / (float)b.getWidth();
+                    Matrix m = new Matrix();
+                    m.setScale(scale, scale, b.getWidth(), b.getHeight());
+                    Bitmap b2 = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, false);
+                    return b2;
+                }
+
+                private void drawBitmap(ImageManager.IImage image, int base, int baseOffset, Bitmap b, int xPos, int yPos) {
+                    mCanvas.setBitmap(mBitmap);
+                    if (b != null) {
+                        // if the image is close to the target size then crop, otherwise scale
+                        // both the bitmap and the view should be square but I suppose that could
+                        // change in the future.
+                        int w = mCurrentSpec.mCellWidth;
+                        int h = mCurrentSpec.mCellHeight;
+
+                        int bw = b.getWidth();
+                        int bh = b.getHeight();
+
+                        int deltaW = bw - w;
+                        int deltaH = bh - h;
+
+                        if (deltaW < 10 && deltaH < 10) {
+                            int halfDeltaW = deltaW / 2;
+                            int halfDeltaH = deltaH / 2;
+                            android.graphics.Rect src = new android.graphics.Rect(0+halfDeltaW, 0+halfDeltaH, bw-halfDeltaW, bh-halfDeltaH);
+                            android.graphics.Rect dst = new android.graphics.Rect(xPos, yPos, xPos+w, yPos+h);
+                            if (src.width() != dst.width() || src.height() != dst.height()) {
+                                if (Config.LOGV){
+                                    Log.v(TAG, "nope... width doesn't match " + src.width() + " " + dst.width());
+                                    Log.v(TAG, "nope... height doesn't match " + src.height() + " " + dst.height());
+                                }
+                            }
+                            mCanvas.drawBitmap(b, src, dst, mPaint);
+                        } else {
+                            android.graphics.Rect src = new android.graphics.Rect(0, 0, bw, bh);
+                            android.graphics.Rect dst = new android.graphics.Rect(xPos, yPos, xPos+w, yPos+h);
+                            mCanvas.drawBitmap(b, src, dst, mPaint);
+                        }
+                    } else {
+                        // If the thumbnail cannot be drawn, put up an error icon instead
+                        Bitmap error = mImageBlockManager.getErrorBitmap(image);
+                        int width = error.getWidth();
+                        int height = error.getHeight();
+                        Rect source = new Rect(0, 0, width, height);
+                        int left = (mCurrentSpec.mCellWidth - width) / 2 + xPos;
+                        int top = (mCurrentSpec.mCellHeight - height) / 2 + yPos;
+                        Rect dest = new Rect(left, top, left + width, top + height);
+                        mCanvas.drawBitmap(error, source, dest, mPaint);
+                    }
+                    if (ImageManager.isVideo(image)) {
+                        Drawable overlay = null;
+                        if (MenuHelper.getImageFileSize(image) <= mVideoSizeLimit) {
+                            if (mVideoOverlay == null) {
+                                mVideoOverlay = getResources().getDrawable(
+                                        R.drawable.ic_gallery_video_overlay);
+                            }
+                            overlay = mVideoOverlay;
+                        } else {
+                            if (mVideoMmsErrorOverlay == null) {
+                                mVideoMmsErrorOverlay = getResources().getDrawable(
+                                        R.drawable.ic_error_mms_video_overlay);
+                            }
+                            overlay = mVideoMmsErrorOverlay;
+                            Paint paint = new Paint();
+                            paint.setARGB(0x80, 0x00, 0x00, 0x00);
+                            mCanvas.drawRect(xPos, yPos, xPos + mCurrentSpec.mCellWidth,
+                                    yPos + mCurrentSpec.mCellHeight, paint);
+                        }
+                        int width = overlay.getIntrinsicWidth();
+                        int height = overlay.getIntrinsicHeight();
+                        int left = (mCurrentSpec.mCellWidth - width) / 2 + xPos;
+                        int top = (mCurrentSpec.mCellHeight - height) / 2 + yPos;
+                        Rect newBounds = new Rect(left, top, left + width, top + height);
+                        overlay.setBounds(newBounds);
+                        overlay.draw(mCanvas);
+                    }
+                    paintSel(base + baseOffset, xPos, yPos);
+                }
+
+                private void repaintSelection() {
+                    int count = mGallery.mAllImages.getCount();
+                    int startPos = mBlockNumber * mCurrentSpec.mColumns;
+                    synchronized (ImageBlock.this) {
+                        for (int i = 0; i < mCurrentSpec.mColumns; i++) {
+                            int pos = startPos + i;
+
+                            if (pos >= count)
+                                break;
+
+                            int row = 0; // i / mCurrentSpec.mColumns;
+                            int col = i - (row * mCurrentSpec.mColumns);
+
+                            // this is duplicated from getOrKick (TODO: don't duplicate this code)
+                            int spacing = mCurrentSpec.mCellSpacing;
+                            int leftSpacing = mCurrentSpec.mLeftEdgePadding;
+                            final int yPos = spacing + (row * (mCurrentSpec.mCellHeight + spacing));
+                            final int xPos = leftSpacing + (col * (mCurrentSpec.mCellWidth + spacing));
+
+                            paintSel(pos, xPos, yPos);
+                        }
+                    }
+                }
+
+                private void paintSel(int pos, int xPos, int yPos) {
+                    int[] stateSet = EMPTY_STATE_SET;
+                    if (pos == mCurrentSelection && mShowSelection) {
+                        if (mCurrentSelectionPressed) {
+                            stateSet = PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
+                        } else {
+                            stateSet = ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
+                        }
+                    }
+
+                    mCellOutline.setState(stateSet);
+                    mCanvas.setBitmap(mBitmap);
+                    mCellOutline.setBounds(xPos, yPos, xPos+mCurrentSpec.mCellWidth, yPos+mCurrentSpec.mCellHeight);
+                    mCellOutline.draw(mCanvas);
+                }
+
+                private void loadImage(
+                        final int base,
+                        final int baseOffset,
+                        final ImageManager.IImage image,
+                        final int xPos,
+                        final int yPos) {
+                    synchronized (ImageBlock.this) {
+                        final int startBlock = mBlockNumber;
+                        final int pos = base + baseOffset;
+                        final ImageLoader.LoadedCallback r = new ImageLoader.LoadedCallback() {
+                            public void run(Bitmap b) {
+                                boolean more = false;
+                                synchronized (ImageBlock.this) {
+                                    if (startBlock != mBlockNumber) {
+//                                      Log.v(TAG, "wanted block " + mBlockNumber + " but got " + startBlock);
+                                        return;
+                                    }
+
+                                    if (mBitmap == null) {
+                                        return;
+                                    }
+
+                                    drawBitmap(image, base, baseOffset, b, xPos, yPos);
+
+                                    int mask = (1 << baseOffset);
+                                    mRequestedMask &= ~mask;
+                                    mCompletedMask |= mask;
+
+                                 // Log.v(TAG, "for " + mBlockNumber + " mRequestedMask is " + String.format("%x", mRequestedMask) + " and mCompletedMask is " + String.format("%x", mCompletedMask));
+
+                                    if (mRequestedMask == 0) {
+                                        if (mIsVisible) {
+                                            postInvalidate();
+                                        }
+                                        more = true;
+                                    }
+                                }
+                                if (b != null)
+                                    b.recycle();
+
+                                if (more) {
+                                    synchronized (ImageBlockManager.this) {
+                                        ImageBlockManager.this.notify();
+                                        mWorkCounter += 1;
+                                    }
+                                }
+                                if (sDump)
+                                    ImageBlockManager.this.dump();
+                            }
+                        };
+                        mRequestedMask |= (1 << baseOffset);
+                        mLoader.getBitmap(image, pos, r, mIsVisible, false);
+                    }
+                }
+            }
+        }
+
+        public void init(Handler handler) {
+            mHandler = handler;
+        }
+
+        public void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            if (false) {
+                canvas.drawRect(0, 0, getWidth(), getHeight(), mGridViewPaint);
+                if (Config.LOGV)
+                    Log.v(TAG, "painting background w/h " + getWidth() + " / " + getHeight());
+                return;
+            }
+
+            if (mImageBlockManager != null) {
+                mImageBlockManager.doDraw(canvas);
+                mImageBlockManager.moveDataWindow(mDirectionBiasDown, false);
+            }
+        }
+
+        @Override
+        public void computeScroll() {
+            if (mScroller != null) {
+                boolean more = mScroller.computeScrollOffset();
+                scrollTo(0, (int)mScroller.getCurrY());
+                if (more) {
+                    postInvalidate();  // So we draw again
+                } else {
+                    mScroller = null;
+                }
+            } else {
+                super.computeScroll();
+            }
+        }
+
+        private android.graphics.Rect getRectForPosition(int pos) {
+            int row = pos / mCurrentSpec.mColumns;
+            int col = pos - (row * mCurrentSpec.mColumns);
+
+            int left = mCurrentSpec.mLeftEdgePadding + (col * mCurrentSpec.mCellWidth) + (Math.max(0, col-1) * mCurrentSpec.mCellSpacing);
+            int top  = (row * mCurrentSpec.mCellHeight) + (row * mCurrentSpec.mCellSpacing);
+
+            return new android.graphics.Rect(left, top, left + mCurrentSpec.mCellWidth + mCurrentSpec.mCellWidth, top + mCurrentSpec.mCellHeight + mCurrentSpec.mCellSpacing);
+        }
+
+        int computeSelectedIndex(android.view.MotionEvent ev) {
+            int spacing = mCurrentSpec.mCellSpacing;
+            int leftSpacing = mCurrentSpec.mLeftEdgePadding;
+
+            int x = (int) ev.getX();
+            int y = (int) ev.getY();
+            int row = (mScrollY + y - spacing) / (mCurrentSpec.mCellHeight + spacing);
+            int col = Math.min(mCurrentSpec.mColumns - 1, (x - leftSpacing) / (mCurrentSpec.mCellWidth + spacing));
+            return (row * mCurrentSpec.mColumns) + col;
+        }
+
+        @Override
+        public boolean onTouchEvent(android.view.MotionEvent ev) {
+            mGestureDetector.onTouchEvent(ev);
+            return true;
+        }
+
+        private void onSelect(int index) {
+            if (index >= 0 && index < mGallery.mAllImages.getCount()) {
+                ImageManager.IImage img = mGallery.mAllImages.getImageAt(index);
+                if (img == null)
+                    return;
+
+                if (mGallery.isPickIntent()) {
+                    mGallery.launchCropperOrFinish(img);
+                } else {
+                    Uri targetUri = img.fullSizeImageUri();
+                    Uri thisUri = mGallery.getIntent().getData();
+                    if (thisUri != null) {
+                        String bucket = thisUri.getQueryParameter("bucketId");
+                        if (bucket != null) {
+                            targetUri = targetUri.buildUpon().appendQueryParameter("bucketId", bucket).build();
+                        }
+                    }
+                    Intent intent = new Intent(Intent.ACTION_VIEW, targetUri);
+
+                    if (img instanceof ImageManager.VideoObject) {
+                        intent.putExtra(MediaStore.EXTRA_SCREEN_ORIENTATION,
+                                ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+                    }
+
+                    try {
+                        mContext.startActivity(intent);
+                    } catch (Exception ex) {
+                        // sdcard removal??
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void scrollBy(int x, int y) {
+            scrollTo(x, mScrollY + y);
+        }
+
+        Toast mDateLocationToast;
+        int [] mDateRange = new int[2];
+
+        private String month(int month) {
+            String text = "";
+            switch (month) {
+                case 0:  text = "January";   break;
+                case 1:  text = "February";  break;
+                case 2:  text = "March";     break;
+                case 3:  text = "April";     break;
+                case 4:  text = "May";       break;
+                case 5:  text = "June";      break;
+                case 6:  text = "July";      break;
+                case 7:  text = "August";    break;
+                case 8:  text = "September"; break;
+                case 9:  text = "October";   break;
+                case 10: text = "November";  break;
+                case 11: text = "December";  break;
+            }
+            return text;
+        }
+
+        Runnable mToastRunnable = new Runnable() {
+            public void run() {
+                if (mDateLocationToast != null) {
+                    mDateLocationToast.cancel();
+                    mDateLocationToast = null;
+                }
+
+                int count = mGallery.mAllImages.getCount();
+                if (count == 0)
+                    return;
+
+                GridViewSpecial.this.mImageBlockManager.getVisibleRange(mDateRange);
+
+                ImageManager.IImage firstImage = mGallery.mAllImages.getImageAt(mDateRange[0]);
+                int lastOffset = Math.min(count-1, mDateRange[1]);
+                ImageManager.IImage lastImage = mGallery.mAllImages.getImageAt(lastOffset);
+
+                GregorianCalendar dateStart = new GregorianCalendar();
+                GregorianCalendar dateEnd   = new GregorianCalendar();
+
+                dateStart.setTimeInMillis(firstImage.getDateTaken());
+                dateEnd.setTimeInMillis(lastImage.getDateTaken());
+
+                String text1 = month(dateStart.get(Calendar.MONTH)) + " " + dateStart.get(Calendar.YEAR);
+                String text2 = month(dateEnd  .get(Calendar.MONTH)) + " " + dateEnd  .get(Calendar.YEAR);
+
+                String text = text1;
+                if (!text2.equals(text1))
+                    text = text + " : " + text2;
+
+                mDateLocationToast = Toast.makeText(mContext, text, Toast.LENGTH_LONG);
+                mDateLocationToast.show();
+            }
+        };
+
+        @Override
+        public void scrollTo(int x, int y) {
+            y = Math.min(mMaxScrollY, y);
+            y = Math.max(mMinScrollY, y);
+            if (y > mScrollY)
+                mDirectionBiasDown = true;
+            else if (y < mScrollY)
+                mDirectionBiasDown = false;
+            super.scrollTo(x, y);
+        }
+    }
+}
diff --git a/src/com/android/camera/ImageLoader.java b/src/com/android/camera/ImageLoader.java
new file mode 100644
index 0000000..e398fba
--- /dev/null
+++ b/src/com/android/camera/ImageLoader.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2007 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.camera;
+
+import java.util.ArrayList;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.util.Config;
+import android.util.Log;
+
+class ImageLoader {
+    private static final String TAG = "ImageLoader";
+
+    // queue of work to do in the worker thread
+    private ArrayList<WorkItem>      mQueue = new ArrayList<WorkItem>();
+    private ArrayList<WorkItem>      mInProgress = new ArrayList<WorkItem>();
+
+    // the worker thread and a done flag so we know when to exit
+    // currently we only exit from finalize
+    private boolean                  mDone;
+    private ArrayList<Thread>        mDecodeThreads = new ArrayList<Thread>();
+    private android.os.Handler       mHandler;
+
+    private int                      mThreadCount = 1;
+
+    synchronized void clear(Uri uri) {
+    }
+
+    synchronized public void dump() {
+        synchronized (mQueue) {
+            if (Config.LOGV)
+                Log.v(TAG, "Loader queue length is " + mQueue.size());
+        }
+    }
+
+    public interface LoadedCallback {
+        public void run(Bitmap result);
+    }
+
+    public void pushToFront(final ImageManager.IImage image) {
+        synchronized (mQueue) {
+            WorkItem w = new WorkItem(image, 0, null, false);
+
+            int existing = mQueue.indexOf(w);
+            if (existing >= 1) {
+                WorkItem existingWorkItem = mQueue.remove(existing);
+                mQueue.add(0, existingWorkItem);
+                mQueue.notifyAll();
+            }
+        }
+    }
+
+    public boolean cancel(final ImageManager.IImage image) {
+        synchronized (mQueue) {
+            WorkItem w = new WorkItem(image, 0, null, false);
+
+            int existing = mQueue.indexOf(w);
+            if (existing >= 0) {
+                mQueue.remove(existing);
+                return true;
+            }
+            return false;
+        }
+    }
+
+    public Bitmap getBitmap(final ImageManager.IImage image, final LoadedCallback imageLoadedRunnable, final boolean postAtFront, boolean postBack) {
+        return getBitmap(image, 0, imageLoadedRunnable, postAtFront, postBack);
+    }
+
+    public Bitmap getBitmap(final ImageManager.IImage image, int tag, final LoadedCallback imageLoadedRunnable, final boolean postAtFront, boolean postBack) {
+        synchronized (mDecodeThreads) {
+            if (mDecodeThreads.size() == 0) {
+                start();
+            }
+        }
+        long t1 = System.currentTimeMillis();
+        long t2,t3,t4;
+        synchronized (mQueue) {
+            t2 = System.currentTimeMillis();
+            WorkItem w = new WorkItem(image, tag, imageLoadedRunnable, postBack);
+
+            if (!mInProgress.contains(w)) {
+                boolean contains = mQueue.contains(w);
+                if (contains) {
+                    if (postAtFront) {
+                        // move this item to the front
+                        mQueue.remove(w);
+                        mQueue.add(0, w);
+                    }
+                } else {
+                    if (postAtFront)
+                        mQueue.add(0, w);
+                    else
+                        mQueue.add(w);
+                    mQueue.notifyAll();
+                }
+            }
+            if (false)
+                dumpQueue("+" + (postAtFront ? "F " : "B ") + tag + ": ");
+            t3 = System.currentTimeMillis();
+        }
+        t4 = System.currentTimeMillis();
+//        Log.v(TAG, "getBitmap breakdown: tot= " + (t4-t1) + "; " + "; " + (t4-t3) + "; " + (t3-t2) + "; " + (t2-t1));
+        return null;
+    }
+
+    private void dumpQueue(String s) {
+        synchronized (mQueue) {
+            StringBuilder sb = new StringBuilder(s);
+            for (int i = 0; i < mQueue.size(); i++) {
+                sb.append(mQueue.get(i).mTag + " ");
+            }
+            if (Config.LOGV)
+                Log.v(TAG, sb.toString());
+        }
+    }
+
+    long bitmapSize(Bitmap b) {
+        return b.getWidth() * b.getHeight() * 4;
+    }
+
+    class WorkItem {
+        ImageManager.IImage mImage;
+        int mTargetX, mTargetY;
+        int mTag;
+        LoadedCallback mOnLoadedRunnable;
+        boolean mPostBack;
+
+        WorkItem(ImageManager.IImage image, int tag, LoadedCallback onLoadedRunnable, boolean postBack) {
+            mImage = image;
+            mTag = tag;
+            mOnLoadedRunnable = onLoadedRunnable;
+            mPostBack = postBack;
+        }
+
+        public boolean equals(Object other) {
+            WorkItem otherWorkItem = (WorkItem) other;
+            if (otherWorkItem.mImage != mImage)
+                return false;
+
+            return true;
+        }
+
+        public int hashCode() {
+            return mImage.fullSizeImageUri().hashCode();
+        }
+    }
+
+    public ImageLoader(android.os.Handler handler, int threadCount) {
+        mThreadCount = threadCount;
+        mHandler = handler;
+        start();
+    }
+
+    synchronized private void start() {
+        if (Config.LOGV)
+            Log.v(TAG, "ImageLoader.start() <<<<<<<<<<<<<<<<<<<<<<<<<<<<");
+
+        synchronized (mDecodeThreads) {
+            if (mDecodeThreads.size() > 0)
+                return;
+
+            mDone = false;
+            for (int i = 0;i < mThreadCount; i++) {
+                Thread t = new Thread(new Runnable() {
+                    // pick off items on the queue, one by one, and compute their bitmap.
+                    // place the resulting bitmap in the cache.  then post a notification
+                    // back to the ui so things can get updated appropriately.
+                    public void run() {
+                        while (!mDone) {
+                            WorkItem workItem = null;
+                            synchronized (mQueue) {
+                                if (mQueue.size() > 0) {
+                                    workItem = mQueue.remove(0);
+                                    mInProgress.add(workItem);
+                                }
+                                else {
+                                    try {
+                                        mQueue.wait();
+                                    } catch (InterruptedException ex) {
+                                    }
+                                }
+                            }
+                            if (workItem != null) {
+                                if (false)
+                                    dumpQueue("-" + workItem.mTag + ": ");
+                                Bitmap b = null;
+                                try {
+                                    b = workItem.mImage.miniThumbBitmap();
+                                } catch (Exception ex) {
+                                    if (Config.LOGV) Log.v(TAG, "couldn't load miniThumbBitmap " + ex.toString());
+                                    // sd card removal or sd card full
+                                }
+                                if (b == null) {
+                                    if (Config.LOGV) Log.v(TAG, "unable to read thumbnail for " + workItem.mImage.fullSizeImageUri());
+                                }
+
+                                synchronized (mQueue) {
+                                    mInProgress.remove(workItem);
+                                }
+
+                                if (workItem.mOnLoadedRunnable != null) {
+                                    if (workItem.mPostBack) {
+                                        final WorkItem w1 = workItem;
+                                        final Bitmap bitmap = b;
+                                        if (!mDone) {
+                                            mHandler.post(new Runnable() {
+                                                public void run() {
+                                                    w1.mOnLoadedRunnable.run(bitmap);
+                                                }
+                                            });
+                                        }
+                                    } else {
+                                        workItem.mOnLoadedRunnable.run(b);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                });
+                t.setName("image-loader-" + i);
+                mDecodeThreads.add(t);
+                t.start();
+            }
+        }
+    }
+
+    public static Bitmap transform(Matrix scaler, Bitmap source, int targetWidth, int targetHeight,
+            boolean scaleUp) {
+        int deltaX = source.getWidth() - targetWidth;
+        int deltaY = source.getHeight() - targetHeight;
+        if (!scaleUp && (deltaX < 0 || deltaY < 0)) {
+            /*
+             * In this case the bitmap is smaller, at least in one dimension, than the
+             * target.  Transform it by placing as much of the image as possible into
+             * the target and leaving the top/bottom or left/right (or both) black.
+             */
+            Bitmap b2 = Bitmap.createBitmap(targetWidth, targetHeight, Bitmap.Config.ARGB_8888);
+            Canvas c = new Canvas(b2);
+
+            int deltaXHalf = Math.max(0, deltaX/2);
+            int deltaYHalf = Math.max(0, deltaY/2);
+            Rect src = new Rect(
+                    deltaXHalf,
+                    deltaYHalf,
+                    deltaXHalf + Math.min(targetWidth, source.getWidth()),
+                    deltaYHalf + Math.min(targetHeight, source.getHeight()));
+            int dstX = (targetWidth  - src.width())  / 2;
+            int dstY = (targetHeight - src.height()) / 2;
+            Rect dst = new Rect(
+                    dstX,
+                    dstY,
+                    targetWidth - dstX,
+                    targetHeight - dstY);
+            if (Config.LOGV)
+                Log.v(TAG, "draw " + src.toString() + " ==> " + dst.toString());
+            c.drawBitmap(source, src, dst, null);
+            return b2;
+        }
+        float bitmapWidthF = source.getWidth();
+        float bitmapHeightF = source.getHeight();
+
+        float bitmapAspect = bitmapWidthF / bitmapHeightF;
+        float viewAspect   = (float) targetWidth / (float) targetHeight;
+
+        if (bitmapAspect > viewAspect) {
+            float scale = targetHeight / bitmapHeightF;
+            if (scale < .9F || scale > 1F) {
+                scaler.setScale(scale, scale);
+            } else {
+                scaler = null;
+            }
+        } else {
+            float scale = targetWidth / bitmapWidthF;
+            if (scale < .9F || scale > 1F) {
+                scaler.setScale(scale, scale);
+            } else {
+                scaler = null;
+            }
+        }
+
+        Bitmap b1;
+        if (scaler != null) {
+            // this is used for minithumb and crop, so we want to filter here.
+            b1 = Bitmap.createBitmap(source, 0, 0,
+                    source.getWidth(), source.getHeight(), scaler, true);
+        } else {
+            b1 = source;
+        }
+
+        int dx1 = Math.max(0, b1.getWidth() - targetWidth);
+        int dy1 = Math.max(0, b1.getHeight() - targetHeight);
+
+        Bitmap b2 = Bitmap.createBitmap(
+                b1,
+                dx1/2,
+                dy1/2,
+                targetWidth,
+                targetHeight);
+
+        if (b1 != source)
+            b1.recycle();
+
+        return b2;
+    }
+
+    public void stop() {
+        if (Config.LOGV)
+            Log.v(TAG, "ImageLoader.stop " + mDecodeThreads.size() + " threads");
+        mDone = true;
+        synchronized (mQueue) {
+            mQueue.notifyAll();
+        }
+        while (mDecodeThreads.size() > 0) {
+            Thread t = mDecodeThreads.get(0);
+            try {
+                t.join();
+                mDecodeThreads.remove(0);
+            } catch (InterruptedException ex) {
+                // so now what?
+            }
+        }
+    }
+}
diff --git a/src/com/android/camera/ImageManager.java b/src/com/android/camera/ImageManager.java
new file mode 100755
index 0000000..99e5366
--- /dev/null
+++ b/src/com/android/camera/ImageManager.java
@@ -0,0 +1,4203 @@
+/*
+ * Copyright (C) 2007 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.camera;
+
+import android.content.Context;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.ContentUris;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.DataSetObserver;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.location.Location;
+import android.media.MediaMetadataRetriever;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.ParcelFileDescriptor;
+import android.provider.BaseColumns;
+import android.provider.DrmStore;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Images.ImageColumns;
+import android.provider.MediaStore.Images.Thumbnails;
+import android.provider.MediaStore.Video.VideoColumns;
+import android.provider.MediaStore.Images;
+import android.provider.MediaStore.MediaColumns;
+import android.provider.MediaStore.Video;
+import android.util.Config;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ *
+ * ImageManager is used to retrieve and store images
+ * in the media content provider.
+ *
+ */
+public class ImageManager {
+    public static final String CAMERA_IMAGE_BUCKET_NAME =
+        Environment.getExternalStorageDirectory().toString() + "/DCIM/Camera";
+    public static final String CAMERA_IMAGE_BUCKET_ID = getBucketId(CAMERA_IMAGE_BUCKET_NAME);
+
+    /**
+     * Matches code in MediaProvider.computeBucketValues. Should be a common function.
+     */
+
+    public static String getBucketId(String path) {
+        return String.valueOf(path.toLowerCase().hashCode());
+    }
+    
+    /**
+     * OSX requires plugged-in USB storage to have path /DCIM/NNNAAAAA to be imported.
+     * This is a temporary fix for bug#1655552.
+     */
+    public static void ensureOSXCompatibleFolder() {
+        File nnnAAAAA = new File(
+            Environment.getExternalStorageDirectory().toString() + "/DCIM/100ANDRO");
+        if ((!nnnAAAAA.exists()) && (!nnnAAAAA.mkdir())) {
+            Log.e(TAG, "create NNNAAAAA file: "+ nnnAAAAA.getPath()+" failed");
+        }
+    }
+
+    // To enable verbose logging for this class, change false to true. The other logic ensures that
+    // this logging can be disabled by turned off DEBUG and lower, and that it can be enabled by
+    // "setprop log.tag.ImageManager VERBOSE" if desired.
+    //
+    // IMPORTANT: Never check in this file set to true!
+    private static final boolean VERBOSE = Config.LOGD && (false || Config.LOGV);
+    private static final String TAG = "ImageManager";
+
+    private static final int MINI_THUMB_DATA_FILE_VERSION = 3;
+
+    static public void debug_where(String tag, String msg) {
+        try {
+            throw new Exception();
+        } catch (Exception ex) {
+            if (msg != null) {
+                Log.v(tag, msg);
+            }
+            boolean first = true;
+            for (StackTraceElement s : ex.getStackTrace()) {
+                if (first)
+                    first = false;
+                else
+                    Log.v(tag, s.toString());
+            }
+        }
+    }
+
+    /*
+     * Compute the sample size as a function of the image size and the target.
+     * Scale the image down so that both the width and height are just above
+     * the target.  If this means that one of the dimension goes from above
+     * the target to below the target (e.g. given a width of 480 and an image
+     * width of 600 but sample size of 2 -- i.e. new width 300 -- bump the
+     * sample size down by 1.
+     */
+    private static int computeSampleSize(BitmapFactory.Options options, int target) {
+        int w = options.outWidth;
+        int h = options.outHeight;
+
+        int candidateW = w / target;
+        int candidateH = h / target;
+        int candidate = Math.max(candidateW, candidateH);
+
+        if (candidate == 0)
+            return 1;
+
+        if (candidate > 1) {
+            if ((w > target) && (w / candidate) < target)
+                candidate -= 1;
+        }
+
+        if (candidate > 1) {
+            if ((h > target) && (h / candidate) < target)
+                candidate -= 1;
+        }
+
+        if (VERBOSE)
+            Log.v(TAG, "for w/h " + w + "/" + h + " returning " + candidate + "(" + (w/candidate) + " / " + (h/candidate));
+
+        return candidate;
+    }
+    /*
+     * All implementors of ICancelable should inherit from BaseCancelable
+     * since it provides some convenience methods such as acknowledgeCancel
+     * and checkCancel.
+     */
+    public abstract class BaseCancelable implements ICancelable {
+        boolean mCancel = false;
+        boolean mFinished = false;
+
+        /*
+         * Subclasses should call acknowledgeCancel when they're finished with
+         * their operation.
+         */
+        protected void acknowledgeCancel() {
+            synchronized (this) {
+                mFinished = true;
+                if (!mCancel)
+                    return;
+                if (mCancel) {
+                    this.notify();
+                }
+            }
+        }
+
+        public boolean cancel() {
+            synchronized (this) {
+                if (mCancel) {
+                    return false;
+                }
+                if (mFinished) {
+                    return false;
+                }
+                mCancel = true;
+                boolean retVal = doCancelWork();
+
+                try {
+                    this.wait();
+                } catch (InterruptedException ex) {
+                    // now what???  TODO
+                }
+
+                return retVal;
+            }
+        }
+
+        /*
+         * Subclasses can call this to see if they have been canceled.
+         * This is the polling model.
+         */
+        protected void checkCanceled() throws CanceledException {
+            synchronized (this) {
+                if (mCancel)
+                    throw new CanceledException();
+            }
+        }
+
+        /*
+         * Subclasses implement this method to take whatever action
+         * is necessary when getting canceled.  Sometimes it's not
+         * possible to do anything in which case the "checkCanceled"
+         * polling model may be used (or some combination).
+         */
+        public abstract boolean doCancelWork();
+    }
+
+    private static final int sBytesPerMiniThumb = 10000;
+    static final private byte [] sMiniThumbData = new byte[sBytesPerMiniThumb];
+
+    /**
+     * Represents a particular image and provides access
+     * to the underlying bitmap and two thumbnail bitmaps
+     * as well as other information such as the id, and
+     * the path to the actual image data.
+     */
+    abstract class BaseImage implements IImage {
+        protected ContentResolver mContentResolver;
+        protected long mId, mMiniThumbMagic;
+        protected BaseImageList mContainer;
+        protected HashMap<String, String> mExifData;
+        protected int mCursorRow;
+
+        protected BaseImage(long id, long miniThumbId, ContentResolver cr, BaseImageList container, int cursorRow) {
+            mContentResolver = cr;
+            mId              = id;
+            mMiniThumbMagic  = miniThumbId;
+            mContainer       = container;
+            mCursorRow       = cursorRow;
+        }
+
+        abstract Bitmap.CompressFormat compressionType();
+
+        public void commitChanges() {
+            Cursor c = getCursor();
+            synchronized (c) {
+                if (c.moveToPosition(getRow())) {
+                    c.commitUpdates();
+                    c.requery();
+                }
+            }
+        }
+
+        /**
+         * Take a given bitmap and compress it to a file as described
+         * by the Uri parameter.
+         *
+         * @param bitmap    the bitmap to be compressed/stored
+         * @param uri       where to store the bitmap
+         * @return          true if we succeeded
+         */
+        protected IGetBoolean_cancelable compressImageToFile(
+                final Bitmap bitmap,
+                final byte [] jpegData,
+                final Uri uri) {
+            class CompressImageToFile extends BaseCancelable implements IGetBoolean_cancelable {
+                ThreadSafeOutputStream mOutputStream = null;
+
+                public boolean doCancelWork() {
+                    if (mOutputStream != null) {
+                        try {
+                            mOutputStream.close();
+                            return true;
+                        } catch (IOException ex) {
+                            // TODO what to do here
+                        }
+                    }
+                    return false;
+                }
+
+                public boolean get() {
+                    try {
+                        long t1 = System.currentTimeMillis();
+                        OutputStream delegate = mContentResolver.openOutputStream(uri);
+                        synchronized (this) {
+                            checkCanceled();
+                            mOutputStream = new ThreadSafeOutputStream(delegate);
+                        }
+                        long t2 = System.currentTimeMillis();
+                        if (bitmap != null) {
+                            bitmap.compress(compressionType(), 75, mOutputStream);
+                        } else {
+                            long x1 = System.currentTimeMillis();
+                            mOutputStream.write(jpegData);
+                            long x2 = System.currentTimeMillis();
+                            if (VERBOSE) Log.v(TAG, "done writing... " + jpegData.length + " bytes took " + (x2-x1));
+                        }
+                        long t3 = System.currentTimeMillis();
+                        if (VERBOSE) Log.v(TAG, String.format("CompressImageToFile.get took %d (%d, %d)",(t3-t1),(t2-t1),(t3-t2)));
+                        return true;
+                    } catch (FileNotFoundException ex) {
+                        return false;
+                    } catch (CanceledException ex) {
+                        return false;
+                    } catch (IOException ex) {
+                        return false;
+                    }
+                    finally {
+                        if (mOutputStream != null) {
+                            try {
+                                mOutputStream.close();
+                            } catch (IOException ex) {
+                                // not much we can do here so ignore
+                            }
+                        }
+                        acknowledgeCancel();
+                    }
+                }
+            }
+            return new CompressImageToFile();
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other == null)
+                return false;
+            if (!(other instanceof Image))
+                return false;
+
+            return fullSizeImageUri().equals(((Image)other).fullSizeImageUri());
+        }
+
+        public Bitmap fullSizeBitmap(int targetWidthHeight) {
+            return fullSizeBitmap(targetWidthHeight, true);
+        }
+
+        protected Bitmap fullSizeBitmap(int targetWidthHeight, boolean rotateAsNeeded) {
+            Uri url = mContainer.contentUri(mId);
+            if (VERBOSE) Log.v(TAG, "getCreateBitmap for " + url);
+            if (url == null)
+                return null;
+
+            Bitmap b = null;
+            if (b == null) {
+                b = makeBitmap(targetWidthHeight, url);
+                if (b != null && rotateAsNeeded) {
+                    b = rotate(b, getDegreesRotated());
+                }
+            }
+            return b;
+        }
+
+
+        public IGetBitmap_cancelable fullSizeBitmap_cancelable(final int targetWidthHeight) {
+            final class LoadBitmapCancelable extends BaseCancelable implements IGetBitmap_cancelable {
+                ParcelFileDescriptor mPFD;
+                BitmapFactory.Options mOptions = new BitmapFactory.Options();
+                long mCancelInitiationTime;
+
+                public LoadBitmapCancelable(ParcelFileDescriptor pfdInput) {
+                    mPFD = pfdInput;
+                }
+
+                public boolean doCancelWork() {
+                    if (VERBOSE)
+                        Log.v(TAG, "requesting bitmap load cancel");
+                    mCancelInitiationTime = System.currentTimeMillis();
+                    mOptions.requestCancelDecode();
+                    return true;
+                }
+
+                public Bitmap get() {
+                    try {
+                        Bitmap b = makeBitmap(targetWidthHeight, fullSizeImageUri(), mPFD, mOptions);
+                        if (mCancelInitiationTime != 0) {
+                            if (VERBOSE)
+                                Log.v(TAG, "cancelation of bitmap load success==" + (b == null ? "TRUE" : "FALSE") + " -- took " + (System.currentTimeMillis() - mCancelInitiationTime));
+                        }
+                        if (b != null) {
+                            int degrees = getDegreesRotated();
+                            if (degrees != 0) {
+                                Matrix m = new Matrix();
+                                m.setRotate(degrees, (float) b.getWidth() / 2, (float) b.getHeight() / 2);
+                                Bitmap b2 = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, true);
+                                if (b != b2)
+                                    b.recycle();
+                                b = b2;
+                            }
+                        }
+                        return b;
+                    } catch (Exception ex) {
+                        return null;
+                    } finally {
+                        acknowledgeCancel();
+                    }
+                }
+            }
+
+            try {
+                ParcelFileDescriptor pfdInput = mContentResolver.openFileDescriptor(fullSizeImageUri(), "r");
+                return new LoadBitmapCancelable(pfdInput);
+            } catch (FileNotFoundException ex) {
+                return null;
+            } catch (UnsupportedOperationException ex) {
+                return null;
+            }
+        }
+
+        public InputStream fullSizeImageData() {
+            try {
+                InputStream input = mContentResolver.openInputStream(
+                        fullSizeImageUri());
+                return input;
+            } catch (IOException ex) {
+                return null;
+            }
+        }
+
+        public long fullSizeImageId() {
+            return mId;
+        }
+
+        public Uri fullSizeImageUri() {
+            return mContainer.contentUri(mId);
+        }
+
+        public IImageList getContainer() {
+            return mContainer;
+        }
+
+        Cursor getCursor() {
+            return mContainer.getCursor();
+        }
+
+        public long getDateTaken() {
+            if (mContainer.indexDateTaken() < 0) return 0;
+            Cursor c = getCursor();
+            synchronized (c) {
+                c.moveToPosition(getRow());
+                return c.getLong(mContainer.indexDateTaken());
+            }
+        }
+
+        protected int getDegreesRotated() {
+            return 0;
+        }
+
+        public String getMimeType() {
+            if (mContainer.indexMimeType() < 0) {
+                Cursor c = null;
+                try {
+                    c = mContentResolver.query(
+                        fullSizeImageUri(),
+                        new String[] { "_id", Images.Media.MIME_TYPE },
+                        null,
+                        null, null);
+                    if (c != null && c.moveToFirst()) {
+                        return c.getString(1);
+                    } else {
+                        return "";
+                    }
+                } finally {
+                    if (c != null)
+                        c.close();
+                }
+            } else {
+                String mimeType = null;
+                Cursor c = getCursor();
+                synchronized(c) {
+                    if (c.moveToPosition(getRow())) {
+                        mimeType = c.getString(mContainer.indexMimeType());
+                    }
+                }
+                return mimeType;
+            }
+        }
+
+        /* (non-Javadoc)
+         * @see com.android.camera.IImage#getDescription()
+         */
+        public String getDescription() {
+            if (mContainer.indexDescription() < 0) {
+                Cursor c = null;
+                try {
+                    c = mContentResolver.query(
+                        fullSizeImageUri(),
+                        new String[] { "_id", Images.Media.DESCRIPTION },
+                        null,
+                        null, null);
+                    if (c != null && c.moveToFirst()) {
+                        return c.getString(1);
+                    } else {
+                        return "";
+                    }
+                } finally {
+                    if (c != null)
+                        c.close();
+                }
+            } else {
+                String description = null;
+                Cursor c = getCursor();
+                synchronized(c) {
+                    if (c.moveToPosition(getRow())) {
+                        description = c.getString(mContainer.indexDescription());
+                    }
+                }
+                return description;
+            }
+        }
+
+        /* (non-Javadoc)
+         * @see com.android.camera.IImage#getIsPrivate()
+         */
+        public boolean getIsPrivate() {
+            if (mContainer.indexPrivate() < 0) return false;
+            boolean isPrivate = false;
+            Cursor c = getCursor();
+            synchronized(c) {
+                if (c.moveToPosition(getRow())) {
+                    isPrivate = c.getInt(mContainer.indexPrivate()) != 0;
+                }
+            }
+            return isPrivate;
+        }
+
+        public double getLatitude() {
+            if (mContainer.indexLatitude() < 0) return 0D;
+            Cursor c = getCursor();
+            synchronized (c) {
+                c.moveToPosition(getRow());
+                return c.getDouble(mContainer.indexLatitude());
+            }
+        }
+
+        public double getLongitude() {
+            if (mContainer.indexLongitude() < 0) return 0D;
+            Cursor c = getCursor();
+            synchronized (c) {
+                c.moveToPosition(getRow());
+                return c.getDouble(mContainer.indexLongitude());
+            }
+        }
+
+        /* (non-Javadoc)
+         * @see com.android.camera.IImage#getTitle()
+         */
+        public String getTitle() {
+            String name = null;
+            Cursor c = getCursor();
+            synchronized(c) {
+                if (c.moveToPosition(getRow())) {
+                    if (mContainer.indexTitle() != -1) {
+                        name = c.getString(mContainer.indexTitle());
+                    }
+                }
+            }
+            return name != null && name.length() > 0 ? name : String.valueOf(mId);
+        }
+
+        /* (non-Javadoc)
+         * @see com.android.camera.IImage#getDisplayName()
+         */
+        public String getDisplayName() {
+            if (mContainer.indexDisplayName() < 0) {
+                Cursor c = null;
+                try {
+                    c = mContentResolver.query(
+                        fullSizeImageUri(),
+                        new String[] { "_id", Images.Media.DISPLAY_NAME },
+                        null,
+                        null, null);
+                    if (c != null && c.moveToFirst()) {
+                        return c.getString(1);
+                    }
+                } finally {
+                    if (c != null)
+                        c.close();
+                }
+            } else {
+                String name = null;
+                Cursor c = getCursor();
+                synchronized(c) {
+                    if (c.moveToPosition(getRow())) {
+                        name = c.getString(mContainer.indexDisplayName());
+                    }
+                }
+                if (name != null && name.length() > 0)
+                    return name;
+            }
+            return String.valueOf(mId);
+        }
+
+        public String getPicasaId() {
+            /*
+            if (mContainer.indexPicasaWeb() < 0) return null;
+            Cursor c = getCursor();
+            synchronized (c) {
+                c.moveTo(getRow());
+                return c.getString(mContainer.indexPicasaWeb());
+            }
+            */
+            return null;
+        }
+
+        public int getRow() {
+            return mCursorRow;
+        }
+
+        public int getWidth() {
+            ParcelFileDescriptor input = null;
+            try {
+                Uri uri = fullSizeImageUri();
+                input = mContentResolver.openFileDescriptor(uri, "r");
+                BitmapFactory.Options options = new BitmapFactory.Options();
+                options.inJustDecodeBounds = true;
+                BitmapFactory.decodeFileDescriptor(input.getFileDescriptor(), null, options);
+                return options.outWidth;
+            } catch (IOException ex) {
+                return 0;
+            } finally {
+                try {
+                    if (input != null) {
+                        input.close();
+                    }
+                } catch (IOException ex) {
+                }
+            }
+        }
+
+        public int getHeight() {
+            ParcelFileDescriptor input = null;
+            try {
+                Uri uri = fullSizeImageUri();
+                input = mContentResolver.openFileDescriptor(uri, "r");
+                BitmapFactory.Options options = new BitmapFactory.Options();
+                options.inJustDecodeBounds = true;
+                BitmapFactory.decodeFileDescriptor(input.getFileDescriptor(), null, options);
+                return options.outHeight;
+            } catch (IOException ex) {
+                return 0;
+            } finally {
+                try {
+                    if (input != null) {
+                        input.close();
+                    }
+                } catch (IOException ex) {
+                }
+            }
+        }
+
+        public boolean hasLatLong() {
+            if (mContainer.indexLatitude() < 0 || mContainer.indexLongitude() < 0) return false;
+            Cursor c = getCursor();
+            synchronized (c) {
+                c.moveToPosition(getRow());
+                return !c.isNull(mContainer.indexLatitude()) && !c.isNull(mContainer.indexLongitude());
+            }
+        }
+
+        /* (non-Javadoc)
+         * @see com.android.camera.IImage#imageId()
+         */
+        public long imageId() {
+            return mId;
+        }
+
+        /**
+         * Make a bitmap from a given Uri.
+         *
+         * @param uri
+         */
+        private Bitmap makeBitmap(int targetWidthOrHeight, Uri uri) {
+            ParcelFileDescriptor input = null;
+            try {
+                input = mContentResolver.openFileDescriptor(uri, "r");
+                return makeBitmap(targetWidthOrHeight, uri, input, null);
+            } catch (IOException ex) {
+                return null;
+            } finally {
+                try {
+                    if (input != null) {
+                        input.close();
+                    }
+                } catch (IOException ex) {
+                }
+            }
+        }
+
+        protected Bitmap makeBitmap(int targetWidthHeight, Uri uri, ParcelFileDescriptor pfdInput, BitmapFactory.Options options) {
+            return mContainer.makeBitmap(targetWidthHeight, uri, pfdInput, options);
+        }
+
+        /* (non-Javadoc)
+         * @see com.android.camera.IImage#thumb1()
+         */
+        public Bitmap miniThumbBitmap() {
+            try {
+                long id = mId;
+                long dbMagic = mMiniThumbMagic;
+                if (dbMagic == 0 || dbMagic == id) {
+                    dbMagic = ((BaseImageList)getContainer()).checkThumbnail(this, getCursor(), getRow());
+                    if (VERBOSE) Log.v(TAG, "after computing thumbnail dbMagic is " + dbMagic);
+                }
+
+                synchronized(sMiniThumbData) {
+                    dbMagic = mMiniThumbMagic;
+                    byte [] data = mContainer.getMiniThumbFromFile(id, sMiniThumbData, dbMagic);
+                    if (data == null) {
+                        byte[][] createdThumbData = new byte[1][];
+                        try {
+                            dbMagic = ((BaseImageList)getContainer()).checkThumbnail(this, getCursor(),
+                                    getRow(), createdThumbData);
+                        } catch (IOException ex) {
+                            // Typically IOException because the sd card is full.
+                            // But createdThumbData may have been filled in, so continue on.
+                        }
+                        data = createdThumbData[0];
+                    }
+                    if (data == null) {
+                        data = mContainer.getMiniThumbFromFile(id, sMiniThumbData, dbMagic);
+                    }
+                    if (data == null) {
+                        if (VERBOSE)
+                            Log.v(TAG, "unable to get miniThumbBitmap, data is null");
+                    }
+                    if (data != null) {
+                        Bitmap b = BitmapFactory.decodeByteArray(data, 0, data.length);
+                        if (b == null) {
+                            if (VERBOSE) {
+                                Log.v(TAG, "couldn't decode byte array for mini thumb, length was " + data.length);
+                            }
+                        }
+                        return b;
+                    }
+                }
+                return null;
+            } catch (Exception ex) {
+                // Typically IOException because the sd card is full.
+                if (VERBOSE) {
+                    Log.e(TAG, "miniThumbBitmap got exception " + ex.toString());
+                    for (StackTraceElement s : ex.getStackTrace())
+                        Log.e(TAG, "... " + s.toString());
+                }
+                return null;
+            }
+        }
+
+        public void onRemove() {
+            mContainer.mCache.remove(mId);
+        }
+
+        protected void saveMiniThumb(Bitmap source) throws IOException {
+            mContainer.saveMiniThumbToFile(source, fullSizeImageId(), 0);
+        }
+
+        /* (non-Javadoc)
+         * @see com.android.camera.IImage#setName()
+         */
+        public void setDescription(String description) {
+            if (mContainer.indexDescription() < 0) return;
+            Cursor c = getCursor();
+            synchronized (c) {
+                if (c.moveToPosition(getRow())) {
+                    c.updateString(mContainer.indexDescription(), description);
+                }
+            }
+        }
+
+        /* (non-Javadoc)
+         * @see com.android.camera.IImage#setIsPrivate()
+         */
+        public void setIsPrivate(boolean isPrivate) {
+            if (mContainer.indexPrivate() < 0) return;
+            Cursor c = getCursor();
+            synchronized (c) {
+                if (c.moveToPosition(getRow())) {
+                    c.updateInt(mContainer.indexPrivate(), isPrivate ? 1 : 0);
+                }
+            }
+        }
+
+        /* (non-Javadoc)
+         * @see com.android.camera.IImage#setName()
+         */
+        public void setName(String name) {
+            Cursor c = getCursor();
+            synchronized (c) {
+                if (c.moveToPosition(getRow())) {
+                    c.updateString(mContainer.indexTitle(), name);
+                }
+            }
+        }
+
+        public void setPicasaId(String id) {
+            Cursor c = null;
+            try {
+                c = mContentResolver.query(
+                    fullSizeImageUri(),
+                    new String[] { "_id", Images.Media.PICASA_ID },
+                    null,
+                    null, null);
+                if (c != null && c.moveToFirst()) {
+                    if (VERBOSE) {
+                        Log.v(TAG, "storing picasaid " + id + " for " + fullSizeImageUri());
+                    }
+                    c.updateString(1, id);
+                    c.commitUpdates();
+                    if (VERBOSE) {
+                        Log.v(TAG, "updated image with picasa id " + id);
+                    }
+                }
+            } finally {
+                if (c != null)
+                    c.close();
+            }
+        }
+
+        /* (non-Javadoc)
+         * @see com.android.camera.IImage#thumbUri()
+         */
+        public Uri thumbUri() {
+            Uri uri = fullSizeImageUri();
+            // The value for the query parameter cannot be null :-(, so using a dummy "1"
+            uri = uri.buildUpon().appendQueryParameter("thumb", "1").build();
+            return uri;
+        }
+
+        @Override
+        public String toString() {
+            return fullSizeImageUri().toString();
+        }
+    }
+
+    abstract static class BaseImageList implements IImageList {
+        Context mContext;
+        ContentResolver mContentResolver;
+        Uri mBaseUri, mUri;
+        int mSort;
+        String mBucketId;
+        boolean mDistinct;
+        Cursor mCursor;
+        boolean mCursorDeactivated;
+        protected HashMap<Long, IImage> mCache = new HashMap<Long, IImage>();
+
+        IImageList.OnChange mListener = null;
+        Handler mHandler;
+        protected RandomAccessFile mMiniThumbData;
+        protected Uri mThumbUri;
+
+        public BaseImageList(Context ctx, ContentResolver cr, Uri uri, int sort, String bucketId) {
+            mContext = ctx;
+            mSort = sort;
+            mUri = uri;
+            mBaseUri = uri;
+            mBucketId = bucketId;
+
+            mContentResolver = cr;
+        }
+
+        String randomAccessFilePath(int version) {
+            String directoryName = Environment.getExternalStorageDirectory().toString() + "/DCIM/.thumbnails";
+            String path = directoryName + "/.thumbdata" + version + "-" + mUri.hashCode();
+            return path;
+        }
+
+        RandomAccessFile miniThumbDataFile() {
+            if (mMiniThumbData == null) {
+                String path = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION);
+                File directory = new File(new File(path).getParent());
+                if (!directory.isDirectory()) {
+                    if (!directory.mkdirs()) {
+                        Log.e(TAG, "!!!! unable to create .thumbnails directory " + directory.toString());
+                    }
+                }
+                File f = new File(path);
+                if (VERBOSE) Log.v(TAG, "file f is " + f.toString());
+                try {
+                    mMiniThumbData = new RandomAccessFile(f, "rw");
+                } catch (IOException ex) {
+
+                }
+            }
+            return mMiniThumbData;
+        }
+
+        /**
+         * Store a given thumbnail in the database.
+         */
+        protected Bitmap storeThumbnail(Bitmap thumb, long imageId) {
+            if (thumb == null)
+                return null;
+
+            try {
+                Uri uri = getThumbnailUri(imageId, thumb.getWidth(), thumb.getHeight());
+                if (uri == null) {
+                    return thumb;
+                }
+                OutputStream thumbOut = mContentResolver.openOutputStream(uri);
+                thumb.compress(Bitmap.CompressFormat.JPEG, 60, thumbOut);
+                thumbOut.close();
+                return thumb;
+            }
+            catch (Exception ex) {
+                if (VERBOSE) Log.d(TAG, "unable to store thumbnail: " + ex);
+                return thumb;
+            }
+        }
+
+        /**
+         * Store a JPEG thumbnail from the EXIF header in the database.
+         */
+        protected boolean storeThumbnail(byte[] jpegThumbnail, long imageId, int width, int height) {
+            if (jpegThumbnail == null)
+                return false;
+
+            Uri uri = getThumbnailUri(imageId, width, height);
+            if (uri == null) {
+                return false;
+            }
+            try {
+                OutputStream thumbOut = mContentResolver.openOutputStream(uri);
+                thumbOut.write(jpegThumbnail);
+                thumbOut.close();
+                return true;
+            }
+            catch (FileNotFoundException ex) {
+                return false;
+            }
+            catch (IOException ex) {
+                return false;
+            }
+        }
+
+        private Uri getThumbnailUri(long imageId, int width, int height) {
+            // we do not store thumbnails for DRM'd images
+            if (mThumbUri == null) {
+                return null;
+            }
+
+            Uri uri = null;
+            Cursor c = null;
+            try {
+                c = mContentResolver.query(
+                        mThumbUri,
+                        THUMB_PROJECTION,
+                        Thumbnails.IMAGE_ID + "=?",
+                        new String[]{String.valueOf(imageId)},
+                        null);
+                if (c != null && c.moveToFirst()) {
+                    // If, for some reaosn, we already have a row with a matching
+                    // image id, then just update that row rather than creating a
+                    // new row.
+                    uri = ContentUris.withAppendedId(mThumbUri, c.getLong(indexThumbId()));
+                    c.commitUpdates();
+                }
+            } finally {
+                if (c != null)
+                    c.close();
+            }
+            if (uri == null) {
+                ContentValues values = new ContentValues(4);
+                values.put(Images.Thumbnails.KIND, Images.Thumbnails.MINI_KIND);
+                values.put(Images.Thumbnails.IMAGE_ID, imageId);
+                values.put(Images.Thumbnails.HEIGHT, height);
+                values.put(Images.Thumbnails.WIDTH, width);
+                uri = mContentResolver.insert(mThumbUri, values);
+            }
+            return uri;
+        }
+
+        java.util.Random mRandom = new java.util.Random(System.currentTimeMillis());
+
+        protected SomewhatFairLock mLock = new SomewhatFairLock();
+
+        class SomewhatFairLock {
+            private Object mSync = new Object();
+            private boolean mLocked = false;
+            private ArrayList<Thread> mWaiting = new ArrayList<Thread>();
+
+            void lock() {
+//              if (VERBOSE) Log.v(TAG, "lock... thread " + Thread.currentThread().getId());
+                synchronized (mSync) {
+                    while (mLocked) {
+                        try {
+//                          if (VERBOSE) Log.v(TAG, "waiting... thread " + Thread.currentThread().getId());
+                            mWaiting.add(Thread.currentThread());
+                            mSync.wait();
+                            if (mWaiting.get(0) == Thread.currentThread()) {
+                                mWaiting.remove(0);
+                                break;
+                            }
+                        } catch (InterruptedException ex) {
+                            //
+                        }
+                    }
+//                  if (VERBOSE) Log.v(TAG, "locked... thread " + Thread.currentThread().getId());
+                    mLocked = true;
+                }
+            }
+
+            void unlock() {
+//              if (VERBOSE) Log.v(TAG, "unlocking... thread " + Thread.currentThread().getId());
+                synchronized (mSync) {
+                    mLocked = false;
+                    mSync.notifyAll();
+                }
+            }
+        }
+
+        // If the photo has an EXIF thumbnail and it's big enough, extract it and save that JPEG as
+        // the large thumbnail without re-encoding it. We still have to decompress it though, in
+        // order to generate the minithumb.
+        private Bitmap createThumbnailFromEXIF(String filePath, long id) {
+            if (filePath != null) {
+                byte [] thumbData = null;
+                synchronized (ImageManager.instance()) {
+                    thumbData = (new ExifInterface(filePath)).getThumbnail();
+                }
+                if (thumbData != null) {
+                    // Sniff the size of the EXIF thumbnail before decoding it. Photos from the
+                    // device will pass, but images that are side loaded from other cameras may not.
+                    BitmapFactory.Options options = new BitmapFactory.Options();
+                    options.inJustDecodeBounds = true;
+                    BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, options);
+                    int width = options.outWidth;
+                    int height = options.outHeight;
+                    if (width >= THUMBNAIL_TARGET_SIZE && height >= THUMBNAIL_TARGET_SIZE) {
+                        if (storeThumbnail(thumbData, id, width, height)) {
+                            // this is used for *encoding* the minithumb, so
+                            // we don't want to dither or convert to 565 here.
+                            //
+                            // Decode with a scaling factor
+                            // to match MINI_THUMB_TARGET_SIZE closely
+                            // which will produce much better scaling quality
+                            // and is significantly faster.
+                            options.inSampleSize = computeSampleSize(options, THUMBNAIL_TARGET_SIZE);
+
+                            if (VERBOSE) {
+                                Log.v(TAG, "in createThumbnailFromExif using inSampleSize of " + options.inSampleSize);
+                            }
+                            options.inDither = false;
+                            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+                            options.inJustDecodeBounds = false;
+                            return BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, options);
+                        }
+                    }
+                }
+            }
+            return null;
+        }
+
+        // The fallback case is to decode the original photo to thumbnail size, then encode it as a
+        // JPEG. We return the thumbnail Bitmap in order to create the minithumb from it.
+        private Bitmap createThumbnailFromUri(Cursor c, long id) {
+            Uri uri = ContentUris.withAppendedId(mBaseUri, id);
+            Bitmap bitmap = makeBitmap(THUMBNAIL_TARGET_SIZE, uri, null, null);
+            if (bitmap != null) {
+                storeThumbnail(bitmap, id);
+            } else {
+                uri = ContentUris.withAppendedId(mBaseUri, id);
+                bitmap = makeBitmap(MINI_THUMB_TARGET_SIZE, uri, null, null);
+            }
+            return bitmap;
+        }
+
+        // returns id
+        public long checkThumbnail(BaseImage existingImage, Cursor c, int i) throws IOException {
+            return checkThumbnail(existingImage, c, i, null);
+        }
+
+        /**
+         * Checks to see if a mini thumbnail exists in the cache. If not, tries to create it and
+         * add it to the cache.
+         * @param existingImage
+         * @param c
+         * @param i
+         * @param createdThumbnailData if this parameter is non-null, and a new mini-thumbnail
+         * bitmap is created, the new bitmap's data will be stored in createdThumbnailData[0].
+         * Note that if the sdcard is full, it's possible that
+         * createdThumbnailData[0] will be set even if the method throws an IOException. This is
+         * actually useful, because it allows the caller to use the created thumbnail even if
+         * the sdcard is full.
+         * @return
+         * @throws IOException
+         */
+        public long checkThumbnail(BaseImage existingImage, Cursor c, int i,
+                byte[][] createdThumbnailData) throws IOException {
+            long magic, fileMagic = 0, id;
+            try {
+                mLock.lock();
+                if (existingImage == null) {
+                    // if we don't have an Image object then get the id and magic from
+                    // the cursor.  Synchronize on the cursor object.
+                    synchronized (c) {
+                        if (!c.moveToPosition(i)) {
+                            return -1;
+                        }
+                        magic = c.getLong(indexMiniThumbId());
+                        id = c.getLong(indexId());
+                    }
+                } else {
+                    // if we have an Image object then ask them for the magic/id
+                    magic = existingImage.mMiniThumbMagic;
+                    id = existingImage.fullSizeImageId();
+                }
+
+                if (magic != 0) {
+                    // check the mini thumb file for the right data.  Right is defined as
+                    // having the right magic number at the offset reserved for this "id".
+                    RandomAccessFile r = miniThumbDataFile();
+                    if (r != null) {
+                        synchronized (r) {
+                            long pos = id * sBytesPerMiniThumb;
+                            try {
+                                // check that we can read the following 9 bytes (1 for the "status" and 8 for the long)
+                                if (r.length() >= pos + 1 + 8) {
+                                    r.seek(pos);
+                                    if (r.readByte() == 1) {
+                                        fileMagic = r.readLong();
+                                        if (fileMagic == magic && magic != 0 && magic != id) {
+                                            return magic;
+                                        }
+                                    }
+                                }
+                            } catch (IOException ex) {
+                                Log.v(TAG, "got exception checking file magic: " + ex);
+                            }
+                        }
+                    }
+                    if (VERBOSE) {
+                        Log.v(TAG, "didn't verify... fileMagic: " + fileMagic + "; magic: " + magic + "; id: " + id + "; ");
+                    }
+                }
+
+                // If we can't retrieve the thumbnail, first check if there is one embedded in the
+                // EXIF data. If not, or it's not big enough, decompress the full size image.
+                Bitmap bitmap = null;
+                String filePath = null;
+                synchronized (c) {
+                    if (c.moveToPosition(i)) {
+                        filePath = c.getString(indexData());
+                    }
+                }
+                if (filePath != null) {
+                    String mimeType = c.getString(indexMimeType());
+                    boolean isVideo = isVideoMimeType(mimeType);
+                    if (isVideo) {
+                        bitmap = createVideoThumbnail(filePath);
+                    } else {
+                        bitmap = createThumbnailFromEXIF(filePath, id);
+                        if (bitmap == null) {
+                            bitmap = createThumbnailFromUri(c, id);
+                        }
+                    }
+                    synchronized (c) {
+                        int degrees = 0;
+                        if (c.moveToPosition(i)) {
+                            int column = indexOrientation();
+                            if (column >= 0)
+                                degrees = c.getInt(column);
+                        }
+                        if (degrees != 0) {
+                            Bitmap b2 = rotate(bitmap, degrees);
+                            if (b2 != bitmap)
+                                bitmap.recycle();
+                            bitmap = b2;
+                        }
+                    }
+                }
+
+                // make a new magic number since things are out of sync
+                do {
+                    magic = mRandom.nextLong();
+                } while (magic == 0);
+                if (bitmap != null) {
+                    byte [] data = miniThumbData(bitmap);
+                    if (createdThumbnailData != null) {
+                        createdThumbnailData[0] = data;
+                    }
+                    saveMiniThumbToFile(data, id, magic);
+                }
+
+                synchronized (c) {
+                    c.moveToPosition(i);
+                    c.updateLong(indexMiniThumbId(), magic);
+                    c.commitUpdates();
+                    c.requery();
+                    c.moveToPosition(i);
+
+                    if (existingImage != null) {
+                        existingImage.mMiniThumbMagic = magic;
+                    }
+                    return magic;
+                }
+            } finally {
+                mLock.unlock();
+            }
+        }
+
+        public void checkThumbnails(ThumbCheckCallback cb, int totalThumbnails) {
+            Cursor c = Images.Media.query(
+                    mContentResolver,
+                    mBaseUri,
+                    new String[] { "_id", "mini_thumb_magic" },
+                    thumbnailWhereClause(),
+                    thumbnailWhereClauseArgs(),
+                    "_id ASC");
+
+            int count = c.getCount();
+            if (VERBOSE)
+                Log.v(TAG, ">>>>>>>>>>> need to check " + c.getCount() + " rows");
+
+            c.close();
+
+            if (!ImageManager.hasStorage()) {
+                if (VERBOSE)
+                    Log.v(TAG, "bailing from the image checker thread -- no storage");
+                return;
+            }
+
+            String oldPath = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION - 1);
+            File oldFile = new File(oldPath);
+
+            if (count == 0) {
+                // now check that we have the right thumbs file
+//                Log.v(TAG, "count is zero but oldFile.exists() is " + oldFile.exists());
+                if (!oldFile.exists()) {
+                    return;
+                }
+            }
+
+            c = getCursor();
+            try {
+                if (VERBOSE) Log.v(TAG, "checkThumbnails found " + c.getCount());
+                int current = 0;
+                for (int i = 0; i < c.getCount(); i++) {
+                    try {
+                        checkThumbnail(null, c, i);
+                    } catch (Exception ex) {
+                        Log.e(TAG, "!!!!! failed to check thumbnail... was the sd card removed?");
+                        break;
+                    }
+                    if (cb != null) {
+                        if (!cb.checking(current, totalThumbnails)) {
+                            if (VERBOSE) Log.v(TAG, "got false from checking... break <<<<<<<<<<<<<<<<<<<<<<<<");
+                            break;
+                        }
+                    }
+                    current += 1;
+                }
+            } finally {
+                if (VERBOSE) Log.v(TAG, "checkThumbnails existing after reaching count " + c.getCount());
+                try {
+                    oldFile.delete();
+                } catch (Exception ex) {
+                    // ignore
+                }
+            }
+        }
+
+        protected String thumbnailWhereClause() {
+            return sMiniThumbIsNull + " and " + sWhereClause;
+        }
+
+        protected String[] thumbnailWhereClauseArgs() {
+            return sAcceptableImageTypes;
+        }
+
+        public void commitChanges() {
+            synchronized (mCursor) {
+                mCursor.commitUpdates();
+                requery();
+            }
+        }
+        protected Uri contentUri(long id) {
+            try {
+                // does our uri already have an id (single image query)?
+                // if so just return it
+                long existingId = ContentUris.parseId(mBaseUri);
+                if (existingId != id)
+                    Log.e(TAG, "id mismatch");
+                return mBaseUri;
+            } catch (NumberFormatException ex) {
+                // otherwise tack on the id
+                return ContentUris.withAppendedId(mBaseUri, id);
+            }
+        }
+
+        public void deactivate() {
+            mCursorDeactivated = true;
+            try {
+                mCursor.deactivate();
+            } catch (IllegalStateException e) {
+                // IllegalStateException may be thrown if the cursor is stale.
+                Log.e(TAG, "Caught exception while deactivating cursor.", e);
+            }
+            if (mMiniThumbData != null) {
+                try {
+                    mMiniThumbData.close();
+                    mMiniThumbData = null;
+                } catch (IOException ex) {
+
+                }
+            }
+        }
+
+        public void dump(String msg) {
+            int count = getCount();
+            if (VERBOSE) Log.v(TAG, "dump ImageList (count is " + count + ") " + msg);
+            for (int i = 0; i < count; i++) {
+                IImage img = getImageAt(i);
+                if (img == null)
+                    if (VERBOSE) Log.v(TAG, "   " + i + ": " + "null");
+                else
+                    if (VERBOSE) Log.v(TAG, "   " + i + ": " + img.toString());
+            }
+            if (VERBOSE) Log.v(TAG, "end of dump container");
+        }
+        public int getCount() {
+            Cursor c = getCursor();
+            synchronized (c) {
+                    try {
+                        return c.getCount();
+                    } catch (Exception ex) {
+                    }
+                return 0;
+            }
+        }
+
+        public boolean isEmpty() {
+            return getCount() == 0;
+        }
+
+        protected Cursor getCursor() {
+            synchronized (mCursor) {
+                if (mCursorDeactivated) {
+                    activateCursor();
+                }
+                return mCursor;
+            }
+        }
+
+        protected void activateCursor() {
+            requery();
+        }
+
+        public IImage getImageAt(int i) {
+            Cursor c = getCursor();
+            synchronized (c) {
+                boolean moved;
+                try {
+                    moved = c.moveToPosition(i);
+                } catch (Exception ex) {
+                    return null;
+                }
+                if (moved) {
+                    try {
+                        long id = c.getLong(0);
+                        long miniThumbId = 0;
+                        int rotation = 0;
+                        if (indexMiniThumbId() != -1) {
+                            miniThumbId = c.getLong(indexMiniThumbId());
+                        }
+                        if (indexOrientation() != -1) {
+                            rotation = c.getInt(indexOrientation());
+                        }
+                        long timestamp = c.getLong(1);
+                        IImage img = mCache.get(id);
+                        if (img == null) {
+                            img = make(id, miniThumbId, mContentResolver, this, timestamp, i, rotation);
+                            mCache.put(id, img);
+                        }
+                        return img;
+                    } catch (Exception ex) {
+                        Log.e(TAG, "got this exception trying to create image object: " + ex);
+                        return null;
+                    }
+                } else {
+                    Log.e(TAG, "unable to moveTo to " + i + "; count is " + c.getCount());
+                    return null;
+                }
+            }
+        }
+        public IImage getImageForUri(Uri uri) {
+            // TODO make this a hash lookup
+            for (int i = 0; i < getCount(); i++) {
+                if (getImageAt(i).fullSizeImageUri().equals(uri)) {
+                    return getImageAt(i);
+                }
+            }
+            return null;
+        }
+        private byte [] getMiniThumbFromFile(long id, byte [] data, long magicCheck) {
+            RandomAccessFile r = miniThumbDataFile();
+            if (r == null)
+                return null;
+
+            long pos = id * sBytesPerMiniThumb;
+            RandomAccessFile f = r;
+            synchronized (f) {
+                try {
+                    f.seek(pos);
+                    if (f.readByte() == 1) {
+                        long magic = f.readLong();
+                        if (magic != magicCheck) {
+                            if (VERBOSE) Log.v(TAG, "for id " + id + "; magic: " + magic + "; magicCheck: " + magicCheck + " (fail)");
+                            return null;
+                        }
+                        int length = f.readInt();
+                        f.read(data, 0, length);
+                        return data;
+                    } else {
+                        return null;
+                    }
+                } catch (IOException ex) {
+                    long fileLength;
+                    try {
+                        fileLength = f.length();
+                    } catch (IOException ex1) {
+                        fileLength = -1;
+                    }
+                    if (VERBOSE) {
+                        Log.e(TAG, "couldn't read thumbnail for " + id + "; " + ex.toString() + "; pos is " + pos + "; length is " + fileLength);
+                    }
+                    return null;
+                }
+            }
+        }
+        protected int getRowFor(IImage imageObj) {
+            Cursor c = getCursor();
+            synchronized (c) {
+                int index = 0;
+                long targetId = imageObj.fullSizeImageId();
+                if (c.moveToFirst()) {
+                    do {
+                        if (c.getLong(0) == targetId) {
+                            return index;
+                        }
+                        index += 1;
+                    } while (c.moveToNext());
+                }
+                return -1;
+            }
+        }
+
+        protected abstract int indexOrientation();
+        protected abstract int indexDateTaken();
+        protected abstract int indexDescription();
+        protected abstract int indexMimeType();
+        protected abstract int indexData();
+        protected abstract int indexId();
+        protected abstract int indexLatitude();
+        protected abstract int indexLongitude();
+        protected abstract int indexMiniThumbId();
+        protected abstract int indexPicasaWeb();
+        protected abstract int indexPrivate();
+        protected abstract int indexTitle();
+        protected abstract int indexDisplayName();
+        protected abstract int indexThumbId();
+
+        protected IImage make(long id, long miniThumbId, ContentResolver cr, IImageList list, long timestamp, int index, int rotation) {
+            return null;
+        }
+
+        protected abstract Bitmap makeBitmap(int targetWidthHeight, Uri uri, ParcelFileDescriptor pfdInput, BitmapFactory.Options options);
+
+        public boolean removeImage(IImage image) {
+            Cursor c = getCursor();
+            synchronized (c) {
+                /*
+                 * TODO: consider putting the image in a holding area so
+                 * we can get it back as needed
+                 * TODO: need to delete the thumbnails as well
+                 */
+                boolean moved;
+                try {
+                    moved = c.moveToPosition(image.getRow());
+                } catch (Exception ex) {
+                    Log.e(TAG, "removeImage got exception " + ex.toString());
+                    return false;
+                }
+                if (moved) {
+                    Uri u = image.fullSizeImageUri();
+                    mContentResolver.delete(u, null, null);
+                    image.onRemove();
+                    requery();
+                }
+            }
+            return true;
+        }
+
+
+        /* (non-Javadoc)
+         * @see com.android.camera.IImageList#removeImageAt(int)
+         */
+        public void removeImageAt(int i) {
+            Cursor c = getCursor();
+            synchronized (c) {
+                /*
+                 * TODO: consider putting the image in a holding area so
+                 * we can get it back as needed
+                 * TODO: need to delete the thumbnails as well
+                 */
+                dump("before delete");
+                IImage image = getImageAt(i);
+                boolean moved;
+                try {
+                    moved = c.moveToPosition(i);
+                } catch (Exception ex) {
+                    return;
+                }
+                if (moved) {
+                    Uri u = image.fullSizeImageUri();
+                    mContentResolver.delete(u, null, null);
+                    requery();
+                    image.onRemove();
+                }
+                dump("after delete");
+            }
+        }
+
+        public void removeOnChangeListener(OnChange changeCallback) {
+            if (changeCallback == mListener)
+                mListener = null;
+        }
+
+        protected void requery() {
+            mCache.clear();
+            mCursor.requery();
+            mCursorDeactivated = false;
+        }
+
+        protected void saveMiniThumbToFile(Bitmap bitmap, long id, long magic) throws IOException {
+            byte[] data = miniThumbData(bitmap);
+            saveMiniThumbToFile(data, id, magic);
+        }
+
+        protected void saveMiniThumbToFile(byte[] data, long id, long magic) throws IOException {
+            RandomAccessFile r = miniThumbDataFile();
+            if (r == null)
+                return;
+
+            long pos = id * sBytesPerMiniThumb;
+            long t0 = System.currentTimeMillis();
+            synchronized (r) {
+                try {
+                    long t1 = System.currentTimeMillis();
+                    long t2 = System.currentTimeMillis();
+                    if (data != null) {
+                        if (data.length > sBytesPerMiniThumb) {
+                            if (VERBOSE) Log.v(TAG, "!!!!!!!!!!!!!!!!!!!!!!!!!!! " + data.length + " > " + sBytesPerMiniThumb);
+                            return;
+                        }
+                        r.seek(pos);
+                        r.writeByte(0);     // we have no data in this slot
+
+                        // if magic is 0 then leave it alone
+                        if (magic == 0)
+                            r.skipBytes(8);
+                        else
+                            r.writeLong(magic);
+                        r.writeInt(data.length);
+                        r.write(data);
+                        //                      f.flush();
+                        r.seek(pos);
+                        r.writeByte(1);  // we have data in this slot
+                        long t3 = System.currentTimeMillis();
+
+                        if (VERBOSE) Log.v(TAG, "saveMiniThumbToFile took " + (t3-t0) + "; " + (t1-t0) + " " + (t2-t1) + " " + (t3-t2));
+                    }
+                } catch (IOException ex) {
+                    Log.e(TAG, "couldn't save mini thumbnail data for " + id + "; " + ex.toString());
+                    throw ex;
+                }
+            }
+        }
+
+        public void setOnChangeListener(OnChange changeCallback, Handler h) {
+            mListener = changeCallback;
+            mHandler = h;
+        }
+    }
+
+    public class CanceledException extends Exception {
+
+    }
+    public enum DataLocation { NONE, INTERNAL, EXTERNAL, ALL }
+
+    public interface IAddImage_cancelable extends ICancelable {
+        public void get();
+    }
+
+    /*
+     * The model for canceling an in-progress image save is this.  For any
+     * given part of the task of saving return an ICancelable.  The "result"
+     * from an ICancelable can be retrieved using the get* method.  If the
+     * operation was canceled then null is returned.  The act of canceling
+     * is to call "cancel" -- from another thread.
+     *
+     * In general an object which implements ICancelable will need to
+     * check, periodically, whether they are canceled or not.  This works
+     * well for some things and less well for others.
+     *
+     * Right now the actual jpeg encode does not check cancelation but
+     * the part of encoding which writes the data to disk does.  Note,
+     * though, that there is what appears to be a bug in the jpeg encoder
+     * in that if the stream that's being written is closed it crashes
+     * rather than returning an error.  TODO fix that.
+     *
+     * When an object detects that it is canceling it must, before exiting,
+     * call acknowledgeCancel.  This is necessary because the caller of
+     * cancel() will block until acknowledgeCancel is called.
+     */
+    public interface ICancelable {
+        /*
+         * call cancel() when the unit of work in progress needs to be
+         * canceled.  This should return true if it was possible to
+         * cancel and false otherwise.  If this returns false the caller
+         * may still be able to cleanup and simulate cancelation.
+         */
+        public boolean cancel();
+    }
+
+    public interface IGetBitmap_cancelable extends ICancelable {
+        // returns the bitmap or null if there was an error or we were canceled
+        public Bitmap get();
+    };
+    public interface IGetBoolean_cancelable extends ICancelable {
+        public boolean get();
+    }
+    public interface IImage {
+
+        public abstract void commitChanges();
+
+        /**
+         * Get the bitmap for the full size image.
+         * @return  the bitmap for the full size image.
+         */
+        public abstract Bitmap fullSizeBitmap(int targetWidthOrHeight);
+
+        /**
+         *
+         * @return an object which can be canceled while the bitmap is loading
+         */
+        public abstract IGetBitmap_cancelable fullSizeBitmap_cancelable(int targetWidthOrHeight);
+
+        /**
+         * Gets the input stream associated with a given full size image.
+         * This is used, for example, if one wants to email or upload
+         * the image.
+         * @return  the InputStream associated with the image.
+         */
+        public abstract InputStream fullSizeImageData();
+        public abstract long fullSizeImageId();
+        public abstract Uri fullSizeImageUri();
+        public abstract IImageList getContainer();
+        public abstract long getDateTaken();
+
+        /**
+         * Gets the description of the image.
+         * @return  the description of the image.
+         */
+        public abstract String getDescription();
+        public abstract String getMimeType();
+        public abstract int getHeight();
+
+        /**
+         * Gets the flag telling whether this video/photo is private or public.
+         * @return  the description of the image.
+         */
+        public abstract boolean getIsPrivate();
+
+        public abstract double getLatitude();
+
+        public abstract double getLongitude();
+
+        /**
+         * Gets the name of the image.
+         * @return  the name of the image.
+         */
+        public abstract String getTitle();
+
+        public abstract String getDisplayName();
+
+        public abstract String getPicasaId();
+
+        public abstract int getRow();
+
+        public abstract int getWidth();
+
+        public abstract boolean hasLatLong();
+
+        public abstract long imageId();
+
+        public abstract boolean isReadonly();
+
+        public abstract boolean isDrm();
+
+        public abstract Bitmap miniThumbBitmap();
+
+        public abstract void onRemove();
+
+        public abstract boolean rotateImageBy(int degrees);
+
+        /**
+         * Sets the description of the image.
+         */
+        public abstract void setDescription(String description);
+
+        /**
+         * Sets whether the video/photo is private or public.
+         */
+        public abstract void setIsPrivate(boolean isPrivate);
+
+        /**
+         * Sets the name of the image.
+         */
+        public abstract void setName(String name);
+
+        public abstract void setPicasaId(String id);
+
+        /**
+         * Get the bitmap for the medium thumbnail.
+         * @return  the bitmap for the medium thumbnail.
+         */
+        public abstract Bitmap thumbBitmap();
+
+        public abstract Uri thumbUri();
+
+        public abstract String getDataPath();
+    }
+
+    public interface IImageList {
+        public HashMap<String, String> getBucketIds();
+
+        public interface OnChange {
+            public void onChange(IImageList list);
+        }
+
+        public interface ThumbCheckCallback {
+            public boolean checking(int current, int count);
+        }
+
+        public abstract void checkThumbnails(ThumbCheckCallback cb, int totalCount);
+
+        public abstract void commitChanges();
+
+        public abstract void deactivate();
+
+        /**
+         * Returns the count of image objects.
+         *
+         * @return       the number of images
+         */
+        public abstract int getCount();
+
+        /**
+         * @return true if the count of image objects is zero.
+         */
+
+        public abstract boolean isEmpty();
+
+        /**
+         * Returns the image at the ith position.
+         *
+         * @param i     the position
+         * @return      the image at the ith position
+         */
+        public abstract IImage getImageAt(int i);
+
+        /**
+         * Returns the image with a particular Uri.
+         *
+         * @param uri
+         * @return      the image with a particular Uri.
+         */
+        public abstract IImage getImageForUri(Uri uri);;
+
+        /**
+         *
+         * @param image
+         * @return true if the image was removed.
+         */
+        public abstract boolean removeImage(IImage image);
+        /**
+         * Removes the image at the ith position.
+         * @param i     the position
+         */
+        public abstract void removeImageAt(int i);
+
+        public abstract void removeOnChangeListener(OnChange changeCallback);
+        public abstract void setOnChangeListener(OnChange changeCallback, Handler h);
+    }
+
+    class Image extends BaseImage implements IImage {
+        int mRotation;
+
+        protected Image(long id, long miniThumbId, ContentResolver cr, BaseImageList container, int cursorRow, int rotation) {
+            super(id, miniThumbId, cr, container, cursorRow);
+            mRotation = rotation;
+        }
+
+        public String getDataPath() {
+            String path = null;
+            Cursor c = getCursor();
+            synchronized (c) {
+                if (c.moveToPosition(getRow())) {
+                    int column = ((ImageList)getContainer()).indexData();
+                    if (column >= 0)
+                        path = c.getString(column);
+                }
+            }
+            return path;
+        }
+
+        protected int getDegreesRotated() {
+            return mRotation;
+        }
+
+        protected void setDegreesRotated(int degrees) {
+            Cursor c = getCursor();
+            mRotation = degrees;
+            synchronized (c) {
+                if (c.moveToPosition(getRow())) {
+                    int column = ((ImageList)getContainer()).indexOrientation();
+                    if (column >= 0) {
+                        c.updateInt(column, degrees);
+                        getContainer().commitChanges();
+                    }
+                }
+            }
+        }
+
+        protected Bitmap.CompressFormat compressionType() {
+            String mimeType = getMimeType();
+            if (mimeType == null)
+                return Bitmap.CompressFormat.JPEG;
+
+            if (mimeType.equals("image/png"))
+                return Bitmap.CompressFormat.PNG;
+            else if (mimeType.equals("image/gif"))
+                return Bitmap.CompressFormat.PNG;
+
+            return Bitmap.CompressFormat.JPEG;
+        }
+
+        /**
+         * Does not replace the tag if already there. Otherwise, adds to the exif tags.
+         * @param tag
+         * @param value
+         */
+        public void addExifTag(String tag, String value) {
+            if (mExifData == null) {
+                mExifData = new HashMap<String, String>();
+            }
+            if (!mExifData.containsKey(tag)) {
+                mExifData.put(tag, value);
+            } else {
+                if (VERBOSE) Log.v(TAG, "addExifTag where the key already was there: " + tag + " = " + value);
+            }
+        }
+
+        /**
+         * Return the value of the Exif tag as an int. Returns 0 on any type of error.
+         * @param tag
+         * @return
+         */
+        public int getExifTagInt(String tag) {
+            if (mExifData != null) {
+                String tagValue = mExifData.get(tag);
+                if (tagValue != null) {
+                    return Integer.parseInt(tagValue);
+                }
+            }
+            return 0;
+        }
+
+        public boolean isReadonly() {
+            String mimeType = getMimeType();
+            return !"image/jpeg".equals(mimeType) && !"image/png".equals(mimeType);
+        }
+
+        public boolean isDrm() {
+            return false;
+        }
+
+        /**
+         * Remove tag if already there. Otherwise, does nothing.
+         * @param tag
+         */
+        public void removeExifTag(String tag) {
+            if (mExifData == null) {
+                mExifData = new HashMap<String, String>();
+            }
+            mExifData.remove(tag);
+        }
+
+        /**
+         * Replaces the tag if already there. Otherwise, adds to the exif tags.
+         * @param tag
+         * @param value
+         */
+        public void replaceExifTag(String tag, String value) {
+            if (mExifData == null) {
+                mExifData = new HashMap<String, String>();
+            }
+            if (!mExifData.containsKey(tag)) {
+                mExifData.remove(tag);
+            }
+            mExifData.put(tag, value);
+        }
+
+        /* (non-Javadoc)
+         * @see com.android.camera.IImage#saveModifiedImage(android.graphics.Bitmap)
+         */
+        public IGetBoolean_cancelable saveImageContents(
+                final Bitmap image,
+                final byte [] jpegData,
+                final int orientation,
+                final boolean newFile,
+                final Cursor cursor) {
+            final class SaveImageContentsCancelable extends BaseCancelable implements IGetBoolean_cancelable {
+                IGetBoolean_cancelable mCurrentCancelable = null;
+
+                SaveImageContentsCancelable() {
+                }
+
+                public boolean doCancelWork() {
+                    synchronized (this) {
+                        if (mCurrentCancelable != null)
+                            mCurrentCancelable.cancel();
+                    }
+                    return true;
+                }
+
+                public boolean get() {
+                    try {
+                        Bitmap thumbnail = null;
+
+                        long t1 = System.currentTimeMillis();
+                        Uri uri = mContainer.contentUri(mId);
+                        synchronized (this) {
+                            checkCanceled();
+                            mCurrentCancelable = compressImageToFile(image, jpegData, uri);
+                        }
+
+                        long t2 = System.currentTimeMillis();
+                        if (!mCurrentCancelable.get())
+                            return false;
+
+                        synchronized (this) {
+                            String filePath;
+                            synchronized (cursor) {
+                                cursor.moveToPosition(0);
+                                filePath = cursor.getString(2);
+                            }
+                            // TODO: If thumbData is present and usable, we should call the version
+                            // of storeThumbnail which takes a byte array, rather than re-encoding
+                            // a new JPEG of the same dimensions.
+                            byte [] thumbData = null;
+                            synchronized (ImageManager.instance()) {
+                                thumbData = (new ExifInterface(filePath)).getThumbnail();
+                            }
+                            if (VERBOSE) Log.v(TAG, "for file " + filePath + " thumbData is " + thumbData + "; length " + (thumbData!=null ? thumbData.length : -1));
+                            if (thumbData != null) {
+                                thumbnail = BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length);
+                                if (VERBOSE) Log.v(TAG, "embedded thumbnail bitmap " + thumbnail.getWidth() + "/" + thumbnail.getHeight());
+                            }
+                            if (thumbnail == null && image != null) {
+                                thumbnail = image;
+                            }
+                            if (thumbnail == null && jpegData != null) {
+                                thumbnail = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
+                            }
+                        }
+
+                        long t3 = System.currentTimeMillis();
+                        mContainer.storeThumbnail(thumbnail, Image.this.fullSizeImageId());
+                        long t4 = System.currentTimeMillis();
+                        checkCanceled();
+                        if (VERBOSE) Log.v(TAG, ">>>>>>>>>>>>>>>>>>>>> rotating by " + orientation);
+                        try {
+                            saveMiniThumb(rotate(thumbnail, orientation));
+                        } catch (IOException e) {
+                            // Ignore if unable to save thumb.
+                        }
+                        long t5 = System.currentTimeMillis();
+                        checkCanceled();
+
+                        if (VERBOSE) Log.v(TAG, String.format("Timing data %d %d %d %d", t2-t1, t3-t2, t4-t3, t5-t4));
+                        return true;
+                    } catch (CanceledException ex) {
+                        if (VERBOSE) Log.v(TAG, "got canceled... need to cleanup");
+                        return false;
+                    } finally {
+                        /*
+                        Cursor c = getCursor();
+                        synchronized (c) {
+                            if (c.moveTo(getRow())) {
+                                mContainer.requery();
+                            }
+                        }
+                        */
+                        acknowledgeCancel();
+                    }
+                }
+            }
+            return new SaveImageContentsCancelable();
+        }
+
+        private void setExifRotation(int degrees) {
+            try {
+                Cursor c = getCursor();
+                String filePath;
+                synchronized (c) {
+                    filePath = c.getString(mContainer.indexData());
+                }
+                synchronized (ImageManager.instance()) {
+                    ExifInterface exif = new ExifInterface(filePath);
+                    if (mExifData == null) {
+                        mExifData = exif.getAttributes();
+                    }
+                    if (degrees < 0)
+                        degrees += 360;
+
+                    int orientation = ExifInterface.ORIENTATION_NORMAL;
+                    switch (degrees) {
+                    case 0:
+                        orientation = ExifInterface.ORIENTATION_NORMAL;
+                        break;
+                    case 90:
+                        orientation = ExifInterface.ORIENTATION_ROTATE_90;
+                        break;
+                    case 180:
+                        orientation = ExifInterface.ORIENTATION_ROTATE_180;
+                        break;
+                    case 270:
+                        orientation = ExifInterface.ORIENTATION_ROTATE_270;
+                        break;
+                    }
+
+                    replaceExifTag(ExifInterface.TAG_ORIENTATION, Integer.toString(orientation));
+                    replaceExifTag("UserComment", "saveRotatedImage comment orientation: " + orientation);
+                    exif.saveAttributes(mExifData);
+                    exif.commitChanges();
+                }
+            } catch (Exception ex) {
+                Log.e(TAG, "unable to save exif data with new orientation " + fullSizeImageUri());
+            }
+        }
+
+        /**
+         * Save the rotated image by updating the Exif "Orientation" tag.
+         * @param degrees
+         * @return
+         */
+        public boolean rotateImageBy(int degrees) {
+            int newDegrees = getDegreesRotated() + degrees;
+            setExifRotation(newDegrees);
+            setDegreesRotated(newDegrees);
+
+            // setting this to zero will force the call to checkCursor to generate fresh thumbs
+            mMiniThumbMagic = 0;
+            try {
+                mContainer.checkThumbnail(this, mContainer.getCursor(), this.getRow());
+            } catch (IOException e) {
+                // Ignore inability to store mini thumbnail.
+            }
+
+            return true;
+        }
+
+        public Bitmap thumbBitmap() {
+            Bitmap bitmap = null;
+            Cursor c = null;
+            if (mContainer.mThumbUri != null) {
+                try {
+                    c = mContentResolver.query(
+                            mContainer.mThumbUri,
+                            THUMB_PROJECTION,
+                            Thumbnails.IMAGE_ID + "=?",
+                            new String[] { String.valueOf(fullSizeImageId()) },
+                            null);
+                    if (c != null && c.moveToFirst()) {
+                        Uri thumbUri = ContentUris.withAppendedId(mContainer.mThumbUri, c.getLong(((ImageList)mContainer).INDEX_THUMB_ID));
+                        ParcelFileDescriptor pfdInput;
+                        try {
+                            BitmapFactory.Options options = new BitmapFactory.Options();
+                            options.inDither = false;
+                            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+                            pfdInput = mContentResolver.openFileDescriptor(thumbUri, "r");
+                            bitmap = BitmapFactory.decodeFileDescriptor(pfdInput.getFileDescriptor(), null, options);
+                            pfdInput.close();
+                        } catch (FileNotFoundException ex) {
+                            Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
+                        } catch (IOException ex) {
+                            Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
+                        } catch (NullPointerException ex) {
+                            // we seem to get this if the file doesn't exist anymore
+                            Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
+                        }
+                    }
+                } catch (Exception ex) {
+                    // sdcard removed?
+                    return null;
+                } finally {
+                    if (c != null)
+                        c.close();
+                }
+            }
+
+            if (bitmap == null) {
+                bitmap = fullSizeBitmap(THUMBNAIL_TARGET_SIZE, false);
+                if (VERBOSE) {
+                    Log.v(TAG, "no thumbnail found... storing new one for " + fullSizeImageId());
+                }
+                bitmap = mContainer.storeThumbnail(bitmap, fullSizeImageId());
+            }
+
+            if (bitmap != null) {
+                int degrees = getDegreesRotated();
+                if (degrees != 0) {
+                    Matrix m = new Matrix();
+                    m.setRotate(degrees, (float) bitmap.getWidth() / 2, (float) bitmap.getHeight() / 2);
+                    bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(),
+                                                            m, true);
+                }
+            }
+
+            long elapsed = System.currentTimeMillis();
+            return bitmap;
+        }
+
+    }
+
+    final static private String sWhereClause = "(" + Images.Media.MIME_TYPE + " in (?, ?, ?))";
+    final static private String[] sAcceptableImageTypes = new String[] { "image/jpeg", "image/png", "image/gif" };
+    final static private String sMiniThumbIsNull = "mini_thumb_magic isnull";
+
+    private static final String[] IMAGE_PROJECTION = new String[] {
+            "_id",
+            "_data",
+            ImageColumns.DATE_TAKEN,
+            ImageColumns.MINI_THUMB_MAGIC,
+            ImageColumns.ORIENTATION,
+            ImageColumns.MIME_TYPE
+        };
+
+    /**
+     * Represents an ordered collection of Image objects.
+     * Provides an api to add and remove an image.
+     */
+    class ImageList extends BaseImageList implements IImageList {
+        final int INDEX_ID               = indexOf(IMAGE_PROJECTION, "_id");
+        final int INDEX_DATA             = indexOf(IMAGE_PROJECTION, "_data");
+        final int INDEX_MIME_TYPE        = indexOf(IMAGE_PROJECTION, MediaColumns.MIME_TYPE);
+        final int INDEX_DATE_TAKEN       = indexOf(IMAGE_PROJECTION, ImageColumns.DATE_TAKEN);
+        final int INDEX_MINI_THUMB_MAGIC = indexOf(IMAGE_PROJECTION, ImageColumns.MINI_THUMB_MAGIC);
+        final int INDEX_ORIENTATION      = indexOf(IMAGE_PROJECTION, ImageColumns.ORIENTATION);
+
+        final int INDEX_THUMB_ID         = indexOf(THUMB_PROJECTION, BaseColumns._ID);
+        final int INDEX_THUMB_IMAGE_ID   = indexOf(THUMB_PROJECTION, Images.Thumbnails.IMAGE_ID);
+        final int INDEX_THUMB_WIDTH      = indexOf(THUMB_PROJECTION, Images.Thumbnails.WIDTH);
+        final int INDEX_THUMB_HEIGHT     = indexOf(THUMB_PROJECTION, Images.Thumbnails.HEIGHT);
+
+        boolean mIsRegistered = false;
+        ContentObserver mContentObserver;
+        DataSetObserver mDataSetObserver;
+
+        public HashMap<String, String> getBucketIds() {
+            Cursor c = Images.Media.query(
+                        mContentResolver,
+                        mBaseUri.buildUpon().appendQueryParameter("distinct", "true").build(),
+                        new String[] {
+                            ImageColumns.BUCKET_DISPLAY_NAME,
+                            ImageColumns.BUCKET_ID
+                        },
+                        whereClause(),
+                        whereClauseArgs(),
+                        sortOrder());
+
+            HashMap<String, String> hash = new HashMap<String, String>();
+            if (c != null && c.moveToFirst()) {
+                do {
+                    hash.put(c.getString(1), c.getString(0));
+                } while (c.moveToNext());
+            }
+            return hash;
+        }
+        /**
+         * ImageList constructor.
+         * @param cr    ContentResolver
+         */
+        public ImageList(Context ctx, ContentResolver cr, Uri imageUri, Uri thumbUri, int sort, String bucketId) {
+            super(ctx, cr, imageUri, sort, bucketId);
+            mBaseUri = imageUri;
+            mThumbUri = thumbUri;
+            mSort = sort;
+
+            mContentResolver = cr;
+
+            mCursor = createCursor();
+            if (mCursor == null) {
+                Log.e(TAG, "unable to create image cursor for " + mBaseUri);
+                throw new UnsupportedOperationException();
+            }
+
+            if (VERBOSE) {
+                Log.v(TAG, "for " + mBaseUri.toString() + " got cursor " + mCursor + " with length " + (mCursor != null ? mCursor.getCount() : "-1"));
+            }
+
+            final Runnable updateRunnable = new Runnable() {
+                public void run() {
+                    // handling these external updates is causing ANR problems that are unresolved.
+                    // For now ignore them since there shouldn't be anyone modifying the database on the fly.
+                    if (true)
+                        return;
+
+                    synchronized (mCursor) {
+                        requery();
+                    }
+                    if (mListener != null)
+                        mListener.onChange(ImageList.this);
+                }
+            };
+
+            mContentObserver = new ContentObserver(null) {
+                @Override
+                public boolean deliverSelfNotifications() {
+                    return false;
+                }
+
+                @Override
+                public void onChange(boolean selfChange) {
+                    if (VERBOSE) Log.v(TAG, "MyContentObserver.onChange; selfChange == " + selfChange);
+                    updateRunnable.run();
+                }
+            };
+
+            mDataSetObserver = new DataSetObserver() {
+                @Override
+                public void onChanged() {
+                    if (VERBOSE) Log.v(TAG, "MyDataSetObserver.onChanged");
+//                  updateRunnable.run();
+                }
+
+                @Override
+                public void onInvalidated() {
+                    if (VERBOSE) Log.v(TAG, "MyDataSetObserver.onInvalidated: " + mCursorDeactivated);
+                }
+            };
+
+            registerObservers();
+        }
+
+        private void registerObservers() {
+            if (mIsRegistered)
+                return;
+
+            mCursor.registerContentObserver(mContentObserver);
+            mCursor.registerDataSetObserver(mDataSetObserver);
+            mIsRegistered = true;
+        }
+
+        private void unregisterObservers() {
+            if (!mIsRegistered)
+                return;
+
+            mCursor.unregisterContentObserver(mContentObserver);
+            mCursor.unregisterDataSetObserver(mDataSetObserver);
+            mIsRegistered = false;
+        }
+
+        public void deactivate() {
+            super.deactivate();
+            unregisterObservers();
+        }
+
+        protected void activateCursor() {
+            super.activateCursor();
+            registerObservers();
+        }
+
+        protected String whereClause() {
+            if (mBucketId != null) {
+                return sWhereClause + " and " + Images.Media.BUCKET_ID + " = '" + mBucketId + "'";
+            } else {
+                return sWhereClause;
+            }
+        }
+
+        protected String[] whereClauseArgs() {
+            return sAcceptableImageTypes;
+        }
+
+        protected Cursor createCursor() {
+            Cursor c =
+                Images.Media.query(
+                    mContentResolver,
+                    mBaseUri,
+                    IMAGE_PROJECTION,
+                    whereClause(),
+                    whereClauseArgs(),
+                    sortOrder());
+            if (VERBOSE)
+                Log.v(TAG, "createCursor got cursor with count " + (c == null ? -1 : c.getCount()));
+            return c;
+        }
+
+        protected int indexOrientation() {  return INDEX_ORIENTATION;      }
+        protected int indexDateTaken()   {  return INDEX_DATE_TAKEN;       }
+        protected int indexDescription() {  return -1;                     }
+        protected int indexMimeType()    {  return INDEX_MIME_TYPE;        }
+        protected int indexData()        {  return INDEX_DATA;             }
+        protected int indexId()          {  return INDEX_ID;               }
+        protected int indexLatitude()    {  return -1;                     }
+        protected int indexLongitude()   {  return -1;                     }
+        protected int indexMiniThumbId() {  return INDEX_MINI_THUMB_MAGIC; }
+
+        protected int indexPicasaWeb()   {  return -1;                     }
+        protected int indexPrivate()     {  return -1;                     }
+        protected int indexTitle()       {  return -1;                     }
+        protected int indexDisplayName() {  return -1;                     }
+        protected int indexThumbId()     {  return INDEX_THUMB_ID;        }
+
+        @Override
+        protected IImage make(long id, long miniThumbId, ContentResolver cr, IImageList list, long timestamp, int index, int rotation) {
+            return new Image(id, miniThumbId, mContentResolver, this, index, rotation);
+        }
+
+        protected Bitmap makeBitmap(int targetWidthHeight, Uri uri, ParcelFileDescriptor pfd, BitmapFactory.Options options) {
+            Bitmap b = null;
+
+            try {
+                if (pfd == null)
+                    pfd = makeInputStream(uri);
+
+                if (pfd == null)
+                    return null;
+
+                if (options == null)
+                    options = new BitmapFactory.Options();
+
+                java.io.FileDescriptor fd = pfd.getFileDescriptor();
+                options.inSampleSize = 1;
+                if (targetWidthHeight != -1) {
+                    options.inJustDecodeBounds = true;
+                    long t1 = System.currentTimeMillis();
+                    BitmapFactory.decodeFileDescriptor(fd, null, options);
+                    long t2 = System.currentTimeMillis();
+                    if (options.mCancel || options.outWidth == -1 || options.outHeight == -1) {
+                        return null;
+                    }
+                    options.inSampleSize = computeSampleSize(options, targetWidthHeight);
+                    options.inJustDecodeBounds = false;
+                }
+
+                options.inDither = false;
+                options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+                long t1 = System.currentTimeMillis();
+                b = BitmapFactory.decodeFileDescriptor(fd, null, options);
+                long t2 = System.currentTimeMillis();
+                if (VERBOSE) {
+                    Log.v(TAG, "A: got bitmap " + b + " with sampleSize " + options.inSampleSize + " took " + (t2-t1));
+                }
+            } catch (OutOfMemoryError ex) {
+                if (VERBOSE) Log.v(TAG, "got oom exception " + ex);
+                return null;
+            } finally {
+                try {
+                    pfd.close();
+                } catch (IOException ex) {
+                }
+            }
+            return b;
+        }
+
+        private ParcelFileDescriptor makeInputStream(Uri uri) {
+            try {
+                return mContentResolver.openFileDescriptor(uri, "r");
+            } catch (IOException ex) {
+                return null;
+            }
+        }
+
+        private String sortOrder() {
+            // add id to the end so that we don't ever get random sorting
+            // which could happen, I suppose, if the first two values were
+            // duplicated
+            String ascending = (mSort == SORT_ASCENDING ? " ASC" : " DESC");
+            return
+                Images.Media.DATE_TAKEN + ascending + "," +
+                Images.Media._ID + ascending;
+        }
+
+    }
+
+    /**
+     * Represents an ordered collection of Image objects from the DRM provider.
+     */
+    class DrmImageList extends ImageList implements IImageList {
+        private final String[] DRM_IMAGE_PROJECTION = new String[] {
+            DrmStore.Audio._ID,
+            DrmStore.Audio.DATA,
+            DrmStore.Audio.MIME_TYPE,
+        };
+
+        final int INDEX_ID            = indexOf(DRM_IMAGE_PROJECTION, DrmStore.Audio._ID);
+        final int INDEX_MIME_TYPE     = indexOf(DRM_IMAGE_PROJECTION, DrmStore.Audio.MIME_TYPE);
+
+        public DrmImageList(Context ctx, ContentResolver cr, Uri imageUri, int sort, String bucketId) {
+            super(ctx, cr, imageUri, null, sort, bucketId);
+        }
+
+        protected Cursor createCursor() {
+            return mContentResolver.query(mBaseUri, DRM_IMAGE_PROJECTION, null, null, sortOrder());
+        }
+
+        @Override
+        public void checkThumbnails(ThumbCheckCallback cb, int totalCount) {
+            // do nothing
+        }
+
+        @Override
+        public long checkThumbnail(BaseImage existingImage, Cursor c, int i) {
+            return 0;
+        }
+
+        class DrmImage extends Image {
+            protected DrmImage(long id, ContentResolver cr, BaseImageList container, int cursorRow) {
+                super(id, 0, cr, container, cursorRow, 0);
+            }
+
+            public boolean isDrm() {
+                return true;
+            }
+
+            public boolean isReadonly() {
+                return true;
+            }
+
+            public Bitmap miniThumbBitmap() {
+                return fullSizeBitmap(MINI_THUMB_TARGET_SIZE);
+            }
+
+            public Bitmap thumbBitmap() {
+                return fullSizeBitmap(THUMBNAIL_TARGET_SIZE);
+            }
+
+            public String getDisplayName() {
+                return getTitle();
+            }
+        }
+
+        @Override
+        protected IImage make(long id, long miniThumbId, ContentResolver cr, IImageList list, long timestamp, int index, int rotation) {
+            return new DrmImage(id, mContentResolver, this, index);
+        }
+
+        protected int indexOrientation() {  return -1; }
+        protected int indexDateTaken()   {  return -1; }
+        protected int indexDescription() {  return -1; }
+        protected int indexMimeType()    {  return -1; }
+        protected int indexId()          {  return -1; }
+        protected int indexLatitude()    {  return -1; }
+        protected int indexLongitude()   {  return -1; }
+        protected int indexMiniThumbId() {  return -1; }
+        protected int indexPicasaWeb()   {  return -1; }
+        protected int indexPrivate()     {  return -1; }
+        protected int indexTitle()       {  return -1; }
+        protected int indexDisplayName() {  return -1; }
+        protected int indexThumbId()     {  return -1; }
+
+        // TODO review this probably should be based on DATE_TAKEN same as images
+        private String sortOrder() {
+            String ascending = (mSort == SORT_ASCENDING ? " ASC" : " DESC");
+            return
+                DrmStore.Images.TITLE  + ascending + "," +
+                DrmStore.Images._ID;
+        }
+    }
+
+    class ImageListUber implements IImageList {
+        private IImageList [] mSubList;
+        private int mSort;
+        private IImageList.OnChange mListener = null;
+        Handler mHandler;
+
+        // This is an array of Longs wherein each Long consists of
+        // two components.  The first component indicates the number of
+        // consecutive entries that belong to a given sublist.
+        // The second component indicates which sublist we're referring
+        // to (an int which is used to index into mSubList).
+        ArrayList<Long> mSkipList = null;
+
+        int [] mSkipCounts = null;
+
+        public HashMap<String, String> getBucketIds() {
+            HashMap<String, String> hashMap = new HashMap<String, String>();
+            for (IImageList list: mSubList) {
+                hashMap.putAll(list.getBucketIds());
+            }
+            return hashMap;
+        }
+
+        public ImageListUber(IImageList [] sublist, int sort) {
+            mSubList = sublist.clone();
+            mSort = sort;
+
+            if (mListener != null) {
+                for (IImageList list: sublist) {
+                    list.setOnChangeListener(new OnChange() {
+                        public void onChange(IImageList list) {
+                            if (mListener != null) {
+                                mListener.onChange(ImageListUber.this);
+                            }
+                        }
+                    }, mHandler);
+                }
+            }
+        }
+
+        public void checkThumbnails(ThumbCheckCallback cb, int totalThumbnails) {
+            for (IImageList i : mSubList) {
+                int count = i.getCount();
+                i.checkThumbnails(cb, totalThumbnails);
+                totalThumbnails -= count;
+            }
+        }
+
+        public void commitChanges() {
+            final IImageList sublist[] = mSubList;
+            final int length = sublist.length;
+            for (int i = 0; i < length; i++)
+                sublist[i].commitChanges();
+        }
+
+        public void deactivate() {
+            final IImageList sublist[] = mSubList;
+            final int length = sublist.length;
+            int pos = -1;
+            while (++pos < length) {
+                IImageList sub = sublist[pos];
+                sub.deactivate();
+            }
+        }
+
+        public int getCount() {
+            final IImageList sublist[] = mSubList;
+            final int length = sublist.length;
+            int count = 0;
+            for (int i = 0; i < length; i++)
+                count += sublist[i].getCount();
+            return count;
+        }
+
+        public boolean isEmpty() {
+            final IImageList sublist[] = mSubList;
+            final int length = sublist.length;
+            for (int i = 0; i < length; i++) {
+                if (! sublist[i].isEmpty()) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        // mSkipCounts is used to tally the counts as we traverse
+        // the mSkipList.  It's a member variable only so that
+        // we don't have to allocate each time through.  Otherwise
+        // it could just as easily be a local.
+
+        public synchronized IImage getImageAt(int index) {
+            if (index < 0 || index > getCount())
+                throw new IndexOutOfBoundsException("index " + index + " out of range max is " + getCount());
+
+            // first make sure our allocations are in order
+            if (mSkipCounts == null || mSubList.length > mSkipCounts.length)
+                mSkipCounts = new int[mSubList.length];
+
+            if (mSkipList == null)
+                mSkipList = new ArrayList<Long>();
+
+            // zero out the mSkipCounts since that's only used for the
+            // duration of the function call
+            for (int i = 0; i < mSubList.length; i++)
+                mSkipCounts[i] = 0;
+
+            // a counter of how many images we've skipped in
+            // trying to get to index.  alternatively we could
+            // have decremented index but, alas, I liked this
+            // way more.
+            int skipCount = 0;
+
+            // scan the existing mSkipList to see if we've computed
+            // enough to just return the answer
+            for (int i = 0; i < mSkipList.size(); i++) {
+                long v = mSkipList.get(i);
+
+                int offset = (int) (v & 0xFFFF);
+                int which  = (int) (v >> 32);
+
+                if (skipCount + offset > index) {
+                    int subindex = mSkipCounts[which] + (index - skipCount);
+                    IImage img = mSubList[which].getImageAt(subindex);
+                    return img;
+                }
+
+                skipCount += offset;
+                mSkipCounts[which] += offset;
+            }
+
+            // if we get here we haven't computed the answer for
+            // "index" yet so keep computing.  This means running
+            // through the list of images and either modifying the
+            // last entry or creating a new one.
+            long count = 0;
+            while (true) {
+                long maxTimestamp = mSort == SORT_ASCENDING ? Long.MAX_VALUE : Long.MIN_VALUE;
+                int which = -1;
+                for (int i = 0; i < mSubList.length; i++) {
+                    int pos = mSkipCounts[i];
+                    IImageList list = mSubList[i];
+                    if (pos < list.getCount()) {
+                        IImage image = list.getImageAt(pos);
+                        // this should never be null but sometimes the database is
+                        // causing problems and it is null
+                        if (image != null) {
+                            long timestamp = image.getDateTaken();
+                            if (mSort == SORT_ASCENDING ? (timestamp < maxTimestamp) : (timestamp > maxTimestamp)) {
+                                maxTimestamp = timestamp;
+                                which = i;
+                            }
+                        }
+                    }
+                }
+
+                if (which == -1) {
+                    if (VERBOSE) Log.v(TAG, "which is -1, returning null");
+                    return null;
+                }
+
+                boolean done = false;
+                count = 1;
+                if (mSkipList.size() > 0) {
+                    int pos = mSkipList.size() - 1;
+                    long oldEntry = mSkipList.get(pos);
+                    if ((oldEntry >> 32) == which) {
+                        long newEntry = oldEntry + 1;
+                        mSkipList.set(pos, newEntry);
+                        done = true;
+                    }
+                }
+                if (!done) {
+                    long newEntry = ((long)which << 32) | count;
+                    if (VERBOSE) {
+                        Log.v(TAG, "new entry is " + Long.toHexString(newEntry));
+                    }
+                    mSkipList.add(newEntry);
+                }
+
+                if (skipCount++ == index) {
+                    return mSubList[which].getImageAt(mSkipCounts[which]);
+                }
+                mSkipCounts[which] += 1;
+            }
+        }
+
+        public IImage getImageForUri(Uri uri) {
+            // TODO perhaps we can preflight the base of the uri
+            // against each sublist first
+            for (int i = 0; i < mSubList.length; i++) {
+                IImage img = mSubList[i].getImageForUri(uri);
+                if (img != null)
+                    return img;
+            }
+            return null;
+        }
+
+        /**
+         * Modify the skip list when an image is deleted by finding
+         * the relevant entry in mSkipList and decrementing the
+         * counter.  This is simple because deletion can never
+         * cause change the order of images.
+         */
+        public void modifySkipCountForDeletedImage(int index) {
+            int skipCount = 0;
+
+            for (int i = 0; i < mSkipList.size(); i++) {
+                long v = mSkipList.get(i);
+
+                int offset = (int) (v & 0xFFFF);
+                int which  = (int) (v >> 32);
+
+                if (skipCount + offset > index) {
+                    mSkipList.set(i, v-1);
+                    break;
+                }
+
+                skipCount += offset;
+            }
+        }
+
+        public boolean removeImage(IImage image) {
+            IImageList parent = image.getContainer();
+            int pos = -1;
+            int baseIndex = 0;
+            while (++pos < mSubList.length) {
+                IImageList sub = mSubList[pos];
+                if (sub == parent) {
+                    if (sub.removeImage(image)) {
+                        modifySkipCountForDeletedImage(baseIndex);
+                        return true;
+                    } else {
+                        break;
+                    }
+                }
+                baseIndex += sub.getCount();
+            }
+            return false;
+        }
+
+        public void removeImageAt(int index) {
+            IImage img = getImageAt(index);
+            if (img != null) {
+                IImageList list = img.getContainer();
+                if (list != null) {
+                    list.removeImage(img);
+                    modifySkipCountForDeletedImage(index);
+                }
+            }
+        }
+
+        public void removeOnChangeListener(OnChange changeCallback) {
+            if (changeCallback == mListener)
+                mListener = null;
+        }
+
+        public void setOnChangeListener(OnChange changeCallback, Handler h) {
+            mListener = changeCallback;
+            mHandler = h;
+        }
+
+    }
+
+    public static abstract class SimpleBaseImage implements IImage {
+        public void commitChanges() {
+            throw new UnsupportedOperationException();
+        }
+
+        public InputStream fullSizeImageData() {
+            throw new UnsupportedOperationException();
+        }
+
+        public long fullSizeImageId() {
+            return 0;
+        }
+
+        public Uri fullSizeImageUri() {
+            throw new UnsupportedOperationException();
+        }
+
+        public IImageList getContainer() {
+            return null;
+        }
+
+        public long getDateTaken() {
+            return 0;
+        }
+
+        public String getMimeType() {
+            throw new UnsupportedOperationException();
+        }
+
+        public String getDescription() {
+            throw new UnsupportedOperationException();
+        }
+
+        public boolean getIsPrivate() {
+            throw new UnsupportedOperationException();
+        }
+
+        public double getLatitude() {
+            return 0D;
+        }
+
+        public double getLongitude() {
+            return 0D;
+        }
+
+        public String getTitle() {
+            throw new UnsupportedOperationException();
+        }
+
+        public String getDisplayName() {
+            throw new UnsupportedOperationException();
+        }
+
+        public String getPicasaId() {
+            return null;
+        }
+
+        public int getRow() {
+            throw new UnsupportedOperationException();
+        }
+
+        public int getHeight() {
+            return 0;
+        }
+
+        public int getWidth() {
+            return 0;
+        }
+
+        public boolean hasLatLong() {
+            return false;
+        }
+
+        public boolean isReadonly() {
+            return true;
+        }
+
+        public boolean isDrm() {
+            return false;
+        }
+
+        public void onRemove() {
+            throw new UnsupportedOperationException();
+        }
+
+        public boolean rotateImageBy(int degrees) {
+            return false;
+        }
+
+        public void setDescription(String description) {
+            throw new UnsupportedOperationException();
+        }
+
+        public void setIsPrivate(boolean isPrivate) {
+            throw new UnsupportedOperationException();
+        }
+
+        public void setName(String name) {
+            throw new UnsupportedOperationException();
+        }
+
+        public void setPicasaId(long id) {
+        }
+
+        public void setPicasaId(String id) {
+        }
+
+        public Uri thumbUri() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    class SingleImageList extends BaseImageList implements IImageList {
+        private IImage mSingleImage;
+        private ContentResolver mContentResolver;
+        private Uri mUri;
+
+        class UriImage extends SimpleBaseImage {
+
+            UriImage() {
+            }
+
+            public String getDataPath() {
+                return mUri.getPath();
+            }
+
+            InputStream getInputStream() {
+                try {
+                    if (mUri.getScheme().equals("file")) {
+                        String path = mUri.getPath();
+                        if (VERBOSE)
+                            Log.v(TAG, "path is " + path);
+                        return new java.io.FileInputStream(mUri.getPath());
+                    } else {
+                        return mContentResolver.openInputStream(mUri);
+                    }
+                } catch (FileNotFoundException ex) {
+                    return null;
+                }
+            }
+
+            ParcelFileDescriptor getPFD() {
+                try {
+                    if (mUri.getScheme().equals("file")) {
+                        String path = mUri.getPath();
+                        if (VERBOSE)
+                            Log.v(TAG, "path is " + path);
+                        return ParcelFileDescriptor.open(new File(path), ParcelFileDescriptor.MODE_READ_ONLY);
+                    } else {
+                        return mContentResolver.openFileDescriptor(mUri, "r");
+                    }
+                } catch (FileNotFoundException ex) {
+                    return null;
+                }
+            }
+
+            /* (non-Javadoc)
+             * @see com.android.camera.ImageManager.IImage#fullSizeBitmap(int)
+             */
+            public Bitmap fullSizeBitmap(int targetWidthHeight) {
+                try {
+                    ParcelFileDescriptor pfdInput = getPFD();
+                    BitmapFactory.Options options = new BitmapFactory.Options();
+                    options.inJustDecodeBounds = true;
+                    BitmapFactory.decodeFileDescriptor(pfdInput.getFileDescriptor(), null, options);
+
+                    if (targetWidthHeight != -1)
+                        options.inSampleSize = computeSampleSize(options, targetWidthHeight);
+
+                    options.inJustDecodeBounds = false;
+                    options.inDither = false;
+                    options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+
+                    Bitmap b = BitmapFactory.decodeFileDescriptor(pfdInput.getFileDescriptor(), null, options);
+                    if (VERBOSE) {
+                        Log.v(TAG, "B: got bitmap " + b + " with sampleSize " + options.inSampleSize);
+                    }
+                    pfdInput.close();
+                    return b;
+                } catch (Exception ex) {
+                    Log.e(TAG, "got exception decoding bitmap " + ex.toString());
+                    return null;
+                }
+            }
+
+            public IGetBitmap_cancelable fullSizeBitmap_cancelable(final int targetWidthOrHeight) {
+                final class LoadBitmapCancelable extends BaseCancelable implements IGetBitmap_cancelable {
+                    ParcelFileDescriptor pfdInput;
+                    BitmapFactory.Options mOptions = new BitmapFactory.Options();
+                    long mCancelInitiationTime;
+
+                    public LoadBitmapCancelable(ParcelFileDescriptor pfd) {
+                        pfdInput = pfd;
+                    }
+
+                    public boolean doCancelWork() {
+                        if (VERBOSE)
+                            Log.v(TAG, "requesting bitmap load cancel");
+                        mCancelInitiationTime = System.currentTimeMillis();
+                        mOptions.requestCancelDecode();
+                        return true;
+                    }
+
+                    public Bitmap get() {
+                        try {
+                            Bitmap b = makeBitmap(targetWidthOrHeight, fullSizeImageUri(), pfdInput, mOptions);
+                            if (b == null && mCancelInitiationTime != 0) {
+                                if (VERBOSE)
+                                    Log.v(TAG, "cancel returned null bitmap -- took " + (System.currentTimeMillis()-mCancelInitiationTime));
+                            }
+                            if (VERBOSE) Log.v(TAG, "b is " + b);
+                            return b;
+                        } catch (Exception ex) {
+                            return null;
+                        } finally {
+                            acknowledgeCancel();
+                        }
+                    }
+                }
+
+                try {
+                    ParcelFileDescriptor pfdInput = getPFD();
+                    if (pfdInput == null)
+                        return null;
+                    if (VERBOSE) Log.v(TAG, "inputStream is " + pfdInput);
+                    return new LoadBitmapCancelable(pfdInput);
+                } catch (UnsupportedOperationException ex) {
+                    return null;
+                }
+            }
+
+            @Override
+            public Uri fullSizeImageUri() {
+                return mUri;
+            }
+
+            @Override
+            public InputStream fullSizeImageData() {
+                return getInputStream();
+            }
+
+            public long imageId() {
+                return 0;
+            }
+
+            public Bitmap miniThumbBitmap() {
+                return thumbBitmap();
+            }
+
+            @Override
+            public String getTitle() {
+                return mUri.toString();
+            }
+
+            @Override
+            public String getDisplayName() {
+                return getTitle();
+            }
+
+            @Override
+            public String getDescription() {
+                return "";
+            }
+
+            public Bitmap thumbBitmap() {
+                Bitmap b = fullSizeBitmap(THUMBNAIL_TARGET_SIZE);
+                if (b != null) {
+                    Matrix m = new Matrix();
+                    float scale = Math.min(1F, THUMBNAIL_TARGET_SIZE / (float) b.getWidth());
+                    m.setScale(scale, scale);
+                    Bitmap scaledBitmap = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, true);
+                    return scaledBitmap;
+                } else {
+                    return null;
+                }
+            }
+
+            private BitmapFactory.Options snifBitmapOptions() {
+                ParcelFileDescriptor input = getPFD();
+                if (input == null)
+                    return null;
+                try {
+                    Uri uri = fullSizeImageUri();
+                    BitmapFactory.Options options = new BitmapFactory.Options();
+                    options.inJustDecodeBounds = true;
+                    BitmapFactory.decodeFileDescriptor(input.getFileDescriptor(), null, options);
+                    return options;
+                } finally {
+                    try {
+                        if (input != null) {
+                            input.close();
+                        }
+                    } catch (IOException ex) {
+                    }
+                }
+            }
+
+            @Override
+            public String getMimeType() {
+                BitmapFactory.Options options = snifBitmapOptions();
+                return (options!=null) ? options.outMimeType : "";
+            }
+
+            @Override
+            public int getHeight() {
+                BitmapFactory.Options options = snifBitmapOptions();
+                return (options!=null) ? options.outHeight : 0;
+            }
+
+            @Override
+            public int getWidth() {
+                BitmapFactory.Options options = snifBitmapOptions();
+                return (options!=null) ? options.outWidth : 0;
+            }
+        }
+
+        public SingleImageList(ContentResolver cr, Uri uri) {
+            super(null, cr, uri, ImageManager.SORT_ASCENDING, null);
+            mContentResolver = cr;
+            mUri = uri;
+            mSingleImage = new UriImage();
+        }
+
+        public HashMap<String, String> getBucketIds() {
+            throw new UnsupportedOperationException();
+        }
+
+        public void deactivate() {
+            // nothing to do here
+        }
+
+        public int getCount() {
+            return 1;
+        }
+
+        public boolean isEmpty() {
+            return false;
+        }
+
+        public IImage getImageAt(int i) {
+            if (i == 0)
+                return mSingleImage;
+
+            return null;
+        }
+
+        public IImage getImageForUri(Uri uri) {
+            if (uri.equals(mUri))
+                return mSingleImage;
+            else
+                return null;
+        }
+
+        public IImage getImageWithId(long id) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        protected int indexOrientation() {
+            return -1;
+        }
+
+        @Override
+        protected int indexDateTaken() {
+            return -1;
+        }
+
+        @Override
+        protected int indexMimeType() {
+            return -1;
+        }
+
+        @Override
+        protected int indexDescription() {
+            return -1;
+        }
+
+        @Override
+        protected int indexId() {
+            return -1;
+        }
+
+        @Override
+        protected int indexData() {
+            return -1;
+        }
+
+        @Override
+        protected int indexLatitude() {
+            return -1;
+        }
+
+        @Override
+        protected int indexLongitude() {
+            return -1;
+        }
+
+        @Override
+        protected int indexMiniThumbId() {
+            return -1;
+        }
+
+        @Override
+        protected int indexPicasaWeb() {
+            return -1;
+        }
+
+        @Override
+        protected int indexPrivate() {
+            return -1;
+        }
+
+        @Override
+        protected int indexTitle() {
+            return -1;
+        }
+
+        @Override
+        protected int indexDisplayName() {
+            return -1;
+        }
+
+        @Override
+        protected int indexThumbId() {
+            return -1;
+        }
+
+        private InputStream makeInputStream(Uri uri) {
+            InputStream input = null;
+            try {
+                input = mContentResolver.openInputStream(uri);
+                return input;
+            } catch (IOException ex) {
+                return null;
+            }
+        }
+
+        @Override
+        protected Bitmap makeBitmap(int targetWidthHeight, Uri uri, ParcelFileDescriptor pfdInput, BitmapFactory.Options options) {
+            Bitmap b = null;
+
+            try {
+                if (options == null)
+                    options = new BitmapFactory.Options();
+                options.inSampleSize = 1;
+
+                if (targetWidthHeight != -1) {
+                    options.inJustDecodeBounds = true;
+                    BitmapFactory.decodeFileDescriptor(pfdInput.getFileDescriptor(), null, options);
+
+                    options.inSampleSize = computeSampleSize(options, targetWidthHeight);
+                    options.inJustDecodeBounds = false;
+                }
+                b = BitmapFactory.decodeFileDescriptor(pfdInput.getFileDescriptor(), null, options);
+                if (VERBOSE) {
+                    Log.v(TAG, "C: got bitmap " + b + " with sampleSize " + options.inSampleSize);
+                }
+            } catch (OutOfMemoryError ex) {
+                if (VERBOSE) Log.v(TAG, "got oom exception " + ex);
+                return null;
+            } finally {
+                try {
+                    pfdInput.close();
+                } catch (IOException ex) {
+                }
+            }
+            return b;
+        }
+   }
+
+    class ThreadSafeOutputStream extends OutputStream {
+        java.io.OutputStream mDelegateStream;
+        boolean mClosed;
+
+        public ThreadSafeOutputStream(OutputStream delegate) {
+            mDelegateStream = delegate;
+        }
+
+        @Override
+        synchronized public void close() throws IOException {
+            try {
+                mClosed = true;
+                mDelegateStream.close();
+            } catch (IOException ex) {
+
+            }
+        }
+
+        @Override
+        synchronized public void flush() throws IOException {
+            super.flush();
+        }
+
+        @Override
+        public void write(byte[] b, int offset, int length) throws IOException {
+            /*
+            mDelegateStream.write(b, offset, length);
+            return;
+            */
+            while (length > 0) {
+                synchronized (this) {
+                    if (mClosed)
+                        return;
+
+                    int writeLength = Math.min(8192, length);
+                    mDelegateStream.write(b, offset, writeLength);
+                    offset += writeLength;
+                    length -= writeLength;
+                }
+            }
+        }
+
+        @Override
+        synchronized public void write(int oneByte) throws IOException {
+            if (mClosed)
+                return;
+            mDelegateStream.write(oneByte);
+        }
+    }
+
+    class VideoList extends BaseImageList implements IImageList {
+        private final String[] sProjection = new String[] {
+                Video.Media._ID,
+                Video.Media.DATA,
+                Video.Media.DATE_TAKEN,
+                Video.Media.TITLE,
+                Video.Media.DISPLAY_NAME,
+                Video.Media.DESCRIPTION,
+                Video.Media.IS_PRIVATE,
+                Video.Media.TAGS,
+                Video.Media.CATEGORY,
+                Video.Media.LANGUAGE,
+                Video.Media.LATITUDE,
+                Video.Media.LONGITUDE,
+                Video.Media.MINI_THUMB_MAGIC,
+                Video.Media.MIME_TYPE,
+        };
+
+        final int INDEX_ID               = indexOf(sProjection, Video.Media._ID);
+        final int INDEX_DATA             = indexOf(sProjection, Video.Media.DATA);
+        final int INDEX_DATE_TAKEN       = indexOf(sProjection, Video.Media.DATE_TAKEN);
+        final int INDEX_TITLE            = indexOf(sProjection, Video.Media.TITLE);
+        final int INDEX_DISPLAY_NAME     = indexOf(sProjection, Video.Media.DISPLAY_NAME);
+        final int INDEX_MIME_TYPE        = indexOf(sProjection, Video.Media.MIME_TYPE);
+        final int INDEX_DESCRIPTION      = indexOf(sProjection, Video.Media.DESCRIPTION);
+        final int INDEX_PRIVATE          = indexOf(sProjection, Video.Media.IS_PRIVATE);
+        final int INDEX_TAGS             = indexOf(sProjection, Video.Media.TAGS);
+        final int INDEX_CATEGORY         = indexOf(sProjection, Video.Media.CATEGORY);
+        final int INDEX_LANGUAGE         = indexOf(sProjection, Video.Media.LANGUAGE);
+        final int INDEX_LATITUDE         = indexOf(sProjection, Video.Media.LATITUDE);
+        final int INDEX_LONGITUDE        = indexOf(sProjection, Video.Media.LONGITUDE);
+        final int INDEX_MINI_THUMB_MAGIC = indexOf(sProjection, Video.Media.MINI_THUMB_MAGIC);
+        final int INDEX_THUMB_ID         = indexOf(sProjection, BaseColumns._ID);
+
+        public VideoList(Context ctx, ContentResolver cr, Uri uri, Uri thumbUri,
+                int sort, String bucketId) {
+            super(ctx, cr, uri, sort, bucketId);
+
+            mCursor = createCursor();
+            if (mCursor == null) {
+                Log.e(TAG, "unable to create video cursor for " + mBaseUri);
+                throw new UnsupportedOperationException();
+            }
+
+            if (Config.LOGV) {
+                Log.v(TAG, "for " + mUri.toString() + " got cursor " + mCursor + " with length "
+                        + (mCursor != null ? mCursor.getCount() : -1));
+            }
+
+            if (mCursor == null) {
+                throw new UnsupportedOperationException();
+            }
+            if (mCursor != null && mCursor.moveToFirst()) {
+                int row = 0;
+                do {
+                    long imageId = mCursor.getLong(indexId());
+                    long dateTaken = mCursor.getLong(indexDateTaken());
+                    long miniThumbId = mCursor.getLong(indexMiniThumbId());
+                    mCache.put(imageId, new VideoObject(imageId, miniThumbId, mContentResolver,
+                            this, dateTaken, row++));
+                } while (mCursor.moveToNext());
+            }
+        }
+
+        public HashMap<String, String> getBucketIds() {
+            Cursor c = Images.Media.query(
+                    mContentResolver,
+                    mBaseUri.buildUpon().appendQueryParameter("distinct", "true").build(),
+                    new String[] {
+                        VideoColumns.BUCKET_DISPLAY_NAME,
+                        VideoColumns.BUCKET_ID
+                    },
+                    whereClause(),
+                    whereClauseArgs(),
+                    sortOrder());
+
+        HashMap<String, String> hash = new HashMap<String, String>();
+        if (c != null && c.moveToFirst()) {
+            do {
+                hash.put(c.getString(1), c.getString(0));
+            } while (c.moveToNext());
+        }
+        return hash;
+        }
+
+        protected String whereClause() {
+            if (mBucketId != null) {
+                return Images.Media.BUCKET_ID + " = '" + mBucketId + "'";
+            } else {
+                return null;
+            }
+        }
+
+        protected String[] whereClauseArgs() {
+            return null;
+        }
+
+        @Override
+        protected String thumbnailWhereClause() {
+            return sMiniThumbIsNull;
+        }
+
+        @Override
+        protected String[] thumbnailWhereClauseArgs() {
+            return null;
+        }
+
+        protected Cursor createCursor() {
+            Cursor c =
+                Images.Media.query(
+                    mContentResolver,
+                    mBaseUri,
+                    sProjection,
+                    whereClause(),
+                    whereClauseArgs(),
+                    sortOrder());
+            if (VERBOSE)
+                Log.v(TAG, "createCursor got cursor with count " + (c == null ? -1 : c.getCount()));
+            return c;
+        }
+
+        protected int indexOrientation() {  return -1;                    }
+        protected int indexDateTaken()   {  return INDEX_DATE_TAKEN;      }
+        protected int indexDescription() {  return INDEX_DESCRIPTION;     }
+        protected int indexMimeType()    {  return INDEX_MIME_TYPE;       }
+        protected int indexData()        {  return INDEX_DATA;            }
+        protected int indexId()          {  return INDEX_ID;              }
+        protected int indexLatitude()    {  return INDEX_LATITUDE;        }
+        protected int indexLongitude()   {  return INDEX_LONGITUDE;       }
+        protected int indexMiniThumbId() {  return INDEX_MINI_THUMB_MAGIC;   }
+        protected int indexPicasaWeb()   {  return -1;                    }
+        protected int indexPrivate()     {  return INDEX_PRIVATE;         }
+        protected int indexTitle()       {  return INDEX_TITLE;           }
+        protected int indexDisplayName() {  return -1;                    }
+        protected int indexThumbId()     {  return INDEX_THUMB_ID;        }
+
+        @Override
+        protected IImage make(long id, long miniThumbId, ContentResolver cr, IImageList list,
+                long timestamp, int index, int rotation) {
+            return new VideoObject(id, miniThumbId, mContentResolver, this, timestamp, index);
+        }
+
+        @Override
+        protected Bitmap makeBitmap(int targetWidthHeight, Uri uri, ParcelFileDescriptor pfdInput,
+                BitmapFactory.Options options) {
+            MediaPlayer mp = new MediaPlayer();
+            Bitmap thumbnail = sDefaultThumbnail;
+            try {
+                mp.setDataSource(mContext, uri);
+//              int duration = mp.getDuration();
+//              int at = duration > 2000 ? 1000 : duration / 2;
+                int at = 1000;
+                thumbnail = mp.getFrameAt(at);
+                if (Config.LOGV) {
+                    if ( thumbnail != null) {
+                        Log.v(TAG, "getFrameAt @ " + at + " returned " + thumbnail + "; " +
+                                thumbnail.getWidth() + " " + thumbnail.getHeight());
+                    } else {
+                        Log.v(TAG, "getFrame @ " + at + " failed for " + uri);
+                    }
+                }
+            } catch (IOException ex) {
+            } catch (IllegalArgumentException ex) {
+            } catch (SecurityException ex) {
+            } finally {
+                mp.release();
+            }
+            return thumbnail;
+        }
+
+
+        private String sortOrder() {
+            return Video.Media.DATE_TAKEN + (mSort == SORT_ASCENDING ? " ASC " : " DESC");
+        }
+    }
+
+    private final static Bitmap sDefaultThumbnail = Bitmap.createBitmap(32, 32, Bitmap.Config.RGB_565);
+
+    /**
+     * Represents a particular video and provides access
+     * to the underlying data and two thumbnail bitmaps
+     * as well as other information such as the id, and
+     * the path to the actual video data.
+     */
+    class VideoObject extends BaseImage implements IImage {
+        /**
+         * Constructor.
+         *
+         * @param id        the image id of the image
+         * @param cr        the content resolver
+         */
+        protected VideoObject(long id, long miniThumbId, ContentResolver cr, VideoList container,
+                long dateTaken, int row) {
+            super(id, miniThumbId, cr, container, row);
+        }
+
+        protected Bitmap.CompressFormat compressionType() {
+            return Bitmap.CompressFormat.JPEG;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other == null)
+                return false;
+            if (!(other instanceof VideoObject))
+                return false;
+
+            return fullSizeImageUri().equals(((VideoObject)other).fullSizeImageUri());
+        }
+
+        public String getDataPath() {
+            String path = null;
+            Cursor c = getCursor();
+            synchronized (c) {
+                if (c.moveToPosition(getRow())) {
+                    int column = ((VideoList)getContainer()).indexData();
+                    if (column >= 0)
+                        path = c.getString(column);
+                }
+            }
+            return path;
+        }
+
+        /* (non-Javadoc)
+         * @see com.android.camera.IImage#fullSizeBitmap()
+         */
+        public Bitmap fullSizeBitmap(int targetWidthHeight) {
+            return sNoImageBitmap;
+        }
+
+        public IGetBitmap_cancelable fullSizeBitmap_cancelable(int targetWidthHeight) {
+            return null;
+        }
+
+        /* (non-Javadoc)
+         * @see com.android.camera.IImage#fullSizeImageData()
+         */
+        public InputStream fullSizeImageData() {
+            try {
+                InputStream input = mContentResolver.openInputStream(
+                        fullSizeImageUri());
+                return input;
+            } catch (IOException ex) {
+                return null;
+            }
+        }
+
+        /* (non-Javadoc)
+         * @see com.android.camera.IImage#fullSizeImageId()
+         */
+        public long fullSizeImageId() {
+            return mId;
+        }
+
+        public String getCategory() {
+             return getStringEntry(((VideoList)mContainer).INDEX_CATEGORY);
+         }
+
+        public int getHeight() {
+             return 0;
+         }
+
+        public String getLanguage() {
+             return getStringEntry(((VideoList)mContainer).INDEX_LANGUAGE);
+         }
+
+        public String getPicasaId() {
+            return null;
+        }
+
+        private String getStringEntry(int entryName) {
+            String entry = null;
+            Cursor c = getCursor();
+            synchronized(c) {
+                if (c.moveToPosition(getRow())) {
+                    entry = c.getString(entryName);
+                }
+            }
+            return entry;
+        }
+
+        public String getTags() {
+             return getStringEntry(((VideoList)mContainer).INDEX_TAGS);
+         }
+
+        public int getWidth() {
+             return 0;
+         }
+
+         /* (non-Javadoc)
+         * @see com.android.camera.IImage#imageId()
+         */
+        public long imageId() {
+            return mId;
+        }
+
+         public boolean isReadonly() {
+             return false;
+         }
+
+         public boolean isDrm() {
+             return false;
+         }
+
+         public boolean rotateImageBy(int degrees) {
+            return false;
+        }
+
+         public void setCategory(String category) {
+             setStringEntry(category, ((VideoList)mContainer).INDEX_CATEGORY);
+         }
+
+         public void setLanguage(String language) {
+             setStringEntry(language, ((VideoList)mContainer).INDEX_LANGUAGE);
+         }
+
+         private void setStringEntry(String entry, int entryName) {
+            Cursor c = getCursor();
+            synchronized (c) {
+                if (c.moveToPosition(getRow())) {
+                    c.updateString(entryName, entry);
+                }
+            }
+        }
+
+         public void setTags(String tags) {
+             setStringEntry(tags, ((VideoList)mContainer).INDEX_TAGS);
+         }
+
+         /* (non-Javadoc)
+         * @see com.android.camera.IImage#thumb1()
+         */
+        public Bitmap thumbBitmap() {
+            return fullSizeBitmap(320);
+        }
+
+         @Override
+         public String toString() {
+             StringBuilder sb = new StringBuilder();
+             sb.append("" + mId);
+             return sb.toString();
+         }
+    }
+
+    private final static Bitmap sNoImageBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565);
+
+    /*
+     * How much quality to use when storing the thumbnail.
+     */
+    private static ImageManager sInstance = null;
+    private static final int MINI_THUMB_TARGET_SIZE = 96;
+    private static final int THUMBNAIL_TARGET_SIZE = 320;
+
+    private static final String[] THUMB_PROJECTION = new String[] {
+        BaseColumns._ID,           // 0
+        Images.Thumbnails.IMAGE_ID,      // 1
+        Images.Thumbnails.WIDTH,
+        Images.Thumbnails.HEIGHT
+    };
+
+    private static Uri sStorageURI   = Images.Media.EXTERNAL_CONTENT_URI;
+
+    private static Uri sThumbURI     = Images.Thumbnails.EXTERNAL_CONTENT_URI;
+
+    private static Uri sVideoStorageURI = Uri.parse("content://media/external/video/media");
+
+    private static Uri sVideoThumbURI = Uri.parse("content://media/external/video/thumbnails");
+    /**
+     * Returns an ImageList object that contains
+     * all of the images.
+     * @param cr
+     * @param location
+     * @param includeImages
+     * @param includeVideo
+     * @return the singleton ImageList
+     */
+    static final public int SORT_ASCENDING = 1;
+
+    static final public int SORT_DESCENDING = 2;
+
+    static final public int INCLUDE_IMAGES     = (1 << 0);
+    static final public int INCLUDE_DRM_IMAGES = (1 << 1);
+    static final public int INCLUDE_VIDEOS     = (1 << 2);
+
+    static public DataLocation getDefaultDataLocation() {
+        return DataLocation.EXTERNAL;
+    }
+    private static int indexOf(String [] array, String s) {
+        for (int i = 0; i < array.length; i++) {
+            if (array[i].equals(s)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the singleton instance of the ImageManager.
+     * @return the ImageManager instance.
+     */
+    public static ImageManager instance() {
+        if (sInstance == null) {
+            sInstance = new ImageManager();
+        }
+        return sInstance;
+    }
+
+    /**
+     * Creates a byte[] for a given bitmap of the desired size. Recycles the input bitmap.
+     */
+    static public byte[] miniThumbData(Bitmap source) {
+        if (source == null)
+            return null;
+
+        Bitmap miniThumbnail = extractMiniThumb(source, MINI_THUMB_TARGET_SIZE,
+                MINI_THUMB_TARGET_SIZE);
+        java.io.ByteArrayOutputStream miniOutStream = new java.io.ByteArrayOutputStream();
+        miniThumbnail.compress(Bitmap.CompressFormat.JPEG, 75, miniOutStream);
+        miniThumbnail.recycle();
+
+        try {
+            miniOutStream.close();
+            byte [] data = miniOutStream.toByteArray();
+            return data;
+        } catch (java.io.IOException ex) {
+            Log.e(TAG, "got exception ex " + ex);
+        }
+        return null;
+    }
+
+    /**
+     * Creates a centered bitmap of the desired size. Recycles the input.
+     * @param source
+     * @return
+     */
+    static public Bitmap extractMiniThumb(Bitmap source, int width, int height) {
+        if (source == null) {
+            return null;
+        }
+
+        float scale;
+        if (source.getWidth() < source.getHeight()) {
+            scale = width / (float)source.getWidth();
+        } else {
+            scale = height / (float)source.getHeight();
+        }
+        Matrix matrix = new Matrix();
+        matrix.setScale(scale, scale);
+        Bitmap miniThumbnail = ImageLoader.transform(matrix, source,
+                width, height, false);
+
+        if (miniThumbnail != source) {
+            source.recycle();
+        }
+        return miniThumbnail;
+    }
+
+    static Bitmap rotate(Bitmap b, int degrees) {
+        if (degrees != 0 && b != null) {
+            Matrix m = new Matrix();
+            m.setRotate(degrees, (float) b.getWidth() / 2, (float) b.getHeight() / 2);
+
+            Bitmap b2 = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, true);
+            // TODO should recycle here but that needs more testing/verification
+//            b.recycle();
+            b = b2;
+        }
+        return b;
+    }
+
+    public static int roundOrientation(int orientationInput) {
+        int orientation = orientationInput;
+        if (orientation == -1)
+            orientation = 0;
+
+        orientation = orientation % 360;
+        int retVal;
+        if (orientation < (0*90) + 45) {
+            retVal = 0;
+        } else if (orientation < (1*90) + 45) {
+            retVal = 90;
+        } else if (orientation < (2*90) + 45) {
+            retVal = 180;
+        } else if (orientation < (3*90) + 45) {
+            retVal = 270;
+        } else {
+            retVal = 0;
+        }
+
+        if (VERBOSE) Log.v(TAG, "map orientation " + orientationInput + " to " + retVal);
+        return retVal;
+    }
+
+
+    /**
+     * @return true if the mimetype is an image mimetype.
+     */
+    public static boolean isImageMimeType(String mimeType) {
+        return mimeType.startsWith("image/");
+    }
+
+    /**
+     * @return true if the mimetype is a video mimetype.
+     */
+    public static boolean isVideoMimeType(String mimeType) {
+        return mimeType.startsWith("video/");
+    }
+
+    /**
+     * @return true if the image is an image.
+     */
+    public static boolean isImage(IImage image) {
+        return isImageMimeType(image.getMimeType());
+    }
+
+    /**
+     * @return true if the image is a video.
+     */
+    public static boolean isVideo(IImage image) {
+        return isVideoMimeType(image.getMimeType());
+    }
+
+    public Uri addImage(
+            final Context ctx,
+            final ContentResolver cr,
+            final String imageName,
+            final String description,
+            final long dateTaken,
+            final Location location,
+            final int orientation,
+            final String directory,
+            final String filename) {
+        ContentValues values = new ContentValues(7);
+        values.put(Images.Media.TITLE, imageName);
+        values.put(Images.Media.DISPLAY_NAME, imageName);
+        values.put(Images.Media.DESCRIPTION, description);
+        values.put(Images.Media.DATE_TAKEN, dateTaken);
+        values.put(Images.Media.MIME_TYPE, "image/jpeg");
+        values.put(Images.Media.ORIENTATION, orientation);
+
+        File parentFile = new File(directory);
+        // Lowercase the path for hashing. This avoids duplicate buckets if the filepath
+        // case is changed externally.
+        // Keep the original case for display.
+        String path = parentFile.toString().toLowerCase();
+        String name = parentFile.getName();
+
+        if (VERBOSE) Log.v(TAG, "addImage id is " + path.hashCode() + "; name " + name + "; path is " + path);
+
+        if (location != null) {
+            if (VERBOSE) {
+                Log.v(TAG, "lat long " + location.getLatitude() + " / " + location.getLongitude());
+            }
+            values.put(Images.Media.LATITUDE, location.getLatitude());
+            values.put(Images.Media.LONGITUDE, location.getLongitude());
+        }
+
+        if (directory != null && filename != null) {
+            String value = directory + "/" + filename;
+            values.put("_data", value);
+        }
+
+        long t3 = System.currentTimeMillis();
+        Uri uri = cr.insert(sStorageURI, values);
+
+        // The line above will create a filename that ends in .jpg
+        // That filename is what will be handed to gmail when a user shares a photo.
+        // Gmail gets the name of the picture attachment from the "DISPLAY_NAME" field.
+        // Extract the filename and jam it into the display name.
+        Cursor c = cr.query(
+                uri,
+                new String [] { ImageColumns._ID, Images.Media.DISPLAY_NAME, "_data" },
+                null,
+                null,
+                null);
+        if (c.moveToFirst()) {
+            String filePath = c.getString(2);
+            if (filePath != null) {
+                int pos = filePath.lastIndexOf("/");
+                if (pos >= 0) {
+                    filePath = filePath.substring(pos + 1);     // pick off the filename
+                    c.updateString(1, filePath);
+                    c.commitUpdates();
+                }
+            }
+        }
+        c.close();
+        return uri;
+    }
+
+    public IAddImage_cancelable storeImage(
+                           final Uri uri,
+                           final Context ctx,
+                           final ContentResolver cr,
+                           final int orientation,
+                           final Bitmap source,
+                           final byte [] jpegData) {
+        class AddImageCancelable extends BaseCancelable implements IAddImage_cancelable {
+            private IGetBoolean_cancelable mSaveImageCancelable;
+
+            public boolean doCancelWork() {
+                if (VERBOSE) {
+                    Log.v(TAG, "calling AddImageCancelable.cancel() " + mSaveImageCancelable);
+                }
+
+                if (mSaveImageCancelable != null) {
+                    mSaveImageCancelable.cancel();
+                }
+                return true;
+            }
+
+            public void get() {
+                if (source == null && jpegData == null) {
+                    throw new IllegalArgumentException("source cannot be null");
+                }
+
+                try {
+                    long t1 = System.currentTimeMillis();
+                    synchronized (this) {
+                        if (mCancel) {
+                            throw new CanceledException();
+                        }
+                    }
+                    long id = ContentUris.parseId(uri);
+
+                    BaseImageList il = new ImageList(ctx, cr, sStorageURI, sThumbURI, SORT_ASCENDING, null);
+                    ImageManager.Image image = new Image(id, 0, cr, il, il.getCount(), 0);
+                    long t5 = System.currentTimeMillis();
+                    Cursor c = cr.query(
+                            uri,
+                            new String [] { ImageColumns._ID, ImageColumns.MINI_THUMB_MAGIC, "_data" },
+                            null,
+                            null,
+                            null);
+                    c.moveToPosition(0);
+
+                    synchronized (this) {
+                        checkCanceled();
+                        mSaveImageCancelable = image.saveImageContents(source, jpegData, orientation, true, c);
+                    }
+
+                    if (mSaveImageCancelable.get()) {
+                        long t6 = System.currentTimeMillis();
+                        if (VERBOSE) Log.v(TAG, "saveImageContents took " + (t6-t5));
+                        if (VERBOSE) Log.v(TAG, "updating new picture with id " + id);
+                        c.updateLong(1, id);
+                        c.commitUpdates();
+                        c.close();
+                        long t7 = System.currentTimeMillis();
+                        if (VERBOSE) Log.v(TAG, "commit updates to save mini thumb took " + (t7-t6));
+                    }
+                    else {
+                        c.close();
+                        throw new CanceledException();
+                    }
+                } catch (CanceledException ex) {
+                    if (VERBOSE) {
+                        Log.v(TAG, "caught CanceledException");
+                    }
+                    if (uri != null) {
+                        if (VERBOSE) {
+                            Log.v(TAG, "canceled... cleaning up this uri: " + uri);
+                        }
+                        cr.delete(uri, null, null);
+                    }
+                    acknowledgeCancel();
+                }
+            }
+        }
+        return new AddImageCancelable();
+    }
+
+    static public IImageList makeImageList(Uri uri, Context ctx, int sort) {
+        ContentResolver cr = ctx.getContentResolver();
+        String uriString = (uri != null) ? uri.toString() : "";
+        // TODO we need to figure out whether we're viewing
+        // DRM images in a better way.  Is there a constant
+        // for content://drm somewhere??
+        IImageList imageList;
+
+        if (uriString.startsWith("content://drm")) {
+            imageList = ImageManager.instance().allImages(
+                    ctx,
+                    cr,
+                    ImageManager.DataLocation.ALL,
+                    ImageManager.INCLUDE_DRM_IMAGES,
+                    sort);
+        } else if (!uriString.startsWith(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString())
+            && !uriString.startsWith(MediaStore.Images.Media.INTERNAL_CONTENT_URI.toString())) {
+            imageList = ImageManager.instance().new SingleImageList(cr, uri);
+        } else {
+            String bucketId = uri.getQueryParameter("bucketId");
+            if (VERBOSE) Log.v(TAG, "bucketId is " + bucketId);
+            imageList = ImageManager.instance().allImages(
+                ctx,
+                cr,
+                ImageManager.DataLocation.ALL,
+                ImageManager.INCLUDE_IMAGES,
+                sort,
+                bucketId);
+        }
+        return imageList;
+    }
+
+    public IImageList emptyImageList() {
+        return
+        new IImageList() {
+            public void checkThumbnails(ImageManager.IImageList.ThumbCheckCallback cb,
+                    int totalThumbnails) {
+            }
+
+            public void commitChanges() {
+            }
+
+            public void deactivate() {
+            }
+
+            public HashMap<String, String> getBucketIds() {
+                return new HashMap<String,String>();
+            }
+
+            public int getCount() {
+                return 0;
+            }
+
+            public boolean isEmpty() {
+                return true;
+            }
+
+            public IImage getImageAt(int i) {
+                return null;
+            }
+
+            public IImage getImageForUri(Uri uri) {
+                return null;
+            }
+
+            public boolean removeImage(IImage image) {
+                return false;
+            }
+
+            public void removeImageAt(int i) {
+            }
+
+            public void removeOnChangeListener(ImageManager.IImageList.OnChange changeCallback) {
+            }
+
+            public void setOnChangeListener(ImageManager.IImageList.OnChange changeCallback,
+                    Handler h) {
+            }
+
+        };
+    }
+
+    public IImageList allImages(Context ctx, ContentResolver cr, DataLocation location, int inclusion, int sort) {
+        return allImages(ctx, cr, location, inclusion, sort, null, null);
+    }
+
+    public IImageList allImages(Context ctx, ContentResolver cr, DataLocation location, int inclusion, int sort, String bucketId) {
+        return allImages(ctx, cr, location, inclusion, sort, bucketId, null);
+    }
+
+    public IImageList allImages(Context ctx, ContentResolver cr, DataLocation location, int inclusion, int sort, String bucketId, Uri specificImageUri) {
+        if (VERBOSE) {
+            Log.v(TAG, "allImages " + location + " " + ((inclusion&INCLUDE_IMAGES)!=0) + " + v=" + ((inclusion&INCLUDE_VIDEOS)!=0));
+        }
+
+        if (cr == null) {
+            return null;
+        } else {
+            // false ==> don't require write access
+            boolean haveSdCard = hasStorage(false);
+
+            if (true) {
+                // use this code to merge videos and stills into the same list
+                ArrayList<IImageList> l = new ArrayList<IImageList>();
+
+                if (VERBOSE) {
+                    Log.v(TAG, "initializing ... haveSdCard == " + haveSdCard + "; inclusion is " + String.format("%x", inclusion));
+                }
+                if (specificImageUri != null) {
+                    try {
+                        if (specificImageUri.getScheme().equalsIgnoreCase("content"))
+                            l.add(new ImageList(ctx, cr, specificImageUri, sThumbURI, sort, bucketId));
+                        else
+                            l.add(new SingleImageList(cr, specificImageUri));
+                    } catch (UnsupportedOperationException ex) {
+                    }
+                } else {
+                    if (haveSdCard && location != DataLocation.INTERNAL) {
+                        if ((inclusion & INCLUDE_IMAGES) != 0) {
+                            try {
+                                l.add(new ImageList(ctx, cr, sStorageURI, sThumbURI, sort, bucketId));
+                            } catch (UnsupportedOperationException ex) {
+                            }
+                        }
+                        if ((inclusion & INCLUDE_VIDEOS) != 0) {
+                            try {
+                                l.add(new VideoList(ctx, cr, sVideoStorageURI, sVideoThumbURI, sort, bucketId));
+                            } catch (UnsupportedOperationException ex) {
+                            }
+                        }
+                    }
+                    if (location == DataLocation.INTERNAL || location == DataLocation.ALL) {
+                        if ((inclusion & INCLUDE_IMAGES) != 0) {
+                            try {
+                                l.add(new ImageList(ctx, cr, Images.Media.INTERNAL_CONTENT_URI,
+                                        Images.Thumbnails.INTERNAL_CONTENT_URI, sort, bucketId));
+                            } catch (UnsupportedOperationException ex) {
+                            }
+                        }
+                        if ((inclusion & INCLUDE_DRM_IMAGES) != 0) {
+                            try {
+                                l.add(new DrmImageList(ctx, cr, DrmStore.Images.CONTENT_URI, sort, bucketId));
+                            } catch (UnsupportedOperationException ex) {
+                            }
+                        }
+                    }
+                }
+
+                IImageList [] imageList = l.toArray(new IImageList[l.size()]);
+                return new ImageListUber(imageList, sort);
+            } else {
+                if (haveSdCard && location != DataLocation.INTERNAL) {
+                    return new ImageList(ctx, cr, sStorageURI, sThumbURI, sort, bucketId);
+                } else  {
+                    return new ImageList(ctx, cr, Images.Media.INTERNAL_CONTENT_URI,
+                            Images.Thumbnails.INTERNAL_CONTENT_URI, sort, bucketId);
+                }
+            }
+        }
+    }
+
+    // Create a temporary file to see whether a volume is really writeable. It's important not to
+    // put it in the root directory which may have a limit on the number of files.
+    static private boolean checkFsWritable() {
+        String directoryName = Environment.getExternalStorageDirectory().toString() + "/DCIM";
+        File directory = new File(directoryName);
+        if (!directory.isDirectory()) {
+            if (!directory.mkdirs()) {
+                return false;
+            }
+        }
+        File f = new File(directoryName, ".probe");
+        try {
+            // Remove stale file if any
+            if (f.exists()) {
+                f.delete();
+            }
+            if (!f.createNewFile())
+                return false;
+            f.delete();
+            return true;
+        } catch (IOException ex) {
+            return false;
+        }
+    }
+
+    static public boolean hasStorage() {
+        return hasStorage(true);
+    }
+
+    static public boolean hasStorage(boolean requireWriteAccess) {
+        String state = Environment.getExternalStorageState();
+        if (VERBOSE) Log.v(TAG, "state is " + state);
+        if (Environment.MEDIA_MOUNTED.equals(state)) {
+            if (requireWriteAccess) {
+                boolean writable = checkFsWritable();
+                if (VERBOSE) Log.v(TAG, "writable is " + writable);
+                return writable;
+            } else {
+                return true;
+            }
+        } else if (!requireWriteAccess && Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
+            return true;
+        }
+        return false;
+    }
+
+    public static Cursor query(Context context, Uri uri, String[] projection,
+            String selection, String[] selectionArgs, String sortOrder) {
+        try {
+            ContentResolver resolver = context.getContentResolver();
+            if (resolver == null) {
+                return null;
+            }
+            return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
+         } catch (UnsupportedOperationException ex) {
+            return null;
+        }
+
+    }
+
+    public static boolean isMediaScannerScanning(Context context) {
+        boolean result = false;
+        Cursor cursor = query(context, MediaStore.getMediaScannerUri(),
+                new String [] { MediaStore.MEDIA_SCANNER_VOLUME }, null, null, null);
+        if (cursor != null) {
+            if (cursor.getCount() == 1) {
+                cursor.moveToFirst();
+                result = "external".equals(cursor.getString(0));
+            }
+            cursor.close();
+        }
+
+        if (VERBOSE)
+            Log.v(TAG, ">>>>>>>>>>>>>>>>>>>>>>>>> isMediaScannerScanning returning " + result);
+        return result;
+    }
+
+    /**
+     * Create a video thumbnail for a video. May return null if the video is corrupt.
+     * @param filePath
+     * @return
+     */
+    public static Bitmap createVideoThumbnail(String filePath) {
+        Bitmap bitmap = null;
+        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+        try {
+            retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY);
+            retriever.setDataSource(filePath);
+            bitmap = retriever.captureFrame();
+        } catch(IllegalArgumentException ex) {
+            // Assume this is a corrupt video file
+        } catch (RuntimeException ex) {
+            // Assume this is a corrupt video file.
+        } finally {
+            try {
+                retriever.release();
+            } catch (RuntimeException ex) {
+                // Ignore failures while cleaning up.
+            }
+        }
+        return bitmap;
+    }
+
+    public static String getLastThumbPath() {
+        return Environment.getExternalStorageDirectory().toString() +
+               "/DCIM/.thumbnails/camera_last_thumb";
+    }
+}
diff --git a/src/com/android/camera/ImageViewTouchBase.java b/src/com/android/camera/ImageViewTouchBase.java
new file mode 100644
index 0000000..1774e46
--- /dev/null
+++ b/src/com/android/camera/ImageViewTouchBase.java
@@ -0,0 +1,559 @@
+package com.android.camera;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Config;
+import android.util.Log;
+import android.view.animation.Animation;
+import android.view.animation.TranslateAnimation;
+import android.view.KeyEvent;
+import android.widget.ImageView;
+
+abstract public class ImageViewTouchBase extends ImageView {
+    private static final String TAG = "ImageViewTouchBase";
+    
+    // if we're animating these images, it may be faster to cache the image
+    // at its target size first. to do this set this variable to true.
+    // currently we're not animating images, so we don't need to do this
+    // extra work.
+    private final boolean USE_PERFECT_FIT_OPTIMIZATION = false;
+    
+    // This is the base transformation which is used to show the image
+    // initially.  The current computation for this shows the image in
+    // it's entirety, letterboxing as needed.  One could chose to
+    // show the image as cropped instead.  
+    //
+    // This matrix is recomputed when we go from the thumbnail image to
+    // the full size image.
+    protected Matrix mBaseMatrix = new Matrix();
+
+    // This is the supplementary transformation which reflects what 
+    // the user has done in terms of zooming and panning.
+    //
+    // This matrix remains the same when we go from the thumbnail image
+    // to the full size image.
+    protected Matrix mSuppMatrix = new Matrix();
+
+    // This is the final matrix which is computed as the concatentation
+    // of the base matrix and the supplementary matrix.
+    private Matrix mDisplayMatrix = new Matrix();
+
+    // Temporary buffer used for getting the values out of a matrix.
+    private float[] mMatrixValues = new float[9];
+
+    // The current bitmap being displayed.
+    protected Bitmap mBitmapDisplayed;
+
+    // The thumbnail bitmap.
+    protected Bitmap mThumbBitmap;
+    
+    // The full size bitmap which should be used once we start zooming.
+    private Bitmap mFullBitmap;
+
+    // The bitmap which is exactly sized to what we need.  The decoded bitmap is
+    // drawn into the mPerfectFitBitmap so that animation is faster.
+    protected Bitmap mPerfectFitBitmap;
+
+    // True if the image is the thumbnail.
+    protected boolean mBitmapIsThumbnail;
+
+    // True if the user is zooming -- use the full size image
+    protected boolean mIsZooming;
+
+    // Paint to use to clear the "mPerfectFitBitmap"
+    protected Paint mPaint = new Paint();
+    
+    static boolean sNewZoomControl = false;
+    
+    int mThisWidth = -1, mThisHeight = -1;
+    
+    float mMaxZoom;
+    
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        mThisWidth = right - left;
+        mThisHeight = bottom - top;
+        Runnable r = mOnLayoutRunnable;
+        if (r != null) {
+            mOnLayoutRunnable = null;
+            r.run();
+        }
+        if (mBitmapDisplayed != null) {
+            setBaseMatrix(mBitmapDisplayed, mBaseMatrix);
+            setImageMatrix(getImageViewMatrix());
+        }
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK && getScale() > 1.0f) {
+            // If we're zoomed in, pressing Back jumps out to show the entire image, otherwise Back
+            // returns the user to the gallery.
+            zoomTo(1.0f);
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    protected Handler mHandler = new Handler();
+    
+    protected int mLastXTouchPos;
+    protected int mLastYTouchPos;
+    
+    protected boolean doesScrolling() {
+        return true;
+    }
+
+    // Translate a given point through a given matrix.
+    static private void translatePoint(Matrix matrix, float [] xy) {
+        matrix.mapPoints(xy);
+    }
+
+    // Return the mapped x coordinate through the matrix.
+    static int mapXPoint(Matrix matrix, int point) {
+        // Matrix's mapPoints takes an array of x/y coordinates.
+        // That's why we have to allocte an array of length two
+        // even though we don't use the y coordinate.
+        float [] xy = new float[2];
+        xy[0] = point;
+        xy[1] = 0F;
+        matrix.mapPoints(xy);
+        return (int) xy[0];
+    }
+    
+    @Override
+    public void setImageBitmap(Bitmap bitmap) {
+        throw new NullPointerException();
+    }
+
+    public void setImageBitmap(Bitmap bitmap, boolean isThumbnail) {
+        super.setImageBitmap(bitmap);
+        Drawable d = getDrawable();
+        if (d != null) 
+            d.setDither(true);
+        mBitmapDisplayed = bitmap;
+        mBitmapIsThumbnail = isThumbnail;
+    }
+    
+    protected boolean usePerfectFitBitmap() {
+        return USE_PERFECT_FIT_OPTIMIZATION && !mIsZooming;
+    }
+    
+    public void recycleBitmaps() {
+        if (mFullBitmap != null) {
+            if (Config.LOGV)
+                Log.v(TAG, "recycling mFullBitmap " + mFullBitmap + "; this == " + this.hashCode());
+            mFullBitmap.recycle();
+            mFullBitmap = null;
+        }
+        if (mThumbBitmap != null) {
+            if (Config.LOGV)
+                Log.v(TAG, "recycling mThumbBitmap" + mThumbBitmap + "; this == " + this.hashCode());
+            mThumbBitmap.recycle();
+            mThumbBitmap = null;
+        }
+
+        // mBitmapDisplayed is either mPerfectFitBitmap or mFullBitmap (in the case of zooming)
+        setImageBitmap(null, true);
+    }
+    
+    public void clear() {
+        mBitmapDisplayed = null;
+        recycleBitmaps();
+    }
+    
+    private Runnable mOnLayoutRunnable = null;
+    
+    public void setImageBitmapResetBase(final Bitmap bitmap, final boolean resetSupp, final boolean isThumb) {
+        if ((bitmap != null) && (bitmap == mPerfectFitBitmap)) {
+            // TODO: this should be removed in production
+            throw new IllegalArgumentException("bitmap must not be mPerfectFitBitmap");
+        }
+
+        final int viewWidth = getWidth();
+        final int viewHeight = getHeight();
+
+        if (viewWidth <= 0)  {
+            mOnLayoutRunnable = new Runnable() {
+                public void run() {
+                    setImageBitmapResetBase(bitmap, resetSupp, isThumb);
+                }
+            };
+            return;
+        }
+        
+        if (isThumb && mThumbBitmap != bitmap) {
+            if (mThumbBitmap != null) {
+                mThumbBitmap.recycle();
+            }
+            mThumbBitmap = bitmap;
+        } else if (!isThumb && mFullBitmap != bitmap) {
+            if (mFullBitmap != null) {
+                mFullBitmap.recycle();
+            }
+            mFullBitmap = bitmap;
+        }
+        mBitmapIsThumbnail = isThumb;
+        
+        if (bitmap != null) {
+            if (!usePerfectFitBitmap()) {
+                setScaleType(ImageView.ScaleType.MATRIX);
+                setBaseMatrix(bitmap, mBaseMatrix);
+                setImageBitmap(bitmap, isThumb);
+            } else {
+                Matrix matrix = new Matrix();
+                setBaseMatrix(bitmap, matrix);
+                if ((mPerfectFitBitmap == null) || 
+                        mPerfectFitBitmap.getWidth() != mThisWidth || 
+                        mPerfectFitBitmap.getHeight() != mThisHeight) {
+                    if (mPerfectFitBitmap != null) {
+                        if (Config.LOGV)
+                            Log.v(TAG, "recycling mPerfectFitBitmap " + mPerfectFitBitmap.hashCode());
+                        mPerfectFitBitmap.recycle();
+                    }
+                    mPerfectFitBitmap = Bitmap.createBitmap(mThisWidth, mThisHeight, Bitmap.Config.RGB_565);
+                }
+                Canvas canvas = new Canvas(mPerfectFitBitmap);
+                // clear the bitmap which may be bigger than the image and
+                // contain the the previous image.
+                canvas.drawColor(0xFF000000);
+
+                final int bw = bitmap.getWidth();
+                final int bh = bitmap.getHeight();
+                final float widthScale  = Math.min(viewWidth / (float)bw, 1.0f);
+                final float heightScale = Math.min(viewHeight/ (float)bh, 1.0f);
+                int translateX, translateY;
+                if (widthScale > heightScale) {
+                    translateX = (int)((viewWidth -(float)bw*heightScale)*0.5f);
+                    translateY = (int)((viewHeight-(float)bh*heightScale)*0.5f);
+                } else {
+                    translateX = (int)((viewWidth -(float)bw*widthScale)*0.5f);
+                    translateY = (int)((viewHeight-(float)bh*widthScale)*0.5f);
+                }
+
+                android.graphics.Rect src = new android.graphics.Rect(0, 0, bw, bh);
+                android.graphics.Rect dst = new android.graphics.Rect(
+                        translateX, translateY,  
+                        mThisWidth - translateX, mThisHeight - translateY);
+                canvas.drawBitmap(bitmap, src, dst, mPaint);
+                
+                setImageBitmap(mPerfectFitBitmap, isThumb);
+                setScaleType(ImageView.ScaleType.MATRIX);
+                setImageMatrix(null);
+            }
+        } else {
+            mBaseMatrix.reset();
+            setImageBitmap(null, isThumb);
+        }
+
+        if (resetSupp)
+            mSuppMatrix.reset();
+        setImageMatrix(getImageViewMatrix());
+        mMaxZoom = maxZoom();
+    }
+
+    // Center as much as possible in one or both axis.  Centering is
+    // defined as follows:  if the image is scaled down below the 
+    // view's dimensions then center it (literally).  If the image
+    // is scaled larger than the view and is translated out of view
+    // then translate it back into view (i.e. eliminate black bars).
+    protected void center(boolean vertical, boolean horizontal, boolean animate) {
+        if (mBitmapDisplayed == null)
+            return;
+
+        Matrix m = getImageViewMatrix();
+
+        float [] topLeft  = new float[] { 0, 0 };
+        float [] botRight = new float[] { mBitmapDisplayed.getWidth(), mBitmapDisplayed.getHeight() };
+
+        translatePoint(m, topLeft);
+        translatePoint(m, botRight);
+
+        float height = botRight[1] - topLeft[1];
+        float width  = botRight[0] - topLeft[0];
+
+        float deltaX = 0, deltaY = 0;
+
+        if (vertical) {
+            int viewHeight = getHeight();
+            if (height < viewHeight) {
+                deltaY = (viewHeight - height)/2 - topLeft[1];
+            } else if (topLeft[1] > 0) {
+                deltaY = -topLeft[1];
+            } else if (botRight[1] < viewHeight) {
+                deltaY = getHeight() - botRight[1];
+            }
+        }
+
+        if (horizontal) {
+            int viewWidth = getWidth();
+            if (width < viewWidth) {
+                deltaX = (viewWidth - width)/2 - topLeft[0];
+            } else if (topLeft[0] > 0) {
+                deltaX = -topLeft[0];
+            } else if (botRight[0] < viewWidth) {
+                deltaX = viewWidth - botRight[0];
+            }
+        }
+
+        postTranslate(deltaX, deltaY);
+        if (animate) {
+            Animation a = new TranslateAnimation(-deltaX, 0, -deltaY, 0);
+            a.setStartTime(SystemClock.elapsedRealtime());
+            a.setDuration(250);
+            setAnimation(a);
+        }
+        setImageMatrix(getImageViewMatrix());
+    }
+
+    public void copyFrom(ImageViewTouchBase other) {
+        mSuppMatrix.set(other.mSuppMatrix);
+        mBaseMatrix.set(other.mBaseMatrix);
+        
+        if (mThumbBitmap != null)
+            mThumbBitmap.recycle();
+        
+        if (mFullBitmap != null) 
+            mFullBitmap.recycle();
+        
+        // copy the data
+        mThumbBitmap       = other.mThumbBitmap;
+        mFullBitmap        = null;
+        
+        if (other.mFullBitmap != null)
+            other.mFullBitmap.recycle();
+        
+        // transfer "ownership"
+        other.mThumbBitmap = null;
+        other.mFullBitmap  = null;
+        other.mBitmapIsThumbnail = true;
+
+        setImageMatrix(other.getImageMatrix());
+        setScaleType(other.getScaleType());
+        
+        setImageBitmapResetBase(mThumbBitmap, true, true);
+    }
+    
+    @Override 
+    public void setImageDrawable(android.graphics.drawable.Drawable d) {
+        super.setImageDrawable(d);
+    }
+
+    public ImageViewTouchBase(Context context) {
+        super(context);
+        init();
+    }
+
+    public ImageViewTouchBase(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    private void init() {
+        setScaleType(ImageView.ScaleType.MATRIX);
+        mPaint.setDither(true);
+        mPaint.setFilterBitmap(true);
+    }
+
+    protected float getValue(Matrix matrix, int whichValue) {
+        matrix.getValues(mMatrixValues);
+        return mMatrixValues[whichValue];
+    }
+    
+    // Get the scale factor out of the matrix.
+    protected float getScale(Matrix matrix) {
+        return getValue(matrix, Matrix.MSCALE_X);
+    }
+
+    protected float getScale() {
+        return getScale(mSuppMatrix);
+    }
+    
+    protected float getTranslateX() {
+        return getValue(mSuppMatrix, Matrix.MTRANS_X);
+    }
+
+    protected float getTranslateY() {
+        return getValue(mSuppMatrix, Matrix.MTRANS_Y);
+    }
+
+    // Setup the base matrix so that the image is centered and scaled properly.
+    private void setBaseMatrix(Bitmap bitmap, Matrix matrix) {
+        float viewWidth = getWidth();
+        float viewHeight = getHeight();
+
+        matrix.reset();
+        float widthScale = Math.min(viewWidth / (float)bitmap.getWidth(), 1.0f);
+        float heightScale = Math.min(viewHeight / (float)bitmap.getHeight(), 1.0f);
+        float scale;
+        if (widthScale > heightScale) {
+            scale = heightScale;
+        } else {
+            scale = widthScale;
+        }
+        matrix.setScale(scale, scale);
+        matrix.postTranslate(
+                (viewWidth  - ((float)bitmap.getWidth()  * scale))/2F, 
+                (viewHeight - ((float)bitmap.getHeight() * scale))/2F);
+    }
+    
+    // Combine the base matrix and the supp matrix to make the final matrix.
+    protected Matrix getImageViewMatrix() {
+        mDisplayMatrix.set(mBaseMatrix);
+        mDisplayMatrix.postConcat(mSuppMatrix);
+        return mDisplayMatrix;
+    }
+
+    private void onZoom() {
+        mIsZooming = true;
+        if (mFullBitmap != null && mFullBitmap != mBitmapDisplayed) {
+            setImageBitmapResetBase(mFullBitmap, false, mBitmapIsThumbnail);
+        }
+    }
+    
+    private String describe(Bitmap b) {
+        StringBuilder sb = new StringBuilder();
+        if (b == null) {
+            sb.append("NULL");
+        } else if (b.isRecycled()) {
+            sb.append(String.format("%08x: RECYCLED", b.hashCode()));
+        } else {
+            sb.append(String.format("%08x: LIVE", b.hashCode()));
+            sb.append(String.format("%d x %d (size == %d)", b.getWidth(), b.getHeight(), b.getWidth()*b.getHeight()*2));
+        }
+        return sb.toString();
+    }
+    
+    public void dump() {
+        if (Config.LOGV) {
+            Log.v(TAG, "dump ImageViewTouchBase " + this);
+            Log.v(TAG, "... mBitmapDisplayed  = " + describe(mBitmapDisplayed));
+            Log.v(TAG, "... mThumbBitmap      = " + describe(mThumbBitmap));
+            Log.v(TAG, "... mFullBitmap       = " + describe(mFullBitmap));
+            Log.v(TAG, "... mPerfectFitBitmap = " + describe(mPerfectFitBitmap));
+            Log.v(TAG, "... mIsThumb          = " + mBitmapIsThumbnail);
+        }
+    }
+
+    static final float sPanRate = 7;
+    static final float sScaleRate = 1.25F;
+
+    // Sets the maximum zoom, which is a scale relative to the base matrix. It is calculated to show
+    // the image at 400% zoom regardless of screen or image orientation. If in the future we decode
+    // the full 3 megapixel image, rather than the current 1024x768, this should be changed down to
+    // 200%.
+    protected float maxZoom() {
+        if (mBitmapDisplayed == null)
+            return 1F;
+        
+        float fw = (float) mBitmapDisplayed.getWidth()  / (float)mThisWidth;
+        float fh = (float) mBitmapDisplayed.getHeight() / (float)mThisHeight;
+        float max = Math.max(fw, fh) * 4;
+//        Log.v(TAG, "Bitmap " + mBitmapDisplayed.getWidth() + "x" + mBitmapDisplayed.getHeight() +
+//                " view " + mThisWidth + "x" + mThisHeight + " max zoom " + max);
+        return max;
+    }
+
+    protected void zoomTo(float scale, float centerX, float centerY) {
+        if (scale > mMaxZoom) {
+            scale = mMaxZoom;
+        }
+        onZoom();
+        
+        float oldScale = getScale();
+        float deltaScale = scale / oldScale;
+
+        mSuppMatrix.postScale(deltaScale, deltaScale, centerX, centerY);
+        setImageMatrix(getImageViewMatrix());
+        center(true, true, false);
+    }
+    
+    protected void zoomTo(final float scale, final float centerX, final float centerY, final float durationMs) {
+        final float incrementPerMs = (scale - getScale()) / durationMs;
+        final float oldScale = getScale();
+        final long startTime = System.currentTimeMillis();
+        
+        mHandler.post(new Runnable() {
+            public void run() {
+                long now = System.currentTimeMillis();
+                float currentMs = Math.min(durationMs, (float)(now - startTime));
+                float target = oldScale + (incrementPerMs * currentMs);
+                zoomTo(target, centerX, centerY);
+                
+                if (currentMs < durationMs) {
+                    mHandler.post(this);
+                }
+            }
+        }); 
+    }
+
+    protected void zoomTo(float scale) {
+        float width = getWidth();
+        float height = getHeight();
+        
+        zoomTo(scale, width/2F, height/2F);
+    }
+    
+    protected void zoomIn() {
+        zoomIn(sScaleRate);
+    }
+    
+    protected void zoomOut() {
+        zoomOut(sScaleRate);
+    }
+
+    protected void zoomIn(float rate) {
+        if (getScale() >= mMaxZoom) {
+            return;     // Don't let the user zoom into the molecular level.
+        }
+        if (mBitmapDisplayed == null) {
+            return;
+        }
+        float width = getWidth();
+        float height = getHeight();
+
+        mSuppMatrix.postScale(rate, rate, width/2F, height/2F);
+        setImageMatrix(getImageViewMatrix());
+
+        onZoom();
+    }
+
+    protected void zoomOut(float rate) {
+        if (mBitmapDisplayed == null) {
+            return;
+        }
+        
+        float width = getWidth();
+        float height = getHeight();
+
+        Matrix tmp = new Matrix(mSuppMatrix);
+        tmp.postScale(1F/sScaleRate, 1F/sScaleRate, width/2F, height/2F);
+        if (getScale(tmp) < 1F) {
+            mSuppMatrix.setScale(1F, 1F, width/2F, height/2F);
+        } else {
+            mSuppMatrix.postScale(1F/rate, 1F/rate, width/2F, height/2F);
+        }
+        setImageMatrix(getImageViewMatrix());
+        center(true, true, false);
+
+        onZoom();
+    }
+    
+    protected void postTranslate(float dx, float dy) {
+        mSuppMatrix.postTranslate(dx, dy);
+    }
+
+    protected void panBy(float dx, float dy) {
+        postTranslate(dx, dy);
+        setImageMatrix(getImageViewMatrix());
+    }
+}
+
diff --git a/src/com/android/camera/MenuHelper.java b/src/com/android/camera/MenuHelper.java
new file mode 100644
index 0000000..7148cd6
--- /dev/null
+++ b/src/com/android/camera/MenuHelper.java
@@ -0,0 +1,720 @@
+/*
+ * Copyright (C) 2008 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.camera;
+
+import java.io.Closeable;
+import java.util.ArrayList;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.media.MediaMetadataRetriever;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.StatFs;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Images;
+import android.util.Config;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.View;
+import android.view.MenuItem.OnMenuItemClickListener;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.camera.ImageManager.IImage;
+
+public class MenuHelper {
+    static private final String TAG = "MenuHelper";
+
+    static public final int GENERIC_ITEM      = 1;
+    static public final int IMAGE_SAVING_ITEM = 2;
+    static public final int VIDEO_SAVING_ITEM = 3;
+    static public final int IMAGE_MODE_ITEM   = 4;
+    static public final int VIDEO_MODE_ITEM   = 5;
+    static public final int MENU_ITEM_MAX     = 5;
+
+    static public final int INCLUDE_ALL           = 0xFFFFFFFF;
+    static public final int INCLUDE_VIEWPLAY_MENU = (1 << 0);
+    static public final int INCLUDE_SHARE_MENU    = (1 << 1);
+    static public final int INCLUDE_SET_MENU      = (1 << 2);
+    static public final int INCLUDE_CROP_MENU     = (1 << 3);
+    static public final int INCLUDE_DELETE_MENU   = (1 << 4);
+    static public final int INCLUDE_ROTATE_MENU   = (1 << 5);
+    static public final int INCLUDE_DETAILS_MENU   = (1 << 5);
+
+    static public final int MENU_SWITCH_CAMERA_MODE = 0;
+    static public final int MENU_CAPTURE_PICTURE = 1;
+    static public final int MENU_CAPTURE_VIDEO = 2;
+    static public final int MENU_IMAGE_SHARE = 10;
+    static public final int MENU_IMAGE_SET = 14;
+    static public final int MENU_IMAGE_SET_WALLPAPER = 15;
+    static public final int MENU_IMAGE_SET_CONTACT = 16;
+    static public final int MENU_IMAGE_SET_MYFAVE = 17;
+    static public final int MENU_IMAGE_CROP = 18;
+    static public final int MENU_IMAGE_ROTATE = 19;
+    static public final int MENU_IMAGE_ROTATE_LEFT = 20;
+    static public final int MENU_IMAGE_ROTATE_RIGHT = 21;
+    static public final int MENU_IMAGE_TOSS = 22;
+    static public final int MENU_VIDEO_PLAY = 23;
+    static public final int MENU_VIDEO_SHARE = 24;
+    static public final int MENU_VIDEO_TOSS = 27;
+
+    public static final int NO_STORAGE_ERROR = -1;
+    public static final int CANNOT_STAT_ERROR = -2;
+
+    /** Activity result code used to report crop results.
+     */
+    public static final int RESULT_COMMON_MENU_CROP = 490;
+
+    public interface MenuItemsResult {
+        public void gettingReadyToOpen(Menu menu, ImageManager.IImage image);
+        public void aboutToCall(MenuItem item, ImageManager.IImage image);
+    }
+
+    public interface MenuInvoker {
+        public void run(MenuCallback r);
+    }
+
+    public interface MenuCallback {
+        public void run(Uri uri, ImageManager.IImage image);
+    }
+
+    private static void closeSilently(Closeable target) {
+        try {
+            if (target != null) target.close();
+        } catch (Throwable t) {
+            // ignore all exceptions, that's what silently means
+        }
+    }
+
+    public static long getImageFileSize(ImageManager.IImage image) {
+        java.io.InputStream data = image.fullSizeImageData();
+        try {
+            return data.available();
+        } catch (java.io.IOException ex) {
+            return -1;
+        } finally {
+            closeSilently(data);
+        }
+    }
+
+    static MenuItemsResult addImageMenuItems(
+            Menu menu,
+            int inclusions,
+            final boolean isImage,
+            final Activity activity,
+            final Handler handler,
+            final Runnable onDelete,
+            final MenuInvoker onInvoke) {
+        final ArrayList<MenuItem> requiresWriteAccessItems = new ArrayList<MenuItem>();
+        final ArrayList<MenuItem> requiresNoDrmAccessItems = new ArrayList<MenuItem>();
+
+        if (isImage && ((inclusions & INCLUDE_ROTATE_MENU) != 0)) {
+            SubMenu rotateSubmenu = menu.addSubMenu(IMAGE_SAVING_ITEM, MENU_IMAGE_ROTATE,
+                    40, R.string.rotate).setIcon(android.R.drawable.ic_menu_rotate);
+            // Don't show the rotate submenu if the item at hand is read only
+            // since the items within the submenu won't be shown anyway.  This is
+            // really a framework bug in that it shouldn't show the submenu if
+            // the submenu has no visible items.
+            requiresWriteAccessItems.add(rotateSubmenu.getItem());
+            if (rotateSubmenu != null) {
+                requiresWriteAccessItems.add(rotateSubmenu.add(0, MENU_IMAGE_ROTATE_LEFT, 50, R.string.rotate_left).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+                    public boolean onMenuItemClick(MenuItem item) {
+                        onInvoke.run(new MenuCallback() {
+                            public void run(Uri u, ImageManager.IImage image) {
+                                if (image == null || image.isReadonly())
+                                    return;
+                                image.rotateImageBy(-90);
+                            }
+                        });
+                        return true;
+                    }
+                }).setAlphabeticShortcut('l'));
+                requiresWriteAccessItems.add(rotateSubmenu.add(0, MENU_IMAGE_ROTATE_RIGHT, 60, R.string.rotate_right).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+                    public boolean onMenuItemClick(MenuItem item) {
+                        onInvoke.run(new MenuCallback() {
+                            public void run(Uri u, ImageManager.IImage image) {
+                                if (image == null || image.isReadonly())
+                                    return;
+
+                                image.rotateImageBy(90);
+                            }
+                        });
+                        return true;
+                    }
+                }).setAlphabeticShortcut('r'));
+            }
+        }
+
+        if (isImage && ((inclusions & INCLUDE_CROP_MENU) != 0)) {
+            MenuItem autoCrop = menu.add(IMAGE_SAVING_ITEM, MENU_IMAGE_CROP, 73,
+                    R.string.camera_crop).setOnMenuItemClickListener(
+                            new MenuItem.OnMenuItemClickListener() {
+                public boolean onMenuItemClick(MenuItem item) {
+                    onInvoke.run(new MenuCallback() {
+                        public void run(Uri u, ImageManager.IImage image) {
+                            if (u == null)
+                                return;
+
+                            Intent cropIntent = new Intent();
+                            cropIntent.setClass(activity, CropImage.class);
+                            cropIntent.setData(u);
+                            activity.startActivityForResult(cropIntent, RESULT_COMMON_MENU_CROP);
+                        }
+                    });
+                    return true;
+                }
+            });
+            autoCrop.setIcon(android.R.drawable.ic_menu_crop);
+            requiresWriteAccessItems.add(autoCrop);
+        }
+
+        if (isImage && ((inclusions & INCLUDE_SET_MENU) != 0)) {
+            MenuItem setMenu = menu.add(IMAGE_SAVING_ITEM, MENU_IMAGE_SET, 75, R.string.camera_set);
+            setMenu.setIcon(android.R.drawable.ic_menu_set_as);
+
+            setMenu.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+                public boolean onMenuItemClick(MenuItem item) {
+                    onInvoke.run(new MenuCallback() {
+                        public void run(Uri u, ImageManager.IImage image) {
+                            if (u == null || image == null)
+                                return;
+
+                            if (Config.LOGV)
+                                Log.v(TAG, "in callback u is " + u + "; mime type is " + image.getMimeType());
+                            Intent intent = new Intent(Intent.ACTION_ATTACH_DATA);
+                            intent.setDataAndType(u, image.getMimeType());
+                            intent.putExtra("mimeType", image.getMimeType());
+                            activity.startActivity(Intent.createChooser(intent, activity.getText(R.string.setImage)));
+                        }
+                    });
+                    return true;
+                }
+            });
+        }
+
+        if ((inclusions & INCLUDE_SHARE_MENU) != 0) {
+            if (Config.LOGV)
+                Log.v(TAG, ">>>>> add share");
+            MenuItem item1 = menu.add(IMAGE_SAVING_ITEM, MENU_IMAGE_SHARE, 10,
+                    R.string.camera_share).setOnMenuItemClickListener(
+                    new MenuItem.OnMenuItemClickListener() {
+                public boolean onMenuItemClick(MenuItem item) {
+                    onInvoke.run(new MenuCallback() {
+                        public void run(Uri u, ImageManager.IImage image) {
+                            if (image == null)
+                                return;
+                            Intent intent = new Intent();
+                            intent.setAction(Intent.ACTION_SEND);
+                            String mimeType = image.getMimeType();
+                            intent.setType(mimeType);
+                            intent.putExtra(Intent.EXTRA_STREAM, u);
+                            boolean isImage = ImageManager.isImageMimeType(mimeType);
+                            try {
+                                activity.startActivity(Intent.createChooser(intent,
+                                        activity.getText(
+                                                isImage ? R.string.sendImage : R.string.sendVideo)));
+                            } catch (android.content.ActivityNotFoundException ex) {
+                                Toast.makeText(activity,
+                                        isImage ? R.string.no_way_to_share_image
+                                                : R.string.no_way_to_share_video,
+                                                Toast.LENGTH_SHORT).show();
+                            }
+                        }
+                    });
+                    return true;
+                }
+            });
+            item1.setIcon(android.R.drawable.ic_menu_share);
+            MenuItem item = item1;
+            requiresNoDrmAccessItems.add(item);
+        }
+
+        if ((inclusions & INCLUDE_DELETE_MENU) != 0) {
+            MenuItem deleteItem = menu.add(IMAGE_SAVING_ITEM, MENU_IMAGE_TOSS, 70, R.string.camera_toss);
+            requiresWriteAccessItems.add(deleteItem);
+            deleteItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+                public boolean onMenuItemClick(MenuItem item) {
+                    deleteImageImpl(activity, onDelete, isImage);
+                    return true;
+                }
+            })
+            .setAlphabeticShortcut('d')
+            .setIcon(android.R.drawable.ic_menu_delete);
+        }
+
+        if ((inclusions & INCLUDE_DETAILS_MENU) != 0) {
+            MenuItem detailsMenu = menu.add(0, 0, 80, R.string.details).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+                public boolean onMenuItemClick(MenuItem item) {
+                    onInvoke.run(new MenuCallback() {
+                        public void run(Uri u, ImageManager.IImage image) {
+                            if (image == null)
+                                return;
+
+                            AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+
+                            final View d = View.inflate(activity, R.layout.detailsview, null);
+
+                            ImageView imageView = (ImageView) d.findViewById(R.id.details_thumbnail_image);
+                            imageView.setImageBitmap(image.miniThumbBitmap());
+
+                            TextView textView = (TextView) d.findViewById(R.id.details_image_title);
+                            textView.setText(image.getDisplayName());
+
+                            long length = getImageFileSize(image);
+                            String lengthString = lengthString = length < 0 ? ""
+                                    : android.text.format.Formatter.formatFileSize(activity, length);
+                            ((TextView)d.findViewById(R.id.details_file_size_value))
+                                .setText(lengthString);
+
+                            int dimensionWidth = 0;
+                            int dimensionHeight = 0;
+                            if (isImage) {
+                                dimensionWidth = image.getWidth();
+                                dimensionHeight = image.getHeight();
+                                d.findViewById(R.id.details_duration_row).setVisibility(View.GONE);
+                                d.findViewById(R.id.details_frame_rate_row).setVisibility(View.GONE);
+                                d.findViewById(R.id.details_bit_rate_row).setVisibility(View.GONE);
+                                d.findViewById(R.id.details_format_row).setVisibility(View.GONE);
+                                d.findViewById(R.id.details_codec_row).setVisibility(View.GONE);
+                            } else {
+                                MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+                                try {
+                                    retriever.setMode(MediaMetadataRetriever.MODE_GET_METADATA_ONLY);
+                                    retriever.setDataSource(image.getDataPath());
+                                    try {
+                                        dimensionWidth = Integer.parseInt(
+                                                retriever.extractMetadata(
+                                                MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
+                                        dimensionHeight = Integer.parseInt(
+                                                retriever.extractMetadata(
+                                                MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
+                                    } catch (NumberFormatException e) {
+                                        dimensionWidth = 0;
+                                        dimensionHeight = 0;
+                                    }
+
+                                    try {
+                                        int durationMs = Integer.parseInt(retriever.extractMetadata(
+                                                MediaMetadataRetriever.METADATA_KEY_DURATION));
+                                        String durationValue = formatDuration(
+                                                activity, durationMs);
+                                        ((TextView)d.findViewById(R.id.details_duration_value))
+                                            .setText(durationValue);
+                                    } catch (NumberFormatException e) {
+                                        d.findViewById(R.id.details_frame_rate_row)
+                                        .setVisibility(View.GONE);
+                                    }
+
+                                    try {
+                                        String frame_rate = String.format(
+                                                activity.getString(R.string.details_fps),
+                                                Integer.parseInt(
+                                                        retriever.extractMetadata(
+                                                                MediaMetadataRetriever.METADATA_KEY_FRAME_RATE)));
+                                        ((TextView)d.findViewById(R.id.details_frame_rate_value))
+                                            .setText(frame_rate);
+                                    } catch (NumberFormatException e) {
+                                        d.findViewById(R.id.details_frame_rate_row)
+                                        .setVisibility(View.GONE);
+                                    }
+
+                                    try {
+                                        long bitRate = Long.parseLong(retriever.extractMetadata(
+                                                MediaMetadataRetriever.METADATA_KEY_BIT_RATE));
+                                        String bps;
+                                        if (bitRate < 1000000) {
+                                            bps = String.format(
+                                                    activity.getString(R.string.details_kbps),
+                                                    bitRate / 1000);
+                                        } else {
+                                            bps = String.format(
+                                                    activity.getString(R.string.details_mbps),
+                                                    ((double) bitRate) / 1000000.0);
+                                        }
+                                        ((TextView)d.findViewById(R.id.details_bit_rate_value))
+                                                .setText(bps);
+                                    } catch (NumberFormatException e) {
+                                        d.findViewById(R.id.details_bit_rate_row)
+                                                .setVisibility(View.GONE);
+                                    }
+
+                                    String format = retriever.extractMetadata(
+                                            MediaMetadataRetriever.METADATA_KEY_VIDEO_FORMAT);
+                                    ((TextView)d.findViewById(R.id.details_format_value))
+                                        .setText(format);
+
+                                    String codec = retriever.extractMetadata(
+                                                MediaMetadataRetriever.METADATA_KEY_CODEC);
+
+                                    if (codec == null) {
+                                        d.findViewById(R.id.details_codec_row).
+                                            setVisibility(View.GONE);
+                                    } else {
+                                        ((TextView)d.findViewById(R.id.details_codec_value))
+                                            .setText(codec);
+                                    }
+                                } catch(RuntimeException ex) {
+                                    // Assume this is a corrupt video file.
+                                } finally {
+                                    try {
+                                        retriever.release();
+                                    } catch (RuntimeException ex) {
+                                        // Ignore failures while cleaning up.
+                                    }
+                                }
+                            }
+
+                            String dimensionsString = String.format(
+                                    activity.getString(R.string.details_dimension_x),
+                                    dimensionWidth, dimensionHeight);
+                            ((TextView)d.findViewById(R.id.details_resolution_value))
+                                .setText(dimensionsString);
+
+                            String dateString = "";
+                            long dateTaken = image.getDateTaken();
+                            if (dateTaken != 0) {
+                                java.util.Date date = new java.util.Date(image.getDateTaken());
+                                java.text.SimpleDateFormat dateFormat = new java.text.SimpleDateFormat();
+                                dateString = dateFormat.format(date);
+
+                                ((TextView)d.findViewById(R.id.details_date_taken_value))
+                                    .setText(dateString);
+                            } else {
+                                d.findViewById(R.id.details_date_taken_row)
+                                    .setVisibility(View.GONE);
+                            }
+
+                            builder.setNeutralButton(R.string.details_ok,
+                                    new DialogInterface.OnClickListener() {
+                                    public void onClick(DialogInterface dialog, int which) {
+                                        dialog.dismiss();
+                                    }
+                                });
+
+                            builder.setIcon(android.R.drawable.ic_dialog_info)
+                                .setTitle(R.string.details_panel_title)
+                                .setView(d)
+                                .show();
+
+                        }
+                    });
+                    return true;
+                }
+            });
+            detailsMenu.setIcon(R.drawable.ic_menu_view_details);
+        }
+
+        if ((!isImage) && ((inclusions & INCLUDE_VIEWPLAY_MENU) != 0)) {
+            menu.add(VIDEO_SAVING_ITEM, MENU_VIDEO_PLAY, 0, R.string.video_play)
+                .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+                public boolean onMenuItemClick(MenuItem item) {
+                    onInvoke.run(new MenuCallback() {
+                        public void run(Uri uri, IImage image) {
+                            if (image != null) {
+                                Intent intent = new Intent(Intent.ACTION_VIEW,
+                                        image.fullSizeImageUri());
+                                activity.startActivity(intent);
+                            }
+                        }});
+                    return true;
+                }
+            });
+        }
+
+
+        return new MenuItemsResult() {
+            public void gettingReadyToOpen(Menu menu, ImageManager.IImage image) {
+                // protect against null here.  this isn't strictly speaking required
+                // but if a client app isn't handling sdcard removal properly it
+                // could happen
+                if (image == null) {
+                    return;
+                }
+                boolean readOnly = image.isReadonly();
+                boolean isDrm = image.isDrm();
+                if (Config.LOGV)
+                    Log.v(TAG, "readOnly: " + readOnly + "; drm: " + isDrm);
+                for (MenuItem item: requiresWriteAccessItems) {
+                    if (Config.LOGV)
+                        Log.v(TAG, "item is " + item.toString());
+                      item.setVisible(!readOnly);
+                      item.setEnabled(!readOnly);
+                }
+                for (MenuItem item: requiresNoDrmAccessItems) {
+                    if (Config.LOGV)
+                        Log.v(TAG, "item is " + item.toString());
+                      item.setVisible(!isDrm);
+                      item.setEnabled(!isDrm);
+                }
+            }
+            public void aboutToCall(MenuItem menu, ImageManager.IImage image) {
+            }
+        };
+    }
+
+    static void deletePhoto(Activity activity, Runnable onDelete) {
+        deleteImageImpl(activity, onDelete, true);
+    }
+
+    static void deleteImage(Activity activity, Runnable onDelete, IImage image) {
+        if (image != null) {
+            deleteImageImpl(activity, onDelete, ImageManager.isImage(image));
+        }
+    }
+
+    private static void deleteImageImpl(Activity activity, final Runnable onDelete, boolean isPhoto) {
+        boolean confirm = android.preference.PreferenceManager.getDefaultSharedPreferences(activity).getBoolean("pref_gallery_confirm_delete_key", true);
+        if (!confirm) {
+            if (onDelete != null)
+                onDelete.run();
+        } else {
+            displayDeleteDialog(activity, onDelete, isPhoto);
+        }
+    }
+
+    public static void displayDeleteDialog(Activity activity,
+            final Runnable onDelete, boolean isPhoto) {
+        android.app.AlertDialog.Builder b = new android.app.AlertDialog.Builder(activity);
+        b.setIcon(android.R.drawable.ic_dialog_alert);
+        b.setTitle(R.string.confirm_delete_title);
+        b.setMessage(isPhoto? R.string.confirm_delete_message
+                : R.string.confirm_delete_video_message);
+        b.setPositiveButton(android.R.string.ok, new android.content.DialogInterface.OnClickListener() {
+            public void onClick(android.content.DialogInterface v, int x) {
+                if (onDelete != null)
+                    onDelete.run();
+            }
+        });
+        b.setNegativeButton(android.R.string.cancel, new android.content.DialogInterface.OnClickListener() {
+            public void onClick(android.content.DialogInterface v, int x) {
+
+            }
+        });
+        b.create().show();
+    }
+
+    static void addSwitchModeMenuItem(Menu menu, final Activity activity,
+            final boolean switchToVideo) {
+        int group = switchToVideo ? MenuHelper.IMAGE_MODE_ITEM : MenuHelper.VIDEO_MODE_ITEM;
+        int labelId = switchToVideo ? R.string.switch_to_video_lable
+                : R.string.switch_to_camera_lable;
+        int iconId = switchToVideo ? R.drawable.ic_menu_camera_video_view
+                : android.R.drawable.ic_menu_camera;
+        MenuItem item = menu.add(group, MENU_SWITCH_CAMERA_MODE, 0,
+                labelId).setOnMenuItemClickListener(
+                        new OnMenuItemClickListener() {
+            public boolean onMenuItemClick(MenuItem item) {
+                String action = switchToVideo ? MediaStore.INTENT_ACTION_VIDEO_CAMERA
+                        : MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA;
+                Intent intent = new Intent(action);
+                intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+                activity.finish();
+                activity.startActivity(intent);
+                return true;
+             }
+        });
+        item.setIcon(iconId);
+    }
+
+    static void gotoStillImageCapture(Activity activity) {
+        Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        try {
+            activity.startActivity(intent);
+        } catch (ActivityNotFoundException e) {
+            Log.e(TAG, "Could not start still image capture activity", e);
+        }
+    }
+
+    static void gotoCameraImageGallery(Activity activity) {
+        gotoGallery(activity, R.string.gallery_camera_bucket_name, ImageManager.INCLUDE_IMAGES);
+    }
+
+    static void gotoCameraVideoGallery(Activity activity) {
+        gotoGallery(activity, R.string.gallery_camera_videos_bucket_name,
+                ImageManager.INCLUDE_VIDEOS);
+    }
+
+    static private void gotoGallery(Activity activity, int windowTitleId, int mediaTypes) {
+        Uri target = Images.Media.INTERNAL_CONTENT_URI.buildUpon().appendQueryParameter("bucketId",
+                ImageManager.CAMERA_IMAGE_BUCKET_ID).build();
+        Intent intent = new Intent(Intent.ACTION_VIEW, target);
+        intent.putExtra("windowTitle", activity.getString(windowTitleId));
+        intent.putExtra("mediaTypes", mediaTypes);
+        // Request unspecified so that we match the current camera orientation rather than
+        // matching the "flip orientation" preference.
+        // Disabled because people don't care for it. Also it's
+        // not as compelling now that we have implemented have quick orientation flipping.
+        // intent.putExtra(MediaStore.EXTRA_SCREEN_ORIENTATION,
+        //        android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+        try {
+            activity.startActivity(intent);
+        } catch (ActivityNotFoundException e) {
+            Log.e(TAG, "Could not start gallery activity", e);
+        }
+    }
+
+    static void addCaptureMenuItems(Menu menu, final Activity activity) {
+
+        menu.add(0, MENU_CAPTURE_PICTURE, 1, R.string.capture_picture)
+            .setOnMenuItemClickListener(
+                 new MenuItem.OnMenuItemClickListener() {
+                     public boolean onMenuItemClick(MenuItem item) {
+                        Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
+                        try {
+                               activity.startActivity(intent);
+                        } catch (android.content.ActivityNotFoundException e) {
+                            // Ignore exception
+                        }
+                return true;
+            }
+        })
+        .setIcon(android.R.drawable.ic_menu_camera);
+
+        menu.add(0, MENU_CAPTURE_VIDEO, 2, R.string.capture_video)
+            .setOnMenuItemClickListener(
+                 new MenuItem.OnMenuItemClickListener() {
+                     public boolean onMenuItemClick(MenuItem item) {
+                         Intent intent = new Intent(MediaStore.INTENT_ACTION_VIDEO_CAMERA);
+                         try {
+                             activity.startActivity(intent);
+                         } catch (android.content.ActivityNotFoundException e) {
+                             // Ignore exception
+                         }
+                return true;
+            }
+        })
+        .setIcon(R.drawable.ic_menu_camera_video_view);
+    }
+    static MenuItem addFlipOrientation(Menu menu, final Activity activity, final SharedPreferences prefs) {
+        // position 41 after rotate
+        // D
+        return menu
+                .add(Menu.CATEGORY_SECONDARY, 304, 41, R.string.flip_orientation)
+                .setOnMenuItemClickListener(
+                        new MenuItem.OnMenuItemClickListener() {
+            public boolean onMenuItemClick(MenuItem item) {
+                // Check what our actual orientation is
+                int current = activity.getResources().getConfiguration().orientation;
+                int newOrientation = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+                if (current == Configuration.ORIENTATION_LANDSCAPE) {
+                    newOrientation = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+                }
+                SharedPreferences.Editor editor = prefs.edit();
+                editor.putInt("nuorientation", newOrientation);
+                editor.commit();
+                requestOrientation(activity, prefs, true);
+                return true;
+            }
+        })
+        .setIcon(android.R.drawable.ic_menu_always_landscape_portrait);
+    }
+
+    static void requestOrientation(Activity activity, SharedPreferences prefs) {
+        requestOrientation(activity, prefs, false);
+    }
+
+    static private void requestOrientation(Activity activity, SharedPreferences prefs,
+            boolean ignoreIntentExtra) {
+        int req = prefs.getInt("nuorientation",
+                android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+        // A little trick: use USER instead of UNSPECIFIED, so we ignore the
+        // orientation set by the activity below.  It may have forced a landscape
+        // orientation, which the user has now cleared here.
+        if (req == android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
+            req = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER;
+        }
+        if (! ignoreIntentExtra) {
+            Intent intent = activity.getIntent();
+            req = intent.getIntExtra(MediaStore.EXTRA_SCREEN_ORIENTATION, req);
+        }
+        activity.setRequestedOrientation(req);
+    }
+
+    static void setFlipOrientationEnabled(Activity activity, MenuItem flipItem) {
+        int keyboard = activity.getResources().getConfiguration().hardKeyboardHidden;
+        flipItem.setEnabled(keyboard != android.content.res.Configuration.HARDKEYBOARDHIDDEN_NO);
+    }
+
+    public static String formatDuration(final Activity activity, int durationMs) {
+        int duration = durationMs / 1000;
+        int h = duration / 3600;
+        int m = (duration - h * 3600) / 60;
+        int s = duration - (h * 3600 + m * 60);
+        String durationValue;
+        if (h == 0) {
+            durationValue = String.format(
+                    activity.getString(R.string.details_ms), m, s);
+        } else {
+            durationValue = String.format(
+                    activity.getString(R.string.details_hms), h, m, s);
+        }
+        return durationValue;
+    }
+
+    public static void showStorageToast(Activity activity) {
+      showStorageToast(activity, calculatePicturesRemaining());
+    }
+
+    public static void showStorageToast(Activity activity, int remaining) {
+        String noStorageText = null;
+
+        if (remaining == MenuHelper.NO_STORAGE_ERROR) {
+            String state = Environment.getExternalStorageState();
+            if (state == Environment.MEDIA_CHECKING) {
+                noStorageText = activity.getString(R.string.preparing_sd);
+            } else {
+                noStorageText = activity.getString(R.string.no_storage);
+            }
+        } else if (remaining < 1) {
+            noStorageText = activity.getString(R.string.not_enough_space);
+        }
+
+        if (noStorageText != null) {
+            Toast.makeText(activity, noStorageText, 5000).show();
+        }
+    }
+
+    public static int calculatePicturesRemaining() {
+        try {
+            if (!ImageManager.hasStorage()) {
+                return NO_STORAGE_ERROR;
+            } else {
+                String storageDirectory = Environment.getExternalStorageDirectory().toString();
+                StatFs stat = new StatFs(storageDirectory);
+                float remaining = ((float)stat.getAvailableBlocks() * (float)stat.getBlockSize()) / 400000F;
+                return (int)remaining;
+            }
+        } catch (Exception ex) {
+            // if we can't stat the filesystem then we don't know how many
+            // pictures are remaining.  it might be zero but just leave it
+            // blank since we really don't know.
+            return CANNOT_STAT_ERROR;
+        }
+    }
+}
+
diff --git a/src/com/android/camera/MovieView.java b/src/com/android/camera/MovieView.java
new file mode 100644
index 0000000..bf0e6ca
--- /dev/null
+++ b/src/com/android/camera/MovieView.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2007 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.camera;
+
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ContentValues;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.DialogInterface.OnClickListener;
+import android.content.pm.ActivityInfo;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Video;
+import android.view.View;
+import android.widget.MediaController;
+import android.widget.VideoView;
+
+public class MovieView extends Activity implements MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener
+{
+    private static final String TAG = "MovieView";
+    // Copied from MediaPlaybackService in the Music Player app. Should be public, but isn't.
+    private static final String SERVICECMD = "com.android.music.musicservicecommand";
+    private static final String CMDNAME = "command";
+    private static final String CMDPAUSE = "pause";
+
+    private VideoView   mVideoView;
+    private View        mProgressView;
+    private boolean		mFinishOnCompletion;
+    private Uri			mUri;
+
+    // State maintained for proper onPause/OnResume behaviour.
+    private int mPositionWhenPaused = -1;
+    private boolean mWasPlayingWhenPaused = false;
+
+    public MovieView()
+    {
+    }
+
+    @Override
+    public void onCreate(Bundle icicle)
+    {
+        super.onCreate(icicle);
+
+        setContentView(R.layout.movie_view);
+
+        mVideoView = (VideoView) findViewById(R.id.surface_view);
+        mProgressView = findViewById(R.id.progress_indicator);
+        Intent intent = getIntent();
+        if (intent.hasExtra(MediaStore.EXTRA_SCREEN_ORIENTATION)) {
+            int orientation = intent.getIntExtra(MediaStore.EXTRA_SCREEN_ORIENTATION,
+                    ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+            if (orientation != getRequestedOrientation()) {
+                setRequestedOrientation(orientation);
+            }
+        }
+        mFinishOnCompletion = intent.getBooleanExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, true);
+        mUri = intent.getData();
+
+        // For streams that we expect to be slow to start up, show a
+        // progress spinner until playback starts.
+        String scheme = mUri.getScheme();
+        if ("http".equalsIgnoreCase(scheme) ||
+                "rtsp".equalsIgnoreCase(scheme)) {
+            mHandler.postDelayed(mPlayingChecker, 250);
+        } else {
+            mProgressView.setVisibility(View.GONE);
+        }
+
+        mVideoView.setOnErrorListener(this);
+        mVideoView.setOnCompletionListener(this);
+        mVideoView.setVideoURI(mUri);
+        mVideoView.setMediaController(new MediaController(this));
+        mVideoView.requestFocus(); // make the video view handle keys for seeking and pausing
+
+        Intent i = new Intent(SERVICECMD);
+        i.putExtra(CMDNAME, CMDPAUSE);
+        sendBroadcast(i);
+        {
+            final Integer bookmark = getBookmark();
+            if (bookmark != null) {
+                AlertDialog.Builder builder = new AlertDialog.Builder(this);
+                builder.setTitle(R.string.resume_playing_title);
+                builder.setMessage(String.format(
+                        getString(R.string.resume_playing_message),
+                        MenuHelper.formatDuration(this, bookmark)));
+                builder.setOnCancelListener(new OnCancelListener() {
+                    public void onCancel(DialogInterface dialog) {
+                        finish();
+                    }});
+                builder.setPositiveButton(R.string.resume_playing_resume,
+                        new OnClickListener(){
+                    public void onClick(DialogInterface dialog, int which) {
+                        mVideoView.seekTo(bookmark);
+                        mVideoView.start();
+                    }});
+                builder.setNegativeButton(R.string.resume_playing_restart, new OnClickListener(){
+                    public void onClick(DialogInterface dialog, int which) {
+                        mVideoView.start();
+                    }});
+                builder.show();
+            } else {
+                mVideoView.start();
+            }
+        }
+    }
+
+    private static boolean uriSupportsBookmarks(Uri uri) {
+        String scheme = uri.getScheme();
+        String authority = uri.getAuthority();
+        return ("content".equalsIgnoreCase(scheme)
+                && MediaStore.AUTHORITY.equalsIgnoreCase(authority));
+    }
+    
+    private Integer getBookmark() {
+        if (!uriSupportsBookmarks(mUri)) {
+            return null;
+        }
+        
+        String[] projection = new String[]{Video.VideoColumns.DURATION,
+                Video.VideoColumns.BOOKMARK};
+        try {
+            Cursor cursor = getContentResolver().query(mUri, projection, null, null, null);
+            if (cursor != null) {
+                try {
+                    if ( cursor.moveToFirst() ) {
+                        int duration = getCursorInteger(cursor, 0);
+                        int bookmark = getCursorInteger(cursor, 1);
+                        final int ONE_MINUTE = 60 * 1000;
+                        final int TWO_MINUTES = 2 * ONE_MINUTE;
+                        final int FIVE_MINUTES = 5 * ONE_MINUTE;
+                        if ((bookmark < TWO_MINUTES)
+                                || (duration < FIVE_MINUTES)
+                                || (bookmark > (duration - ONE_MINUTE))) {
+                            return null;
+                        }
+
+                        return new Integer(bookmark);
+                    }
+                } finally {
+                    cursor.close();
+                }
+            }
+        } catch (SQLiteException e) {
+            // ignore
+        }
+
+        return null;
+    }
+
+    private int getCursorInteger(Cursor cursor, int index) {
+        try {
+            return cursor.getInt(index);
+        } catch (SQLiteException e) {
+            return 0;
+        } catch (NumberFormatException e) {
+            return 0;
+        }
+
+    }
+
+    private void setBookmark(int bookmark) {
+        if (!uriSupportsBookmarks(mUri)) {
+            return;
+        }
+        
+        ContentValues values = new ContentValues();
+        values.put(Video.VideoColumns.BOOKMARK, Integer.toString(bookmark));
+        try {
+            getContentResolver().update(mUri, values, null, null);
+        } catch (SecurityException ex) {
+            // Ignore, can happen if we try to set the bookmark on a read-only resource
+            // such as a video attached to GMail.
+        } catch (SQLiteException e) {
+            // ignore. can happen if the content doesn't support a bookmark column.
+        } catch (UnsupportedOperationException e) {
+            // ignore. can happen if the external volume is already detached.
+        }
+    }
+
+    @Override
+    public void onPause() {
+        mHandler.removeCallbacksAndMessages(null);
+        setBookmark(mVideoView.getCurrentPosition());
+
+        mPositionWhenPaused = mVideoView.getCurrentPosition();
+        mWasPlayingWhenPaused = mVideoView.isPlaying();
+        mVideoView.stopPlayback();
+
+        super.onPause();
+    }
+
+    @Override
+    public void onResume() {
+        if (mPositionWhenPaused >= 0) {
+            mVideoView.setVideoURI(mUri);
+            mVideoView.seekTo(mPositionWhenPaused);
+            if (mWasPlayingWhenPaused) {
+                mVideoView.start();
+            }
+        }
+
+        super.onResume();
+    }
+
+    Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+        }
+    };
+
+    Runnable mPlayingChecker = new Runnable() {
+        public void run() {
+            if (mVideoView.isPlaying()) {
+                mProgressView.setVisibility(View.GONE);
+            } else {
+                mHandler.postDelayed(mPlayingChecker, 250);
+            }
+        }
+    };
+
+    public boolean onError(MediaPlayer player, int arg1, int arg2) {
+        mHandler.removeCallbacksAndMessages(null);
+        mProgressView.setVisibility(View.GONE);
+        return false;
+    }
+
+    public void onCompletion(MediaPlayer mp) {
+        if (mFinishOnCompletion) {
+            finish();
+        }
+    }
+}
diff --git a/src/com/android/camera/OnScreenHint.java b/src/com/android/camera/OnScreenHint.java
new file mode 100644
index 0000000..96190a0
--- /dev/null
+++ b/src/com/android/camera/OnScreenHint.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2009 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.camera;
+
+import android.app.INotificationManager;
+import android.app.ITransientNotification;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.RemoteException;
+import android.os.Handler;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+/**
+ * A on-screen hint is a view containing a little message for the user and will
+ * be shown on the screen continuously.  This class helps you create and show
+ * those.
+ *
+ * <p>
+ * When the view is shown to the user, appears as a floating view over the
+ * application.
+ * <p>
+ * The easiest way to use this class is to call one of the static methods that
+ * constructs everything you need and returns a new OnScreenHint object.
+ */
+public class OnScreenHint {
+    static final String TAG = "OnScreenHint";
+    static final boolean localLOGV = false;
+
+    final Context mContext;
+    int mGravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+    int mX, mY;
+    float mHorizontalMargin;
+    float mVerticalMargin;
+    View mView;
+    View mNextView;
+
+    private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
+    private WindowManager mWM;
+    private final Handler mHandler = new Handler();
+
+    /**
+     * Construct an empty OnScreenHint object.  You must call {@link #setView} before you
+     * can call {@link #show}.
+     *
+     * @param context  The context to use.  Usually your {@link android.app.Application}
+     *                 or {@link android.app.Activity} object.
+     */
+    public OnScreenHint(Context context) {
+        mContext = context;
+        mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        mY = context.getResources().getDimensionPixelSize(R.dimen.hint_y_offset);
+
+        mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
+        mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
+        mParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+        mParams.format = PixelFormat.TRANSLUCENT;
+        mParams.windowAnimations = R.style.Animation_OnScreenHint;
+        mParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+        mParams.setTitle("OnScreenHint");
+    }
+
+    /**
+     * Show the view on the screen.
+     */
+    public void show() {
+        if (mNextView == null) {
+            throw new RuntimeException("setView must have been called");
+        }
+        if (localLOGV) Log.v(TAG, "SHOW: " + this);
+        mHandler.post(mShow);
+    }
+
+    /**
+     * Close the view if it's showing.
+     */
+    public void cancel() {
+        if (localLOGV) Log.v(TAG, "HIDE: " + this);
+        mHandler.post(mHide);
+    }
+
+    /**
+     * Set the view to show.
+     * @see #getView
+     */
+    public void setView(View view) {
+        mNextView = view;
+    }
+
+    /**
+     * Return the view.
+     * @see #setView
+     */
+    public View getView() {
+        return mNextView;
+    }
+
+    /**
+     * Set the margins of the view.
+     *
+     * @param horizontalMargin The horizontal margin, in percentage of the
+     *        container width, between the container's edges and the
+     *        notification
+     * @param verticalMargin The vertical margin, in percentage of the
+     *        container height, between the container's edges and the
+     *        notification
+     */
+    public void setMargin(float horizontalMargin, float verticalMargin) {
+        mHorizontalMargin = horizontalMargin;
+        mVerticalMargin = verticalMargin;
+    }
+
+    /**
+     * Return the horizontal margin.
+     */
+    public float getHorizontalMargin() {
+        return mHorizontalMargin;
+    }
+
+    /**
+     * Return the vertical margin.
+     */
+    public float getVerticalMargin() {
+        return mVerticalMargin;
+    }
+
+    /**
+     * Set the location at which the notification should appear on the screen.
+     * @see android.view.Gravity
+     * @see #getGravity
+     */
+    public void setGravity(int gravity, int xOffset, int yOffset) {
+        mGravity = gravity;
+        mX = xOffset;
+        mY = yOffset;
+    }
+
+     /**
+     * Get the location at which the notification should appear on the screen.
+     * @see android.view.Gravity
+     * @see #getGravity
+     */
+    public int getGravity() {
+        return mGravity;
+    }
+
+    /**
+     * Return the X offset in pixels to apply to the gravity's location.
+     */
+    public int getXOffset() {
+        return mX;
+    }
+
+    /**
+     * Return the Y offset in pixels to apply to the gravity's location.
+     */
+    public int getYOffset() {
+        return mY;
+    }
+
+    /**
+     * Make a standard hint that just contains a text view.
+     *
+     * @param context  The context to use.  Usually your {@link android.app.Application}
+     *                 or {@link android.app.Activity} object.
+     * @param text     The text to show.  Can be formatted text.
+     *
+     */
+    public static OnScreenHint makeText(Context context, CharSequence text) {
+        OnScreenHint result = new OnScreenHint(context);
+
+        LayoutInflater inflate = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        View v = inflate.inflate(R.layout.on_screen_hint, null);
+        TextView tv = (TextView)v.findViewById(R.id.message);
+        tv.setText(text);
+
+        result.mNextView = v;
+
+        return result;
+    }
+
+    /**
+     * Make a standard hint that just contains a text view with the text from a resource.
+     *
+     * @param context  The context to use.  Usually your {@link android.app.Application}
+     *                 or {@link android.app.Activity} object.
+     * @param resId    The resource id of the string resource to use.  Can be formatted text.
+     *
+     * @throws Resources.NotFoundException if the resource can't be found.
+     */
+    public static OnScreenHint makeText(Context context, int resId)
+                                throws Resources.NotFoundException {
+        return makeText(context, context.getResources().getText(resId));
+    }
+
+    /**
+     * Update the text in a OnScreenHint that was previously created using one of the makeText() methods.
+     * @param resId The new text for the OnScreenHint.
+     */
+    public void setText(int resId) {
+        setText(mContext.getText(resId));
+    }
+
+    /**
+     * Update the text in a OnScreenHint that was previously created using one of the makeText() methods.
+     * @param s The new text for the OnScreenHint.
+     */
+    public void setText(CharSequence s) {
+        if (mNextView == null) {
+            throw new RuntimeException("This OnScreenHint was not created with OnScreenHint.makeText()");
+        }
+        TextView tv = (TextView) mNextView.findViewById(R.id.message);
+        if (tv == null) {
+            throw new RuntimeException("This OnScreenHint was not created with OnScreenHint.makeText()");
+        }
+        tv.setText(s);
+    }
+
+    private synchronized void handleShow() {
+        if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
+                + " mNextView=" + mNextView);
+        if (mView != mNextView) {
+            // remove the old view if necessary
+            handleHide();
+            mView = mNextView;
+            final int gravity = mGravity;
+            mParams.gravity = gravity;
+            if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
+                mParams.horizontalWeight = 1.0f;
+            }
+            if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
+                mParams.verticalWeight = 1.0f;
+            }
+            mParams.x = mX;
+            mParams.y = mY;
+            mParams.verticalMargin = mVerticalMargin;
+            mParams.horizontalMargin = mHorizontalMargin;
+            if (mView.getParent() != null) {
+                if (localLOGV) Log.v(
+                        TAG, "REMOVE! " + mView + " in " + this);
+                mWM.removeView(mView);
+            }
+            if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
+            mWM.addView(mView, mParams);
+        }
+    }
+
+    private synchronized void handleHide() {
+        if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
+        if (mView != null) {
+            // note: checking parent() just to make sure the view has
+            // been added...  i have seen cases where we get here when
+            // the view isn't yet added, so let's try not to crash.
+            if (mView.getParent() != null) {
+                if (localLOGV) Log.v(
+                        TAG, "REMOVE! " + mView + " in " + this);
+                mWM.removeView(mView);
+            }
+            mView = null;
+        }
+    }
+
+    private Runnable mShow = new Runnable() {
+        public void run() {
+            handleShow();
+        }
+    };
+
+    private Runnable mHide = new Runnable() {
+        public void run() {
+            handleHide();
+        }
+    };
+}
+
diff --git a/src/com/android/camera/PhotoGadgetBind.java b/src/com/android/camera/PhotoGadgetBind.java
new file mode 100644
index 0000000..fff19de
--- /dev/null
+++ b/src/com/android/camera/PhotoGadgetBind.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2009 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.camera;
+
+import com.android.camera.PhotoGadgetProvider.PhotoDatabaseHelper;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.gadget.GadgetManager;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.RemoteViews;
+
+import java.util.ArrayList;
+
+public class PhotoGadgetBind extends Activity {
+    static final String TAG = "PhotoGadgetBind";
+    
+    static final String EXTRA_GADGET_BITMAPS = "com.android.camera.gadgetbitmaps";
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        finish();
+        
+        // The caller has requested that we bind a given bitmap to a specific
+        // gadgetId, which probably is happening during a Launcher upgrade. This
+        // is dangerous because the caller could set bitmaps on gadgetIds they
+        // don't own, so we guard this call at the manifest level by requiring
+        // the BIND_GADGET permission.
+        
+        final Intent intent = getIntent();
+        final Bundle extras = intent.getExtras();
+        
+        final int[] gadgetIds = extras.getIntArray(GadgetManager.EXTRA_GADGET_IDS);
+        final ArrayList<Bitmap> bitmaps = extras.getParcelableArrayList(EXTRA_GADGET_BITMAPS);
+        
+        if (gadgetIds == null || bitmaps == null ||
+                gadgetIds.length != bitmaps.size()) {
+            Log.e(TAG, "Problem parsing photo gadget bind request");
+            return;
+        }
+        
+        GadgetManager gadgetManager = GadgetManager.getInstance(this);
+        PhotoDatabaseHelper helper = new PhotoDatabaseHelper(this);
+        for (int i = 0; i < gadgetIds.length; i++) {
+            // Store the cropped photo in our database
+            int gadgetId = gadgetIds[i];
+            helper.setPhoto(gadgetId, bitmaps.get(i));
+            
+            // Push newly updated gadget to surface
+            RemoteViews views = PhotoGadgetProvider.buildUpdate(this, gadgetId, helper);
+            gadgetManager.updateGadget(new int[] { gadgetId }, views);
+        }
+        helper.close();
+        
+    }
+}
diff --git a/src/com/android/camera/PhotoGadgetConfigure.java b/src/com/android/camera/PhotoGadgetConfigure.java
new file mode 100644
index 0000000..a94b5a3
--- /dev/null
+++ b/src/com/android/camera/PhotoGadgetConfigure.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2009 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.camera;
+
+import com.android.camera.PhotoGadgetProvider.PhotoDatabaseHelper;
+
+import android.app.Activity;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.database.sqlite.SQLiteDatabase;
+import android.gadget.GadgetManager;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.Log;
+import android.widget.RemoteViews;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class PhotoGadgetConfigure extends Activity {
+    static final private String TAG = "PhotoGadgetConfigure";
+    
+    static final int REQUEST_GET_PHOTO = 2;
+    
+    int gadgetId = -1;
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        
+        // Someone is requesting that we configure the given gadgetId, which means
+        // we prompt the user to pick and crop a photo.
+        
+        gadgetId = getIntent().getIntExtra(GadgetManager.EXTRA_GADGET_ID, -1);
+        if (gadgetId == -1) {
+            setResult(Activity.RESULT_CANCELED);
+            finish();
+        }
+
+        // TODO: get these values from constants somewhere
+        // TODO: Adjust the PhotoFrame's image size to avoid on the fly scaling
+        Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
+        intent.setType("image/*");
+        intent.putExtra("crop", "true");
+        intent.putExtra("aspectX", 1);
+        intent.putExtra("aspectY", 1);
+        intent.putExtra("outputX", 192);
+        intent.putExtra("outputY", 192);
+        intent.putExtra("noFaceDetection", true);
+        intent.putExtra("return-data", true);
+        
+        startActivityForResult(intent, REQUEST_GET_PHOTO);
+    }
+    
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (resultCode == RESULT_OK && gadgetId != -1) {
+            // Store the cropped photo in our database
+            Bitmap bitmap = (Bitmap) data.getParcelableExtra("data");
+            
+            PhotoDatabaseHelper helper = new PhotoDatabaseHelper(this);
+            if (helper.setPhoto(gadgetId, bitmap)) {
+                resultCode = Activity.RESULT_OK;
+
+                // Push newly updated gadget to surface
+                RemoteViews views = PhotoGadgetProvider.buildUpdate(this, gadgetId, helper);
+                GadgetManager gadgetManager = GadgetManager.getInstance(this);
+                gadgetManager.updateGadget(new int[] { gadgetId }, views);
+            }
+            helper.close();
+        } else {
+            resultCode = Activity.RESULT_CANCELED;
+        }
+        
+        // Make sure we pass back the original gadgetId
+        Intent resultValue = new Intent();
+        resultValue.putExtra(GadgetManager.EXTRA_GADGET_ID, gadgetId);
+        setResult(resultCode, resultValue);
+        finish();
+    }
+    
+}
diff --git a/src/com/android/camera/PhotoGadgetProvider.java b/src/com/android/camera/PhotoGadgetProvider.java
new file mode 100644
index 0000000..b03217d
--- /dev/null
+++ b/src/com/android/camera/PhotoGadgetProvider.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2009 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.camera;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.gadget.GadgetManager;
+import android.gadget.GadgetProvider;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.Settings;
+import android.provider.Calendar.Attendees;
+import android.provider.Calendar.Calendars;
+import android.provider.Calendar.Instances;
+import android.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.util.Config;
+import android.util.Log;
+import android.util.Xml;
+import android.view.View;
+import android.widget.RemoteViews;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+
+/**
+ * Simple gadget to show a user-selected picture.
+ */
+public class PhotoGadgetProvider extends GadgetProvider {
+    static final String TAG = "PhotoGadgetProvider";
+    static final boolean LOGD = Config.LOGD || true;
+    
+    @Override
+    public void onUpdate(Context context, GadgetManager gadgetManager, int[] gadgetIds) {
+        // Update each requested gadgetId with its unique photo
+        PhotoDatabaseHelper helper = new PhotoDatabaseHelper(context);
+        for (int gadgetId : gadgetIds) {
+            int[] specificGadget = new int[] { gadgetId };
+            RemoteViews views = buildUpdate(context, gadgetId, helper);
+            if (LOGD) Log.d(TAG, "sending out views="+views+" for id="+gadgetId);
+            gadgetManager.updateGadget(specificGadget, views);
+        }
+        helper.close();
+    }
+    
+    @Override
+    public void onDeleted(Context context, int[] gadgetIds) {
+        // Clean deleted photos out of our database
+        PhotoDatabaseHelper helper = new PhotoDatabaseHelper(context);
+        for (int gadgetId : gadgetIds) {
+            helper.deletePhoto(gadgetId);
+        }
+        helper.close();
+    }
+
+    /**
+     * Load photo for given gadget and build {@link RemoteViews} for it.
+     */
+    static RemoteViews buildUpdate(Context context, int gadgetId, PhotoDatabaseHelper helper) {
+        RemoteViews views = null;
+        Bitmap bitmap = helper.getPhoto(gadgetId);
+        if (bitmap != null) {
+            views = new RemoteViews(context.getPackageName(), R.layout.photo_frame);
+            views.setImageViewBitmap(R.id.photo, bitmap);
+        }
+        return views;
+    }
+
+    static class PhotoDatabaseHelper extends SQLiteOpenHelper {
+        private final Context mContext;
+
+        private static final String DATABASE_NAME = "launcher.db";
+        
+        private static final int DATABASE_VERSION = 1;
+
+        static final String TABLE_PHOTOS = "photos";
+        static final String FIELD_GADGET_ID = "gadgetId";
+        static final String FIELD_PHOTO_BLOB = "photoBlob";
+
+        PhotoDatabaseHelper(Context context) {
+            super(context, DATABASE_NAME, null, DATABASE_VERSION);
+            mContext = context;
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            db.execSQL("CREATE TABLE " + TABLE_PHOTOS + " (" +
+                    FIELD_GADGET_ID + " INTEGER PRIMARY KEY," +
+                    FIELD_PHOTO_BLOB + " BLOB" +
+                    ");");
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            int version = oldVersion;
+            
+            if (version != DATABASE_VERSION) {
+                Log.w(TAG, "Destroying all old data.");
+                db.execSQL("DROP TABLE IF EXISTS " + TABLE_PHOTOS);
+                onCreate(db);
+            }
+        }
+        
+        /**
+         * Store the given bitmap in this database for the given gadgetId.
+         */
+        public boolean setPhoto(int gadgetId, Bitmap bitmap) {
+            boolean success = false;
+            try {
+                // Try go guesstimate how much space the icon will take when serialized
+                // to avoid unnecessary allocations/copies during the write.
+                int size = bitmap.getWidth() * bitmap.getHeight() * 4;
+                ByteArrayOutputStream out = new ByteArrayOutputStream(size);
+                bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
+                out.flush();
+                out.close();
+
+                ContentValues values = new ContentValues();
+                values.put(PhotoDatabaseHelper.FIELD_GADGET_ID, gadgetId);
+                values.put(PhotoDatabaseHelper.FIELD_PHOTO_BLOB, out.toByteArray());
+                    
+                SQLiteDatabase db = getWritableDatabase();
+                db.insertOrThrow(PhotoDatabaseHelper.TABLE_PHOTOS, null, values);
+                
+                success = true;
+            } catch (SQLiteException e) {
+                Log.e(TAG, "Could not open database", e);
+            } catch (IOException e) {
+                Log.e(TAG, "Could not serialize photo", e);
+            }
+            if (LOGD) Log.d(TAG, "setPhoto success="+success);
+            return success;
+        }
+        
+        static final String[] PHOTOS_PROJECTION = {
+            FIELD_PHOTO_BLOB,
+        };
+        
+        static final int INDEX_PHOTO_BLOB = 0;
+        
+        /**
+         * Inflate and return a bitmap for the given gadgetId.
+         */
+        public Bitmap getPhoto(int gadgetId) {
+            Cursor c = null;
+            Bitmap bitmap = null;
+            try {
+                SQLiteDatabase db = getReadableDatabase();
+                String selection = String.format("%s=%d", FIELD_GADGET_ID, gadgetId);
+                c = db.query(TABLE_PHOTOS, PHOTOS_PROJECTION, selection, null,
+                        null, null, null, null);
+                
+                if (c != null && LOGD) Log.d(TAG, "getPhoto query count="+c.getCount());
+
+                if (c != null && c.moveToFirst()) {
+                    byte[] data = c.getBlob(INDEX_PHOTO_BLOB);
+                    if (data != null) {
+                        bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
+                    }
+                }
+            } catch (SQLiteException e) {
+                Log.e(TAG, "Could not load photo from database", e);
+            } finally {
+                if (c != null) {
+                    c.close();
+                }
+            }
+            return bitmap;
+        }
+        
+        /**
+         * Remove any bitmap associated with the given gadgetId.
+         */
+        public void deletePhoto(int gadgetId) {
+            try {
+                SQLiteDatabase db = getWritableDatabase();
+                String whereClause = String.format("%s=%d", FIELD_GADGET_ID, gadgetId);
+                db.delete(TABLE_PHOTOS, whereClause, null);
+            } catch (SQLiteException e) {
+                Log.e(TAG, "Could not delete photo from database", e);
+            }
+        }
+    }
+    
+}
+
diff --git a/src/com/android/camera/PickWallpaper.java b/src/com/android/camera/PickWallpaper.java
new file mode 100644
index 0000000..e1fe784
--- /dev/null
+++ b/src/com/android/camera/PickWallpaper.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2007 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.camera;
+
+/**
+ * Wallpaper picker for the camera application. This just redirects to the standard pick action.
+ */
+public class PickWallpaper extends Wallpaper {
+}
diff --git a/src/com/android/camera/SelectedImageGetter.java b/src/com/android/camera/SelectedImageGetter.java
new file mode 100644
index 0000000..9e8fb96
--- /dev/null
+++ b/src/com/android/camera/SelectedImageGetter.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2007 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.camera;
+
+import android.net.Uri;
+
+interface SelectedImageGetter {
+    ImageManager.IImage getCurrentImage();
+    Uri getCurrentImageUri();
+}
+
diff --git a/src/com/android/camera/ShutterButton.java b/src/com/android/camera/ShutterButton.java
new file mode 100644
index 0000000..bd8d042
--- /dev/null
+++ b/src/com/android/camera/ShutterButton.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2008 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.camera;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+/**
+ * A button designed to be used for the on-screen shutter button.
+ * It's currently an ImageView that can call a delegate when the pressed state changes.
+ */
+public class ShutterButton extends ImageView {
+    /**
+     * Interface definition for a callback to be invoked when a ModeButton's pressed state changes.
+     */
+    public interface OnShutterButtonListener {
+        /**
+         * Called when a ShutterButton has been pressed.
+         *
+         * @param b The ShutterButton that was pressed.
+         */
+        void onShutterButtonFocus(ShutterButton b, boolean pressed);
+        void onShutterButtonClick(ShutterButton b);
+    }
+
+    private OnShutterButtonListener mListener;
+    private boolean mOldPressed;
+
+    public ShutterButton(Context context) {
+        super(context);
+    }
+
+    public ShutterButton(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public ShutterButton(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public void setOnShutterButtonListener(OnShutterButtonListener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * Hook into the drawable state changing to get changes to isPressed -- the
+     * onPressed listener doesn't always get called when the pressed state changes.
+     */
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        final boolean pressed = isPressed();
+        if (pressed != mOldPressed) {
+            if (!pressed) {
+                // When pressing the physical camera button the sequence of events is:
+                //    focus pressed, optional camera pressed, focus released.
+                // We want to emulate this sequence of events with the shutter button.
+                // When clicking using a trackball button, the view system changes
+                // the the drawable state before posting click notification, so the
+                // sequence of events is:
+                //    pressed(true), optional click, pressed(false)
+                // When clicking using touch events, the view system changes the
+                // drawable state after posting click notification, so the sequence of
+                // events is:
+                //    pressed(true), pressed(false), optional click
+                // Since we're emulating the physical camera button, we want to have the
+                // same order of events. So we want the optional click callback to be delivered
+                // before the pressed(false) callback.
+                //
+                // To do this, we delay the posting of the pressed(false) event slightly by
+                // pushing it on the event queue. This moves it after the optional click
+                // notification, so our client always sees events in this sequence:
+                //     pressed(true), optional click, pressed(false)
+                post(new Runnable() {
+                    public void run() {
+                        callShutterButtonFocus(pressed);
+                    }
+                });
+            } else {
+                callShutterButtonFocus(pressed);
+            }
+            mOldPressed = pressed;
+        }
+    }
+
+    private void callShutterButtonFocus(boolean pressed) {
+        if (mListener != null) {
+            mListener.onShutterButtonFocus(this, pressed);
+        }
+    }
+
+    @Override
+    public boolean performClick() {
+        boolean result = super.performClick();
+        if (mListener != null) {
+            mListener.onShutterButtonClick(this);
+        }
+        return result;
+    }
+}
diff --git a/src/com/android/camera/SlideShow.java b/src/com/android/camera/SlideShow.java
new file mode 100644
index 0000000..23c7d4a
--- /dev/null
+++ b/src/com/android/camera/SlideShow.java
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2007 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.camera;
+
+import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Paint;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.os.Message;
+import android.view.View;
+import android.view.Window;
+import android.widget.ImageView;
+import android.widget.ViewSwitcher;
+import android.widget.Gallery.LayoutParams;
+
+import android.view.WindowManager;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import com.android.camera.ImageManager.IGetBitmap_cancelable;
+import com.android.camera.ImageManager.IImage;
+import com.android.camera.ImageManager.IImageList;
+
+import android.view.MotionEvent;
+
+public class SlideShow extends Activity implements ViewSwitcher.ViewFactory
+{
+    static final private String TAG = "SlideShow";
+    static final int sLag = 2000;
+    static final int sNextImageInterval = 3000;
+    private ImageManager.IImageList mImageList;
+    private int mCurrentPosition = 0;
+    private ImageView mSwitcher;
+    private boolean mPosted = false;
+
+    @Override
+    protected void onCreate(Bundle icicle)
+    {
+        super.onCreate(icicle);
+        Window wp = getWindow();
+        wp.setFlags(FLAG_KEEP_SCREEN_ON, FLAG_KEEP_SCREEN_ON);
+
+        setContentView(R.layout.slide_show);
+
+        mSwitcher = (ImageView)findViewById(R.id.imageview);
+        if (android.util.Config.LOGV)
+            Log.v(TAG, "mSwitcher " + mSwitcher);
+    }
+
+    @Override
+    protected void onResume()
+    {
+        super.onResume();
+
+        if (mImageList == null) {
+            mImageList = new FileImageList();
+            mCurrentPosition = 0;
+        }
+        loadImage();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        cancelPost();
+    }
+
+    static public class ImageViewTouch extends ImageView {
+        class xy {
+            public xy(float xIn, float yIn) {
+                x = xIn;
+                y = yIn;
+                timeAdded = System.currentTimeMillis();
+            }
+            public xy(MotionEvent e) {
+                x = e.getX();
+                y = e.getY();
+                timeAdded = System.currentTimeMillis();
+            }
+            float x,y;
+            long timeAdded;
+        }
+
+        SlideShow mSlideShow;
+        Paint mPaints[] = new Paint[1];
+        ArrayList<xy> mPoints = new ArrayList<xy>();
+        boolean mDown;
+
+        public ImageViewTouch(Context context) {
+            super(context);
+            mSlideShow = (SlideShow) context;
+            setScaleType(ImageView.ScaleType.CENTER);
+            setupPaint();
+        }
+
+        public ImageViewTouch(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            mSlideShow = (SlideShow) context;
+            setScaleType(ImageView.ScaleType.CENTER);
+            setupPaint();
+        }
+
+        private void setupPaint() {
+            for (int i = 0; i < mPaints.length; i++) {
+                Paint p = new Paint();
+                p.setARGB(255, 255, 255, 0);
+                p.setAntiAlias(true);
+                p.setStyle(Paint.Style.FILL);
+                p.setStrokeWidth(3F);
+                mPaints[i] = p;
+            }
+        }
+
+        private void addEvent(MotionEvent event) {
+            long now = System.currentTimeMillis();
+            mPoints.add(new xy(event));
+            for (int i = 0; i < event.getHistorySize(); i++)
+                mPoints.add(new xy(event.getHistoricalX(i), event.getHistoricalY(i)));
+            while (mPoints.size() > 0) {
+                xy ev = mPoints.get(0);
+                if (now - ev.timeAdded < sLag)
+                    break;
+                mPoints.remove(0);
+            }
+        }
+
+        public boolean onTouchEvent(MotionEvent event) {
+            addEvent(event);
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    mDown = true;
+                    mSlideShow.cancelPost();
+                    postInvalidate();
+                    break;
+                case MotionEvent.ACTION_UP:
+                    mDown = false;
+                    postInvalidate();
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    mSlideShow.cancelPost();
+                    postInvalidate();
+                    break;
+            }
+            return true;
+        }
+
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+
+            boolean didPaint = false;
+            long now = System.currentTimeMillis();
+            for (xy ev: mPoints) {
+                Paint p = mPaints[0];
+                long delta = now - ev.timeAdded;
+                if (delta > sLag)
+                    continue;
+
+                int alpha2 = Math.max(0, 255 - (255 * (int)delta / sLag));
+                if (alpha2 == 0)
+                    continue;
+                p.setAlpha(alpha2);
+                canvas.drawCircle(ev.x, ev.y, 2, p);
+                didPaint = true;
+            }
+            if (didPaint && !mDown)
+                postInvalidate();
+        }
+    }
+
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                cancelPost();
+                loadPreviousImage();
+                return true;
+
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                cancelPost();
+                loadNextImage();
+                return true;
+
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+                if (mPosted)
+                    cancelPost();
+                else
+                    loadNextImage();
+                return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    private void cancelPost() {
+        mHandler.removeCallbacks(mNextImageRunnable);
+        mPosted = false;
+    }
+
+    private void post() {
+        mHandler.postDelayed(mNextImageRunnable, sNextImageInterval);
+        mPosted = true;
+    }
+
+    private void loadImage() {
+        ImageManager.IImage image = mImageList.getImageAt(mCurrentPosition);
+        if (image == null)
+            return;
+
+        Bitmap bitmap = image.thumbBitmap();
+        if (bitmap == null)
+            return;
+
+        mSwitcher.setImageDrawable(new BitmapDrawable(bitmap));
+        post();
+    }
+
+    private Runnable mNextImageRunnable = new Runnable() {
+        public void run() {
+            if (android.util.Config.LOGV)
+                Log.v(TAG, "mNextImagerunnable called");
+            loadNextImage();
+        }
+    };
+
+    private void loadNextImage() {
+        if (++mCurrentPosition >= mImageList.getCount())
+            mCurrentPosition = 0;
+        loadImage();
+    }
+
+    private void loadPreviousImage() {
+        if (mCurrentPosition == 0)
+            mCurrentPosition = mImageList.getCount() - 1;
+        else
+            mCurrentPosition -= 1;
+
+        loadImage();
+    }
+
+    public View makeView() {
+        ImageView i = new ImageView(this);
+        i.setBackgroundColor(0xFF000000);
+        i.setScaleType(ImageView.ScaleType.FIT_CENTER);
+        i.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
+        return i;
+    }
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+        }
+    };
+
+    class FileImageList implements IImageList {
+        public HashMap<String, String> getBucketIds() {
+            throw new UnsupportedOperationException();
+        }
+
+        public void checkThumbnails(ThumbCheckCallback cb, int totalThumbnails) {
+            // TODO Auto-generated method stub
+
+        }
+
+        public void commitChanges() {
+            // TODO Auto-generated method stub
+
+        }
+
+        public void removeOnChangeListener(OnChange changeCallback) {
+            // TODO Auto-generated method stub
+
+        }
+
+        public void setOnChangeListener(OnChange changeCallback, Handler h) {
+            // TODO Auto-generated method stub
+
+        }
+
+        private ArrayList<FileImage> mImages = new ArrayList<FileImage>();
+        // image uri ==> Image object
+        private HashMap<Long, IImage> mCache = new HashMap<Long, IImage>();
+
+        class FileImage extends ImageManager.SimpleBaseImage {
+            long mId;
+            String mPath;
+
+            FileImage(long id, String path) {
+                mId = id;
+                mPath = path;
+            }
+
+            public long imageId() {
+                return mId;
+            }
+
+            public String getDataPath() {
+                return mPath;
+            }
+
+            public Bitmap fullSizeBitmap(int targetWidthOrHeight) {
+                return BitmapFactory.decodeFile(mPath);
+            }
+
+            public IGetBitmap_cancelable fullSizeBitmap_cancelable(int targetWidthOrHeight) {
+                return null;
+            }
+
+            public Bitmap thumbBitmap() {
+                Bitmap b = fullSizeBitmap(320);
+                Matrix m = new Matrix();
+                float scale = 320F / (float) b.getWidth();
+                m.setScale(scale, scale);
+                Bitmap scaledBitmap = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, true);
+                return scaledBitmap;
+            }
+
+            public Bitmap miniThumbBitmap() {
+                return thumbBitmap();
+            }
+
+            public long fullSizeImageId() {
+                return mId;
+            }
+        }
+
+        private void enumerate(String path, ArrayList<String> list) {
+            File f = new File(path);
+            if (f.isDirectory()) {
+                String [] children = f.list();
+                if (children != null) {
+                    for (int i = 0; i < children.length; i++) {
+                        if (children[i].charAt(0) != '.')
+                            enumerate(path + "/" + children[i], list);
+                    }
+                }
+            } else {
+                if (path.endsWith(".jpeg") || path.endsWith(".jpg") || path.endsWith(".png")) {
+                    if (f.length() > 0) {
+                        list.add(path);
+                    }
+                }
+            }
+        }
+
+        public FileImageList() {
+            ArrayList<String> list = new ArrayList<String>();
+            enumerate(Environment.getExternalStorageDirectory().getPath(), list);
+            enumerate("/data/images", list);
+
+            for (int i = 0; i < list.size(); i++) {
+                FileImage img = new FileImage(i, list.get(i));
+                mCache.put((long)i, img);
+                mImages.add(img);
+            }
+        }
+
+        public IImage getImageAt(int i) {
+            if (i >= mImages.size())
+                return null;
+
+            return mImages.get(i);
+        }
+
+        public IImage getImageForUri(Uri uri) {
+            // TODO make this a hash lookup
+            int count = getCount();
+            for (int i = 0; i < count; i++) {
+                IImage image = getImageAt(i);
+                if (image.fullSizeImageUri().equals(uri)) {
+                    return image;
+                }
+            }
+            return null;
+        }
+
+        public IImage getImageWithId(long id) {
+            throw new UnsupportedOperationException();
+        }
+
+        public void removeImageAt(int i) {
+            throw new UnsupportedOperationException();
+        }
+
+        public boolean removeImage(IImage image) {
+            throw new UnsupportedOperationException();
+        }
+
+        public int getCount() {
+            return mImages.size();
+        }
+
+        public boolean isEmpty() {
+            return mImages.isEmpty();
+        }
+
+        public void deactivate() {
+            // nothing to do here
+        }
+    }
+
+}
diff --git a/src/com/android/camera/VideoCamera.java b/src/com/android/camera/VideoCamera.java
new file mode 100644
index 0000000..37e1dcb
--- /dev/null
+++ b/src/com/android/camera/VideoCamera.java
@@ -0,0 +1,1040 @@
+/*
+ * Copyright (C) 2007 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.camera;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.location.LocationManager;
+import android.media.MediaMetadataRetriever;
+import android.media.MediaRecorder;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.os.StatFs;
+import android.os.SystemClock;
+import android.preference.PreferenceManager;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Video;
+import android.text.format.DateFormat;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.MenuItem.OnMenuItemClickListener;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+public class VideoCamera extends Activity implements View.OnClickListener,
+    ShutterButton.OnShutterButtonListener, SurfaceHolder.Callback, MediaRecorder.OnErrorListener {
+
+    private static final String TAG = "videocamera";
+
+    private static final boolean DEBUG = true;
+    private static final boolean DEBUG_SUPPRESS_AUDIO_RECORDING = DEBUG && false;
+    private static final boolean DEBUG_DO_NOT_REUSE_MEDIA_RECORDER = DEBUG && true;
+    private static final boolean DEBUG_LOG_APP_LIFECYCLE = DEBUG && false;
+
+    private static final int CLEAR_SCREEN_DELAY = 4;
+    private static final int UPDATE_RECORD_TIME = 5;
+
+    private static final int SCREEN_DELAY = 2 * 60 * 1000;
+
+    private static final long NO_STORAGE_ERROR = -1L;
+    private static final long CANNOT_STAT_ERROR = -2L;
+    private static final long LOW_STORAGE_THRESHOLD = 512L * 1024L;
+
+    public static final int MENU_SETTINGS = 6;
+    public static final int MENU_GALLERY_PHOTOS = 7;
+    public static final int MENU_GALLERY_VIDEOS = 8;
+    public static final int MENU_SAVE_GALLERY_PHOTO = 34;
+    public static final int MENU_SAVE_PLAY_VIDEO = 35;
+    public static final int MENU_SAVE_SELECT_VIDEO = 36;
+    public static final int MENU_SAVE_NEW_VIDEO = 37;
+
+    SharedPreferences mPreferences;
+
+    private static final float VIDEO_ASPECT_RATIO = 176.0f / 144.0f;
+    VideoPreview mVideoPreview;
+    SurfaceHolder mSurfaceHolder = null;
+    ImageView mBlackout = null;
+    ImageView mVideoFrame;
+    Bitmap mVideoFrameBitmap;
+
+    private MediaRecorder mMediaRecorder;
+    private boolean mMediaRecorderRecording = false;
+    private long mRecordingStartTime;
+    // The video file that the hardware camera is about to record into
+    // (or is recording into.)
+    private String mCameraVideoFilename;
+    private FileDescriptor mCameraVideoFileDescriptor;
+
+    // The video file that has already been recorded, and that is being
+    // examined by the user.
+    private String mCurrentVideoFilename;
+    private Uri mCurrentVideoUri;
+    private ContentValues mCurrentVideoValues;
+
+    boolean mPausing = false;
+
+    static ContentResolver mContentResolver;
+    boolean mDidRegister = false;
+
+    int mCurrentZoomIndex = 0;
+
+    private ShutterButton mShutterButton;
+    private TextView mRecordingTimeView;
+    private boolean mHasSdCard;
+
+    ArrayList<MenuItem> mGalleryItems = new ArrayList<MenuItem>();
+
+    View mPostPictureAlert;
+    LocationManager mLocationManager = null;
+
+    private Handler mHandler = new MainHandler();
+
+    /** This Handler is used to post message back onto the main thread of the application */
+    private class MainHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+
+                case CLEAR_SCREEN_DELAY: {
+                    clearScreenOnFlag();
+                    break;
+                }
+
+                case UPDATE_RECORD_TIME: {
+                    if (mMediaRecorderRecording) {
+                        long now = SystemClock.uptimeMillis();
+                        long delta = now - mRecordingStartTime;
+                        long seconds = delta / 1000;
+                        long minutes = seconds / 60;
+                        long hours = minutes / 60;
+                        long remainderMinutes = minutes - (hours * 60);
+                        long remainderSeconds = seconds - (minutes * 60);
+
+                        String secondsString = Long.toString(remainderSeconds);
+                        if (secondsString.length() < 2) {
+                            secondsString = "0" + secondsString;
+                        }
+                        String minutesString = Long.toString(remainderMinutes);
+                        if (minutesString.length() < 2) {
+                            minutesString = "0" + minutesString;
+                        }
+                        String text = minutesString + ":" + secondsString;
+                        if (hours > 0) {
+                            String hoursString = Long.toString(hours);
+                            if (hoursString.length() < 2) {
+                                hoursString = "0" + hoursString;
+                            }
+                            text = hoursString + ":" + text;
+                        }
+                        mRecordingTimeView.setText(text);
+                        // Work around a limitation of the T-Mobile G1: The T-Mobile
+                        // hardware blitter can't pixel-accurately scale and clip at the same time,
+                        // and the SurfaceFlinger doesn't attempt to work around this limitation.
+                        // In order to avoid visual corruption we must manually refresh the entire
+                        // surface view when changing any overlapping view's contents.
+                        mVideoPreview.invalidate();
+                        mHandler.sendEmptyMessageDelayed(UPDATE_RECORD_TIME, 1000);
+                    }
+                    break;
+                }
+
+                default:
+                    Log.v(TAG, "Unhandled message: " + msg.what);
+                  break;
+            }
+        }
+    };
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
+                mHasSdCard = false;
+                stopVideoRecording();
+                initializeVideo();
+            } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
+                // SD card available
+                // TODO put up a "please wait" message
+                // TODO also listen for the media scanner finished message
+                updateStorageHint();
+                mHasSdCard = true;
+                initializeVideo();
+            } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
+                // SD card unavailable
+                updateStorageHint();
+                mHasSdCard = false;
+                releaseMediaRecorder();
+            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
+                Toast.makeText(VideoCamera.this, getResources().getString(R.string.wait), 5000);
+            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
+                updateStorageHint();
+            }
+        }
+    };
+
+    static private String createName(long dateTaken) {
+        return DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken).toString();
+    }
+
+    /** Called with the activity is first created. */
+    @Override
+    public void onCreate(Bundle icicle) {
+        if (DEBUG_LOG_APP_LIFECYCLE) {
+            Log.v(TAG, "onCreate " + this.hashCode());
+        }
+        super.onCreate(icicle);
+
+        mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
+
+        mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+        mContentResolver = getContentResolver();
+
+        //setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT);
+        requestWindowFeature(Window.FEATURE_PROGRESS);
+        setContentView(R.layout.video_camera);
+
+        mVideoPreview = (VideoPreview) findViewById(R.id.camera_preview);
+        mVideoPreview.setAspectRatio(VIDEO_ASPECT_RATIO);
+
+        // don't set mSurfaceHolder here. We have it set ONLY within
+        // surfaceCreated / surfaceDestroyed, other parts of the code
+        // assume that when it is set, the surface is also set.
+        SurfaceHolder holder = mVideoPreview.getHolder();
+        holder.addCallback(this);
+        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+
+        mBlackout = (ImageView) findViewById(R.id.blackout);
+        mBlackout.setBackgroundDrawable(new ColorDrawable(0xFF000000));
+
+        mPostPictureAlert = findViewById(R.id.post_picture_panel);
+
+        int[] ids = new int[]{R.id.play, R.id.share, R.id.discard,
+                R.id.cancel, R.id.attach};
+        for (int id : ids) {
+            findViewById(id).setOnClickListener(this);
+        }
+
+        mShutterButton = (ShutterButton) findViewById(R.id.shutter_button);
+        mShutterButton.setOnShutterButtonListener(this);
+        mRecordingTimeView = (TextView) findViewById(R.id.recording_time);
+        mVideoFrame = (ImageView) findViewById(R.id.video_frame);
+    }
+
+    @Override
+    public void onStart() {
+        if (DEBUG_LOG_APP_LIFECYCLE) {
+            Log.v(TAG, "onStart " + this.hashCode());
+        }
+        super.onStart();
+
+        Thread t = new Thread(new Runnable() {
+            public void run() {
+                final boolean storageOK = getAvailableStorage() >= LOW_STORAGE_THRESHOLD;
+
+                if (!storageOK) {
+                    mHandler.post(new Runnable() {
+                        public void run() {
+                            updateStorageHint();
+                        }
+                    });
+                }
+            }
+        });
+        t.start();
+    }
+
+    public void onClick(View v) {
+        switch (v.getId()) {
+
+            case R.id.gallery:
+                MenuHelper.gotoCameraVideoGallery(this);
+                break;
+
+            case R.id.attach:
+                doReturnToCaller(true);
+                break;
+
+            case R.id.cancel:
+                doReturnToCaller(false);
+                break;
+
+            case R.id.discard: {
+                discardCurrentVideoAndStartPreview();
+                break;
+            }
+
+            case R.id.share: {
+                Intent intent = new Intent();
+                intent.setAction(Intent.ACTION_SEND);
+                intent.setType("video/3gpp");
+                intent.putExtra(Intent.EXTRA_STREAM, mCurrentVideoUri);
+                try {
+                    startActivity(Intent.createChooser(intent, getText(R.string.sendVideo)));
+                } catch (android.content.ActivityNotFoundException ex) {
+                    Toast.makeText(VideoCamera.this, R.string.no_way_to_share_video, Toast.LENGTH_SHORT).show();
+                }
+
+                break;
+            }
+
+            case R.id.play: {
+                doPlayCurrentVideo();
+                break;
+            }
+        }
+    }
+
+    public void onShutterButtonFocus(ShutterButton button, boolean pressed) {
+        switch (button.getId()) {
+            case R.id.shutter_button:
+                if (pressed) {
+                    if (mMediaRecorderRecording) {
+                        stopVideoRecordingAndDisplayDialog();
+                    } else if (mVideoFrame.getVisibility() == View.VISIBLE) {
+                        doStartCaptureMode();
+                    } else {
+                        startVideoRecording();
+                    }
+                }
+                break;
+        }
+    }
+
+    public void onShutterButtonClick(ShutterButton button) {
+        // Do nothing (everything happens in onShutterButtonFocus).
+    }
+
+    private void doStartCaptureMode() {
+        if (isVideoCaptureIntent()) {
+            discardCurrentVideoAndStartPreview();
+        } else {
+            hideVideoFrameAndStartPreview();
+        }
+    }
+
+    private void doPlayCurrentVideo() {
+        Log.e(TAG, "Playing current video: " + mCurrentVideoUri);
+        Intent intent = new Intent(Intent.ACTION_VIEW, mCurrentVideoUri);
+        try {
+            startActivity(intent);
+        } catch (android.content.ActivityNotFoundException ex) {
+            Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
+        }
+    }
+
+    private void discardCurrentVideoAndStartPreview() {
+        deleteCurrentVideo();
+        hideVideoFrameAndStartPreview();
+    }
+
+    private OnScreenHint mStorageHint;
+
+    private void updateStorageHint() {
+        long remaining = getAvailableStorage();
+        String errorMessage = null;
+        if (remaining == NO_STORAGE_ERROR) {
+            errorMessage = getString(R.string.no_storage);
+        } else if (remaining < LOW_STORAGE_THRESHOLD) {
+            errorMessage = getString(R.string.spaceIsLow_content);
+            if (mStorageHint != null) {
+                mStorageHint.cancel();
+                mStorageHint = null;
+            }
+        }
+        if (errorMessage != null) {
+            if (mStorageHint == null) {
+                mStorageHint = OnScreenHint.makeText(this, errorMessage);
+            } else {
+                mStorageHint.setText(errorMessage);
+            }
+            mStorageHint.show();
+        } else if (mStorageHint != null) {
+            mStorageHint.cancel();
+            mStorageHint = null;
+        }
+    }
+
+    @Override
+    public void onResume() {
+        if (DEBUG_LOG_APP_LIFECYCLE) {
+            Log.v(TAG, "onResume " + this.hashCode());
+        }
+        super.onResume();
+
+        setScreenTimeoutLong();
+
+        mPausing = false;
+
+        // install an intent filter to receive SD card related events.
+        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_EJECT);
+        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
+        intentFilter.addDataScheme("file");
+        registerReceiver(mReceiver, intentFilter);
+        mDidRegister = true;
+        mHasSdCard = ImageManager.hasStorage();
+
+        mBlackout.setVisibility(View.INVISIBLE);
+        if (mVideoFrameBitmap == null) {
+            initializeVideo();
+        } else {
+            showPostRecordingAlert();
+        }
+    }
+
+    @Override
+    public void onStop() {
+        if (DEBUG_LOG_APP_LIFECYCLE) {
+            Log.v(TAG, "onStop " + this.hashCode());
+        }
+        stopVideoRecording();
+        setScreenTimeoutSystemDefault();
+        super.onStop();
+    }
+
+    @Override
+    protected void onPause() {
+        if (DEBUG_LOG_APP_LIFECYCLE) {
+            Log.v(TAG, "onPause " + this.hashCode());
+        }
+        super.onPause();
+
+        stopVideoRecording();
+        hidePostPictureAlert();
+
+        mPausing = true;
+
+        if (mDidRegister) {
+            unregisterReceiver(mReceiver);
+            mDidRegister = false;
+        }
+        mBlackout.setVisibility(View.VISIBLE);
+        setScreenTimeoutSystemDefault();
+
+        if (mStorageHint != null) {
+            mStorageHint.cancel();
+            mStorageHint = null;
+        }
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        setScreenTimeoutLong();
+
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_BACK:
+                if (mMediaRecorderRecording) {
+                    Log.v(TAG, "onKeyBack");
+                    stopVideoRecordingAndDisplayDialog();
+                    return true;
+                } else if(isPostRecordingAlertVisible()) {
+                    hideVideoFrameAndStartPreview();
+                    return true;
+                }
+                break;
+            case KeyEvent.KEYCODE_CAMERA:
+                if (event.getRepeatCount() == 0) {
+                    // If we get a dpad center event without any focused view, move the
+                    // focus to the shutter button and press it.
+                    if (mShutterButton.isInTouchMode()) {
+                        mShutterButton.requestFocusFromTouch();
+                    } else {
+                        mShutterButton.requestFocus();
+                    }
+                    mShutterButton.setPressed(true);
+                    return true;
+                }
+                return true;
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+                if (event.getRepeatCount() == 0) {
+                    // If we get a dpad center event without any focused view, move the
+                    // focus to the shutter button and press it.
+                    if (mShutterButton.isInTouchMode()) {
+                        mShutterButton.requestFocusFromTouch();
+                    } else {
+                        mShutterButton.requestFocus();
+                    }
+                    mShutterButton.setPressed(true);
+                }
+                break;
+            case KeyEvent.KEYCODE_MENU:
+                if (mMediaRecorderRecording) {
+                    stopVideoRecordingAndDisplayDialog();
+                    return true;
+                }
+                break;
+        }
+
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        switch(keyCode) {
+        case KeyEvent.KEYCODE_CAMERA:
+            mShutterButton.setPressed(false);
+            return true;
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
+        stopVideoRecording();
+        initializeVideo();
+    }
+
+    public void surfaceCreated(SurfaceHolder holder) {
+        mSurfaceHolder = holder;
+    }
+
+    public void surfaceDestroyed(SurfaceHolder holder) {
+        mSurfaceHolder = null;
+    }
+
+    void gotoGallery() {
+        MenuHelper.gotoCameraVideoGallery(this);
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        super.onPrepareOptionsMenu(menu);
+
+        for (int i = 1; i <= MenuHelper.MENU_ITEM_MAX; i++) {
+            if (i != MenuHelper.GENERIC_ITEM) {
+                menu.setGroupVisible(i, false);
+            }
+        }
+
+        menu.setGroupVisible(MenuHelper.VIDEO_MODE_ITEM, true);
+        return true;
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+
+        if (isVideoCaptureIntent()) {
+            // No options menu for attach mode.
+            return false;
+        } else {
+            addBaseMenuItems(menu);
+            MenuHelper.addImageMenuItems(
+                    menu,
+                    MenuHelper.INCLUDE_ALL & ~MenuHelper.INCLUDE_ROTATE_MENU,
+                    false,
+                    VideoCamera.this,
+                    mHandler,
+
+                    // Handler for deletion
+                    new Runnable() {
+                        public void run() {
+                            // What do we do here?
+                            // mContentResolver.delete(uri, null, null);
+                        }
+                    },
+                    new MenuHelper.MenuInvoker() {
+                        public void run(final MenuHelper.MenuCallback cb) {
+                        }
+                    });
+
+            MenuItem gallery = menu.add(MenuHelper.IMAGE_SAVING_ITEM, MENU_SAVE_GALLERY_PHOTO, 0,
+                    R.string.camera_gallery_photos_text).setOnMenuItemClickListener(
+                            new MenuItem.OnMenuItemClickListener() {
+                public boolean onMenuItemClick(MenuItem item) {
+                    gotoGallery();
+                    return true;
+                }
+            });
+            gallery.setIcon(android.R.drawable.ic_menu_gallery);
+        }
+        return true;
+    }
+
+    private boolean isVideoCaptureIntent() {
+        String action = getIntent().getAction();
+        return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
+    }
+
+    private void doReturnToCaller(boolean success) {
+        Intent resultIntent = new Intent();
+        int resultCode;
+        if (success) {
+            resultCode = RESULT_OK;
+            resultIntent.setData(mCurrentVideoUri);
+        } else {
+            resultCode = RESULT_CANCELED;
+        }
+        setResult(resultCode, resultIntent);
+        finish();
+    }
+
+    /**
+     * Returns
+     * @return number of bytes available, or an ERROR code.
+     */
+    private static long getAvailableStorage() {
+        try {
+            if (!ImageManager.hasStorage()) {
+                return NO_STORAGE_ERROR;
+            } else {
+                String storageDirectory = Environment.getExternalStorageDirectory().toString();
+                StatFs stat = new StatFs(storageDirectory);
+                return ((long)stat.getAvailableBlocks() * (long)stat.getBlockSize());
+            }
+        } catch (Exception ex) {
+            // if we can't stat the filesystem then we don't know how many
+            // free bytes exist.  It might be zero but just leave it
+            // blank since we really don't know.
+            return CANNOT_STAT_ERROR;
+        }
+    }
+
+    private void cleanupEmptyFile() {
+        if (mCameraVideoFilename != null) {
+            File f = new File(mCameraVideoFilename);
+            if (f.length() == 0 && f.delete()) {
+              Log.v(TAG, "Empty video file deleted: " + mCameraVideoFilename);
+              mCameraVideoFilename = null;
+            }
+        }
+    }
+
+    // Returns false if initializeVideo fails
+    private boolean initializeVideo() {
+        Log.v(TAG, "initializeVideo");
+        boolean isCaptureIntent = isVideoCaptureIntent();
+        Intent intent = getIntent();
+        Bundle myExtras = intent.getExtras();
+
+        if (isCaptureIntent && myExtras != null) {
+            Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
+            if (saveUri != null) {
+                try {
+                    mCameraVideoFileDescriptor = mContentResolver.
+                        openFileDescriptor(saveUri, "rw").getFileDescriptor();
+                    mCurrentVideoUri = saveUri;
+                }
+                catch (java.io.FileNotFoundException ex) {
+                    // invalid uri
+                    Log.e(TAG, ex.toString());
+                }
+            }
+        }
+        releaseMediaRecorder();
+
+        if (mSurfaceHolder == null) {
+            Log.v(TAG, "SurfaceHolder is null");
+            return false;
+        }
+
+        mMediaRecorder = new MediaRecorder();
+
+        if (DEBUG_SUPPRESS_AUDIO_RECORDING) {
+            Log.v(TAG, "DEBUG_SUPPRESS_AUDIO_RECORDING is true.");
+        } else {
+            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+        }
+        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
+        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+
+        if (!mHasSdCard) {
+            mMediaRecorder.setOutputFile("/dev/null");
+        } else {
+            // We try Uri in intent first. If it doesn't work, use our own instead.
+            if (mCameraVideoFileDescriptor != null) {
+                mMediaRecorder.setOutputFile(mCameraVideoFileDescriptor);
+            } else {
+                createVideoPath();
+                mMediaRecorder.setOutputFile(mCameraVideoFilename);
+            }
+        }
+
+        boolean videoQualityHigh = getBooleanPreference(CameraSettings.KEY_VIDEO_QUALITY,
+                CameraSettings.DEFAULT_VIDEO_QUALITY_VALUE);
+
+        if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
+            int extraVideoQuality = intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
+            videoQualityHigh = (extraVideoQuality > 0);
+        }
+
+        // Use the same frame rate for both, since internally
+        // if the frame rate is too large, it can cause camera to become
+        // unstable. We need to fix the MediaRecorder to disable the support
+        // of setting frame rate for now.
+        mMediaRecorder.setVideoFrameRate(20);
+        if (videoQualityHigh) {
+            mMediaRecorder.setVideoSize(352,288);
+        } else {
+            mMediaRecorder.setVideoSize(176,144);
+        }
+        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H263);
+        if (!DEBUG_SUPPRESS_AUDIO_RECORDING) {
+            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
+        }
+        mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
+        try {
+            mMediaRecorder.prepare();
+        } catch (IOException exception) {
+            Log.e(TAG, "prepare failed for " + mCameraVideoFilename);
+            releaseMediaRecorder();
+            // TODO: add more exception handling logic here
+            return false;
+        }
+        mMediaRecorderRecording = false;
+        return true;
+    }
+
+    private void releaseMediaRecorder() {
+        Log.v(TAG, "Releasing media recorder.");
+        if (mMediaRecorder != null) {
+            cleanupEmptyFile();
+            mMediaRecorder.reset();
+            mMediaRecorder.release();
+            mMediaRecorder = null;
+        }
+    }
+
+    private void restartPreview() {
+        if (DEBUG_DO_NOT_REUSE_MEDIA_RECORDER) {
+            Log.v(TAG, "DEBUG_DO_NOT_REUSE_MEDIA_RECORDER recreating mMediaRecorder.");
+            initializeVideo();
+        } else {
+            try {
+                mMediaRecorder.prepare();
+            } catch (IOException exception) {
+                Log.e(TAG, "prepare failed for " + mCameraVideoFilename);
+                releaseMediaRecorder();
+                // TODO: add more exception handling logic here
+            }
+        }
+    }
+
+    private int getIntPreference(String key, int defaultValue) {
+        String s = mPreferences.getString(key, "");
+        int result = defaultValue;
+        try {
+            result = Integer.parseInt(s);
+        } catch (NumberFormatException e) {
+            // Ignore, result is already the default value.
+        }
+        return result;
+    }
+
+    private boolean getBooleanPreference(String key, boolean defaultValue) {
+        return getIntPreference(key, defaultValue ? 1 : 0) != 0;
+    }
+
+    private void createVideoPath() {
+        long dateTaken = System.currentTimeMillis();
+        String title = createName(dateTaken);
+        String displayName = title + ".3gp"; // Used when emailing.
+        String cameraDirPath = ImageManager.CAMERA_IMAGE_BUCKET_NAME;
+        File cameraDir = new File(cameraDirPath);
+        cameraDir.mkdirs();
+        SimpleDateFormat dateFormat = new SimpleDateFormat(
+                getString(R.string.video_file_name_format));
+        Date date = new Date(dateTaken);
+        String filepart = dateFormat.format(date);
+        String filename = cameraDirPath + "/" + filepart + ".3gp";
+        ContentValues values = new ContentValues(7);
+        values.put(Video.Media.TITLE, title);
+        values.put(Video.Media.DISPLAY_NAME, displayName);
+        values.put(Video.Media.DESCRIPTION, "");
+        values.put(Video.Media.DATE_TAKEN, dateTaken);
+        values.put(Video.Media.MIME_TYPE, "video/3gpp");
+        values.put(Video.Media.DATA, filename);
+        mCameraVideoFilename = filename;
+        Log.v(TAG, "Current camera video filename: " + mCameraVideoFilename);
+        mCurrentVideoValues = values;
+    }
+
+    private void registerVideo() {
+        if (mCameraVideoFileDescriptor == null) {
+            Uri videoTable = Uri.parse("content://media/external/video/media");
+            mCurrentVideoUri = mContentResolver.insert(videoTable,
+                    mCurrentVideoValues);
+            Log.v(TAG, "Current video URI: " + mCurrentVideoUri);
+        }
+        mCurrentVideoValues = null;
+    }
+
+    private void deleteCurrentVideo() {
+        if (mCurrentVideoFilename != null) {
+            deleteVideoFile(mCurrentVideoFilename);
+            mCurrentVideoFilename = null;
+        }
+        if (mCurrentVideoUri != null) {
+            mContentResolver.delete(mCurrentVideoUri, null, null);
+            mCurrentVideoUri = null;
+        }
+    }
+
+    private void deleteVideoFile(String fileName) {
+        Log.v(TAG, "Deleting video " + fileName);
+        File f = new File(fileName);
+        if (! f.delete()) {
+            Log.v(TAG, "Could not delete " + fileName);
+        }
+    }
+
+    private void addBaseMenuItems(Menu menu) {
+        MenuHelper.addSwitchModeMenuItem(menu, this, false);
+        {
+            MenuItem gallery = menu.add(MenuHelper.IMAGE_MODE_ITEM, MENU_GALLERY_PHOTOS, 0, R.string.camera_gallery_photos_text).setOnMenuItemClickListener(new OnMenuItemClickListener() {
+                public boolean onMenuItemClick(MenuItem item) {
+                    gotoGallery();
+                    return true;
+                }
+            });
+            gallery.setIcon(android.R.drawable.ic_menu_gallery);
+            mGalleryItems.add(gallery);
+        }
+        {
+            MenuItem gallery = menu.add(MenuHelper.VIDEO_MODE_ITEM, MENU_GALLERY_VIDEOS, 0, R.string.camera_gallery_photos_text).setOnMenuItemClickListener(new OnMenuItemClickListener() {
+                public boolean onMenuItemClick(MenuItem item) {
+                    gotoGallery();
+                    return true;
+                }
+            });
+            gallery.setIcon(android.R.drawable.ic_menu_gallery);
+            mGalleryItems.add(gallery);
+        }
+
+        MenuItem item = menu.add(MenuHelper.GENERIC_ITEM, MENU_SETTINGS, 0, R.string.settings).setOnMenuItemClickListener(new OnMenuItemClickListener() {
+            public boolean onMenuItemClick(MenuItem item) {
+                Intent intent = new Intent();
+                intent.setClass(VideoCamera.this, CameraSettings.class);
+                startActivity(intent);
+                return true;
+            }
+        });
+        item.setIcon(android.R.drawable.ic_menu_preferences);
+    }
+
+    // from MediaRecorder.OnErrorListener
+    public void onError(MediaRecorder mr, int what, int extra) {
+        if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
+            // We may have run out of space on the sdcard.
+            stopVideoRecording();
+            updateStorageHint();
+        }
+    }
+
+    private void startVideoRecording() {
+        Log.v(TAG, "startVideoRecording");
+        if (!mMediaRecorderRecording) {
+
+            if (mStorageHint != null) {
+                Log.v(TAG, "Storage issue, ignore the start request");
+                return;
+            }
+
+            // Check mMediaRecorder to see whether it is initialized or not.
+            if (mMediaRecorder == null && initializeVideo() == false ) {
+                Log.e(TAG, "Initialize video (MediaRecorder) failed.");
+                return;
+            }
+
+            try {
+                mMediaRecorder.setOnErrorListener(this);
+                mMediaRecorder.start();   // Recording is now started
+            } catch (RuntimeException e) {
+                Log.e(TAG, "Could not start media recorder. ", e);
+                return;
+            }
+            mMediaRecorderRecording = true;
+            mRecordingStartTime = SystemClock.uptimeMillis();
+            updateRecordingIndicator(true);
+            mRecordingTimeView.setText("");
+            mRecordingTimeView.setVisibility(View.VISIBLE);
+            mHandler.sendEmptyMessage(UPDATE_RECORD_TIME);
+            setScreenTimeoutInfinite();
+        }
+    }
+
+    private void updateRecordingIndicator(boolean showRecording) {
+        int drawableId = showRecording ? R.drawable.ic_camera_bar_indicator_record
+            : R.drawable.ic_camera_indicator_video;
+        Drawable drawable = getResources().getDrawable(drawableId);
+        mShutterButton.setImageDrawable(drawable);
+    }
+
+    private void stopVideoRecordingAndDisplayDialog() {
+        Log.v(TAG, "stopVideoRecordingAndDisplayDialog");
+        if (mMediaRecorderRecording) {
+            stopVideoRecording();
+            acquireAndShowVideoFrame();
+            showPostRecordingAlert();
+        }
+    }
+
+    private void showPostRecordingAlert() {
+        int[] pickIds = {R.id.attach, R.id.cancel};
+        int[] normalIds = {R.id.gallery, R.id.share, R.id.discard};
+        int[] alwaysOnIds = {R.id.play};
+        int[] hideIds = pickIds;
+        int[] connectIds = normalIds;
+        if (isVideoCaptureIntent()) {
+            hideIds = normalIds;
+            connectIds = pickIds;
+        }
+        for(int id : hideIds) {
+            mPostPictureAlert.findViewById(id).setVisibility(View.GONE);
+        }
+        connectAndFadeIn(connectIds);
+        connectAndFadeIn(alwaysOnIds);
+        mPostPictureAlert.setVisibility(View.VISIBLE);
+    }
+
+    private void connectAndFadeIn(int[] connectIds) {
+        for(int id : connectIds) {
+            View view = mPostPictureAlert.findViewById(id);
+            view.setOnClickListener(this);
+            Animation animation = new AlphaAnimation(0F, 1F);
+            animation.setDuration(500);
+            view.setAnimation(animation);
+        }
+    }
+
+    private void hidePostPictureAlert() {
+        mPostPictureAlert.setVisibility(View.INVISIBLE);
+    }
+
+    private boolean isPostRecordingAlertVisible() {
+        return mPostPictureAlert.getVisibility() == View.VISIBLE;
+    }
+
+    private void stopVideoRecording() {
+        Log.v(TAG, "stopVideoRecording");
+        boolean needToRegisterRecording = false;
+        if (mMediaRecorderRecording || mMediaRecorder != null) {
+            if (mMediaRecorderRecording && mMediaRecorder != null) {
+                try {
+                    mMediaRecorder.setOnErrorListener(null);
+                    mMediaRecorder.stop();
+                } catch (RuntimeException e) {
+                    Log.e(TAG, "stop fail: " + e.getMessage());
+                }
+                mCurrentVideoFilename = mCameraVideoFilename;
+                Log.v(TAG, "Setting current video filename: " + mCurrentVideoFilename);
+                needToRegisterRecording = true;
+                mMediaRecorderRecording = false;
+            }
+            releaseMediaRecorder();
+            updateRecordingIndicator(false);
+            mRecordingTimeView.setVisibility(View.GONE);
+            setScreenTimeoutLong();
+        }
+        if (needToRegisterRecording && mHasSdCard) registerVideo();
+
+        mCameraVideoFilename = null;
+        mCameraVideoFileDescriptor = null;
+    }
+
+    private void setScreenTimeoutSystemDefault() {
+        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
+        clearScreenOnFlag();
+    }
+
+    private void setScreenTimeoutLong() {
+        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
+        setScreenOnFlag();
+        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
+    }
+
+    private void setScreenTimeoutInfinite() {
+        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
+        setScreenOnFlag();
+    }
+
+    private void clearScreenOnFlag() {
+        Window w = getWindow();
+        final int keepScreenOnFlag = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
+        if ((w.getAttributes().flags & keepScreenOnFlag) != 0) {
+            w.clearFlags(keepScreenOnFlag);
+        }
+    }
+
+    private void setScreenOnFlag() {
+        Window w = getWindow();
+        final int keepScreenOnFlag = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
+        if ((w.getAttributes().flags & keepScreenOnFlag) == 0) {
+            w.addFlags(keepScreenOnFlag);
+        }
+    }
+
+    private void hideVideoFrameAndStartPreview() {
+        hidePostPictureAlert();
+        hideVideoFrame();
+        restartPreview();
+    }
+
+    private void acquireAndShowVideoFrame() {
+        recycleVideoFrameBitmap();
+        mVideoFrameBitmap = ImageManager.createVideoThumbnail(mCurrentVideoFilename);
+        mVideoFrame.setImageBitmap(mVideoFrameBitmap);
+        mVideoFrame.setVisibility(View.VISIBLE);
+    }
+
+    private void hideVideoFrame() {
+        recycleVideoFrameBitmap();
+        mVideoFrame.setVisibility(View.GONE);
+    }
+
+    private void recycleVideoFrameBitmap() {
+        if (mVideoFrameBitmap != null) {
+            mVideoFrame.setImageDrawable(null);
+            mVideoFrameBitmap.recycle();
+            mVideoFrameBitmap = null;
+        }
+    }
+}
+
diff --git a/src/com/android/camera/VideoPreview.java b/src/com/android/camera/VideoPreview.java
new file mode 100644
index 0000000..aed1e89
--- /dev/null
+++ b/src/com/android/camera/VideoPreview.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2007 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.camera;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.SurfaceView;
+import android.view.View.MeasureSpec;
+
+class VideoPreview extends SurfaceView {
+    private float mAspectRatio;
+    private int mHorizontalTileSize = 1;
+    private int mVerticalTileSize = 1;
+
+    /**
+     * Setting the aspect ratio to this value means to not enforce an aspect ratio.
+     */
+    public static float DONT_CARE = 0.0f;
+
+    public VideoPreview(Context context) {
+        super(context);
+    }
+
+    public VideoPreview(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public VideoPreview(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public void setTileSize(int horizontalTileSize, int verticalTileSize) {
+        if ((mHorizontalTileSize != horizontalTileSize)
+                || (mVerticalTileSize != verticalTileSize)) {
+            mHorizontalTileSize = horizontalTileSize;
+            mVerticalTileSize = verticalTileSize;
+            requestLayout();
+            invalidate();
+        }
+    }
+
+    public void setAspectRatio(int width, int height) {
+        setAspectRatio(((float) width) / ((float) height));
+    }
+
+    public void setAspectRatio(float aspectRatio) {
+        if (mAspectRatio != aspectRatio) {
+            mAspectRatio = aspectRatio;
+            requestLayout();
+            invalidate();
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (mAspectRatio != DONT_CARE) {
+            int widthSpecSize =  MeasureSpec.getSize(widthMeasureSpec);
+            int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
+
+            int width = widthSpecSize;
+            int height = heightSpecSize;
+
+            if (width > 0 && height > 0) {
+                float defaultRatio = ((float) width) / ((float) height);
+                if (defaultRatio < mAspectRatio) {
+                    // Need to reduce height
+                    height = (int) (width / mAspectRatio);
+                } else if (defaultRatio > mAspectRatio) {
+                    width = (int) (height * mAspectRatio);
+                }
+                width = roundUpToTile(width, mHorizontalTileSize, widthSpecSize);
+                height = roundUpToTile(height, mVerticalTileSize, heightSpecSize);
+                Log.i("VideoPreview", "ar " + mAspectRatio + " setting size: " + width + 'x' + height);
+                setMeasuredDimension(width, height);
+                return;
+            }
+        }
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    private int roundUpToTile(int dimension, int tileSize, int maxDimension) {
+        return Math.min(((dimension + tileSize - 1) / tileSize) * tileSize, maxDimension);
+    }
+}
diff --git a/src/com/android/camera/ViewImage.java b/src/com/android/camera/ViewImage.java
new file mode 100644
index 0000000..07d396f
--- /dev/null
+++ b/src/com/android/camera/ViewImage.java
@@ -0,0 +1,1677 @@
+/*
+ * Copyright (C) 2007 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.camera;
+
+import java.util.Random;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.PreferenceManager;
+import android.provider.MediaStore;
+import android.util.AttributeSet;
+import android.util.Config;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.LinearLayout;
+import android.widget.Scroller;
+import android.widget.Toast;
+import android.widget.ZoomButtonsController;
+import android.widget.ZoomRingController;
+
+import com.android.camera.ImageManager.IImage;
+
+public class ViewImage extends Activity implements View.OnClickListener
+{
+    static final String TAG = "ViewImage";
+    private ImageGetter mGetter;
+
+    static final boolean sSlideShowHidesStatusBar = true;
+
+    // Choices for what adjacents to load.
+    static private final int[] sOrder_adjacents = new int[] { 0, 1, -1 };
+    static private final int[] sOrder_slideshow = new int[] { 0 };
+
+    Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+        }
+    };
+
+    private Random mRandom = new Random(System.currentTimeMillis());
+    private int [] mShuffleOrder;
+    private boolean mUseShuffleOrder = false;
+    private boolean mSlideShowLoop = false;
+
+    private static final int MODE_NORMAL = 1;
+    private static final int MODE_SLIDESHOW = 2;
+    private int mMode = MODE_NORMAL;
+    private boolean mFullScreenInNormalMode;
+    private boolean mShowActionIcons;
+    private View mActionIconPanel;
+    private View mShutterButton;
+
+    private boolean mSortAscending = false;
+    private int mSlideShowInterval;
+    private int mLastSlideShowImage;
+    private boolean mFirst = true;
+    private int mCurrentPosition = 0;
+    private boolean mLayoutComplete = false;
+
+    // represents which style animation to use
+    private int mAnimationIndex;
+    private Animation [] mSlideShowInAnimation;
+    private Animation [] mSlideShowOutAnimation;
+
+    private SharedPreferences mPrefs;
+
+    private View mNextImageView, mPrevImageView;
+    private Animation mHideNextImageViewAnimation = new AlphaAnimation(1F, 0F);
+    private Animation mHidePrevImageViewAnimation = new AlphaAnimation(1F, 0F);
+    private Animation mShowNextImageViewAnimation = new AlphaAnimation(0F, 1F);
+    private Animation mShowPrevImageViewAnimation = new AlphaAnimation(0F, 1F);
+
+
+    static final int sPadding = 20;
+    static final int sHysteresis = sPadding * 2;
+    static final int sBaseScrollDuration = 1000; // ms
+
+    private ImageManager.IImageList mAllImages;
+
+    private int mSlideShowImageCurrent = 0;
+    private ImageViewTouch [] mSlideShowImageViews = new ImageViewTouch[2];
+
+
+    // Array of image views.  The center view is the one the user is focused
+    // on.  The one at the zeroth position and the second position reflect
+    // the images to the left/right of the center image.
+    private ImageViewTouch[] mImageViews = new ImageViewTouch[3];
+
+    // Container for the three image views.  This guy can be "scrolled"
+    // to reveal the image prior to and after the center image.
+    private ScrollHandler mScroller;
+
+    private MenuHelper.MenuItemsResult mImageMenuRunnable;
+
+    private Runnable mDismissOnScreenControlsRunnable;
+    private boolean mCameraReviewMode;
+
+    private int mCurrentOrientation;
+
+
+    public ViewImage() {
+    }
+
+    private void updateNextPrevControls() {
+        boolean showPrev =  mCurrentPosition > 0;
+        boolean showNext = mCurrentPosition < mAllImages.getCount() - 1;
+
+        boolean prevIsVisible = mPrevImageView.getVisibility() == View.VISIBLE;
+        boolean nextIsVisible = mNextImageView.getVisibility() == View.VISIBLE;
+
+        if (showPrev && !prevIsVisible) {
+            Animation a = mShowPrevImageViewAnimation;
+            a.setDuration(500);
+            a.startNow();
+            mPrevImageView.setAnimation(a);
+            mPrevImageView.setVisibility(View.VISIBLE);
+        } else if (!showPrev && prevIsVisible) {
+            Animation a = mHidePrevImageViewAnimation;
+            a.setDuration(500);
+            a.startNow();
+            mPrevImageView.setAnimation(a);
+            mPrevImageView.setVisibility(View.GONE);
+        }
+
+        if (showNext && !nextIsVisible) {
+            Animation a = mShowNextImageViewAnimation;
+            a.setDuration(500);
+            a.startNow();
+            mNextImageView.setAnimation(a);
+            mNextImageView.setVisibility(View.VISIBLE);
+        } else if (!showNext && nextIsVisible) {
+            Animation a = mHideNextImageViewAnimation;
+            a.setDuration(500);
+            a.startNow();
+            mNextImageView.setAnimation(a);
+            mNextImageView.setVisibility(View.GONE);
+        }
+    }
+
+    private void showOnScreenControls() {
+        updateNextPrevControls();
+        scheduleDismissOnScreenControls();
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent m) {
+        boolean sup = super.dispatchTouchEvent(m);
+        if (sup == false) {
+            if (mMode == MODE_SLIDESHOW) {
+                mSlideShowImageViews[mSlideShowImageCurrent].handleTouchEvent(m);
+            } else if (mMode == MODE_NORMAL){
+                mImageViews[1].handleTouchEvent(m);
+            }
+            return true;
+        }
+        return true;
+    }
+
+    private void scheduleDismissOnScreenControls() {
+        mHandler.removeCallbacks(mDismissOnScreenControlsRunnable);
+        mHandler.postDelayed(mDismissOnScreenControlsRunnable, 1500);
+    }
+
+    public void setupDismissOnScreenControlRunnable() {
+        mDismissOnScreenControlsRunnable = new Runnable() {
+            public void run() {
+                if (!mShowActionIcons) {
+                    if (mNextImageView.getVisibility() == View.VISIBLE) {
+                        Animation a = mHideNextImageViewAnimation;
+                        a.setDuration(500);
+                        a.startNow();
+                        mNextImageView.setAnimation(a);
+                        mNextImageView.setVisibility(View.INVISIBLE);
+                    }
+
+                    if (mPrevImageView.getVisibility() == View.VISIBLE) {
+                        Animation a = mHidePrevImageViewAnimation;
+                        a.setDuration(500);
+                        a.startNow();
+                        mPrevImageView.setAnimation(a);
+                        mPrevImageView.setVisibility(View.INVISIBLE);
+                    }
+                }
+            }
+        };
+    }
+
+    private boolean isPickIntent() {
+        String action = getIntent().getAction();
+        return (Intent.ACTION_PICK.equals(action) || Intent.ACTION_GET_CONTENT.equals(action));
+    }
+
+    private static final boolean sUseBounce = false;
+    private static final boolean sAnimateTransitions = false;
+
+    static public class ImageViewTouch extends ImageViewTouchBase {
+        private ViewImage mViewImage;
+        private boolean mEnableTrackballScroll;
+        private GestureDetector mGestureDetector;
+
+        private static int TOUCH_AREA_WIDTH = 60;
+        
+        // The zoom ring setup:
+        // We limit the thumb on the zoom ring in the range 0 to 5/3*PI. The
+        // last PI/3 of the ring is left as a region that the thumb can't go in.
+        // The 5/3*PI range is divided into 60 steps. Each step scales the image
+        // by mScaleRate. We make mScaleRate^60 = maxZoom().
+        
+        // This is the max step value we can have for the zoom ring.
+        private static int MAX_STEP = 60;
+        // This is the angle we used to separate each step.
+        private static float STEP_ANGLE = (5 * (float) Math.PI / 3) / MAX_STEP;
+        // The scale rate for each step.
+        private float mScaleRate;
+
+        // Returns current scale step (numbered from 0 to MAX_STEP).
+        private int getCurrentStep() {
+            float s = getScale();
+            float b = mScaleRate;
+            int step = (int)Math.round(Math.log(s) / Math.log(b));
+            return Math.max(0, Math.min(MAX_STEP, step));
+        }
+
+        // Limit the thumb on the zoom ring in the range 0 to 5/3*PI. (clockwise
+        // angle is negative, and we need to mod 2*PI for the API to work.)
+        private void setZoomRingBounds() {
+            mScaleRate = (float) Math.pow(maxZoom(), 1.0 / MAX_STEP);
+            float limit = (2 - 5 / 3F) * (float) Math.PI;
+            mZoomRingController.setThumbClockwiseBound(limit);
+            mZoomRingController.setThumbCounterclockwiseBound(0);
+        }
+
+        private ZoomButtonsController mZoomButtonsController;
+        
+        // The zoom ring is set to visible by a double tap.
+        private ZoomRingController mZoomRingController;
+        private ZoomRingController.OnZoomListener mZoomListener =
+                new ZoomRingController.OnZoomListener() {
+            public void onCenter(int x, int y) {
+            }
+
+            public void onBeginPan() {
+            }
+
+            public boolean onPan(int deltaX, int deltaY) {
+                postTranslate(-deltaX, -deltaY, sUseBounce);
+                ImageViewTouch.this.center(true, true, false);
+                return true;
+            }
+
+            public void onEndPan() {
+            }
+
+            // The clockwise angle is negative, so we need to mod 2*PI
+            private float stepToAngle(int step) {
+                float angle = step * STEP_ANGLE;
+                angle = (float) Math.PI * 2 - angle;
+                return angle;
+            }
+
+            private int angleToStep(double angle) {
+                angle = Math.PI * 2 - angle;
+                int step = (int)Math.round(angle / STEP_ANGLE);
+                return step;
+            }
+
+            public void onVisibilityChanged(boolean visible) {
+                if (visible) {
+                    int step = getCurrentStep();
+                    float angle = stepToAngle(step);
+                    mZoomRingController.setThumbAngle(angle);
+                }
+            }
+
+            public void onBeginDrag() {
+                setZoomRingBounds();
+            }
+
+            public void onEndDrag() {
+            }
+
+            public boolean onDragZoom(int deltaZoomLevel, int centerX,
+                                      int centerY, float startAngle, float curAngle) {
+                setZoomRingBounds();
+                int deltaStep = angleToStep(curAngle) - getCurrentStep();
+                if ((deltaZoomLevel > 0) && (deltaStep < 0)) return false;
+                if ((deltaZoomLevel < 0) && (deltaStep > 0)) return false;
+                if ((deltaZoomLevel == 0) || (deltaStep == 0)) return false;
+
+                float oldScale = getScale();
+
+                // First move centerX/centerY to the center of the view.
+                int deltaX = getWidth() / 2 - centerX;
+                int deltaY = getHeight() / 2 - centerY;
+                panBy(deltaX, deltaY);
+
+                // Do zoom in/out.                
+                if (deltaStep > 0) {
+                    zoomIn((float) Math.pow(mScaleRate, deltaStep));
+                } else if (deltaStep < 0) {
+                    zoomOut((float) Math.pow(mScaleRate, -deltaStep));
+                }
+
+                // Reverse the first centering.
+                panBy(-deltaX, -deltaY);
+
+                // Return true if the zoom succeeds.
+                return (oldScale != getScale());
+            }
+
+            public void onSimpleZoom(boolean zoomIn) {
+                if (zoomIn) zoomIn();
+                else zoomOut();
+            }
+        };
+
+        public ImageViewTouch(Context context) {
+            super(context);
+            setup(context);
+        }
+
+        public ImageViewTouch(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            setup(context);
+        }
+
+        private void setup(Context context) {
+            mViewImage = (ViewImage) context;
+            mZoomRingController = new ZoomRingController(context, this);
+            mZoomRingController.setVibration(false);
+            mZoomRingController.setZoomCallbackThreshold(STEP_ANGLE);
+            mZoomRingController.setResetThumbAutomatically(false);
+            mZoomRingController.setCallback(mZoomListener);
+            mGestureDetector = new GestureDetector(getContext(), new MyGestureListener());
+            mGestureDetector.setOnDoubleTapListener(new MyDoubleTapListener());
+            mZoomButtonsController = new ZoomButtonsController(context, this);
+            mZoomButtonsController.setOverviewVisible(false);
+            mZoomButtonsController.setCallback(new ZoomButtonsController.OnZoomListener() {
+
+                public void onCenter(int x, int y) {
+                    mZoomListener.onCenter(x, y);
+                }
+
+                public void onOverview() {
+                }
+
+                public void onVisibilityChanged(boolean visible) {
+                    mZoomListener.onVisibilityChanged(visible);
+                }
+
+                public void onZoom(boolean zoomIn) {
+                    mZoomListener.onSimpleZoom(zoomIn);
+                }
+            });
+        }
+
+        public void setEnableTrackballScroll(boolean enable) {
+            mEnableTrackballScroll = enable;
+        }
+
+        protected void postTranslate(float dx, float dy, boolean bounceOK) {
+            super.postTranslate(dx, dy);
+            if (dx != 0F || dy != 0F)
+                mViewImage.showOnScreenControls();
+
+            if (!sUseBounce) {
+                center(true, false, false);
+            }
+        }
+
+        protected ScrollHandler scrollHandler() {
+            return mViewImage.mScroller;
+        }
+
+        public boolean handleTouchEvent(MotionEvent m) {
+            return mGestureDetector.onTouchEvent(m);
+        }
+
+        private class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
+            public boolean onScroll(MotionEvent e1, MotionEvent e2,
+                    float distanceX, float distanceY) {
+                if (getScale() > 1F) {
+                    postTranslate(-distanceX, -distanceY, sUseBounce);
+                    ImageViewTouch.this.center(true, true, false);
+                }
+                return true;
+            }
+        }
+
+        private class MyDoubleTapListener implements GestureDetector.OnDoubleTapListener {
+            // On single tap, we show the arrows. We also change to the
+            // prev/next image if the user taps on the left/right region.
+            public boolean onSingleTapConfirmed(MotionEvent e) {
+                ViewImage viewImage = mViewImage;
+
+                int viewWidth = getWidth();
+                int x = (int) e.getX();
+                int y = (int) e.getY();
+                if (x < TOUCH_AREA_WIDTH) {
+                    viewImage.moveNextOrPrevious(-1);
+                } else if (x > viewWidth - TOUCH_AREA_WIDTH) {
+                    viewImage.moveNextOrPrevious(1);
+                }
+
+                viewImage.setMode(MODE_NORMAL);
+                viewImage.showOnScreenControls();
+
+                return true;
+            }
+
+            // On double tap, we show the zoom ring control.
+            public boolean onDoubleTapEvent(MotionEvent e) {
+                mViewImage.setMode(MODE_NORMAL);
+                mZoomRingController.handleDoubleTapEvent(e);
+                mZoomButtonsController.handleDoubleTapEvent(e);
+                return true;
+            }
+
+            public boolean onDoubleTap(MotionEvent e) {
+                return false;
+            }
+
+        }
+
+        @Override
+        public boolean onKeyDown(int keyCode, KeyEvent event)
+        {
+            // Don't respond to arrow keys if trackball scrolling is not enabled
+            if (!mEnableTrackballScroll) {
+                if ((keyCode >= KeyEvent.KEYCODE_DPAD_UP)
+                        && (keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT)) {
+                    return super.onKeyDown(keyCode, event);
+                }
+            }
+
+            int current = mViewImage.mCurrentPosition;
+
+            int nextImagePos = -2; // default no next image
+            try {
+                switch (keyCode) {
+                    case KeyEvent.KEYCODE_DPAD_CENTER: {
+                        if (mViewImage.isPickIntent()) {
+                            ImageManager.IImage img = mViewImage.mAllImages.getImageAt(mViewImage.mCurrentPosition);
+                            mViewImage.setResult(RESULT_OK,
+                                    new Intent().setData(img.fullSizeImageUri()));
+                            mViewImage.finish();
+                        }
+                        break;
+                    }
+                    case KeyEvent.KEYCODE_DPAD_LEFT: {
+                        panBy(sPanRate, 0);
+                        int maxOffset = (current == 0) ? 0 : sHysteresis;
+                        if (getScale() <= 1F || isShiftedToNextImage(true, maxOffset)) {
+                            nextImagePos = current - 1;
+                        } else {
+                            center(true, false, true);
+                        }
+                        return true;
+                    }
+                    case KeyEvent.KEYCODE_DPAD_RIGHT: {
+                        panBy(-sPanRate, 0);
+                        int maxOffset = (current == mViewImage.mAllImages.getCount()-1) ? 0 : sHysteresis;
+                        if (getScale() <= 1F || isShiftedToNextImage(false, maxOffset)) {
+                            nextImagePos = current + 1;
+                        } else {
+                            center(true, false, true);
+                        }
+                        return true;
+                    }
+                    case KeyEvent.KEYCODE_DPAD_UP: {
+                        panBy(0, sPanRate);
+                        center(true, false, false);
+                        return true;
+                    }
+                    case KeyEvent.KEYCODE_DPAD_DOWN: {
+                        panBy(0, -sPanRate);
+                        center(true, false, false);
+                        return true;
+                    }
+                    case KeyEvent.KEYCODE_DEL:
+                        MenuHelper.deletePhoto(mViewImage, mViewImage.mDeletePhotoRunnable);
+                        break;
+                }
+            } finally {
+                if (nextImagePos >= 0 && nextImagePos < mViewImage.mAllImages.getCount()) {
+                    synchronized (mViewImage) {
+                        mViewImage.setMode(MODE_NORMAL);
+                        mViewImage.setImage(nextImagePos);
+                    }
+               } else if (nextImagePos != -2) {
+                   center(true, true, false);
+               }
+            }
+
+            return super.onKeyDown(keyCode, event);
+        }
+
+        protected boolean isShiftedToNextImage(boolean left, int maxOffset) {
+            boolean retval;
+            Bitmap bitmap = mBitmapDisplayed;
+            Matrix m = getImageViewMatrix();
+            if (left) {
+                float [] t1 = new float[] { 0, 0 };
+                m.mapPoints(t1);
+                retval = t1[0] > maxOffset;
+            } else {
+                int width = bitmap != null ? bitmap.getWidth() : getWidth();
+                float [] t1 = new float[] { width, 0 };
+                m.mapPoints(t1);
+                retval = t1[0] + maxOffset < getWidth();
+            }
+            return retval;
+        }
+
+        protected void scrollX(int deltaX) {
+            scrollHandler().scrollBy(deltaX, 0);
+        }
+
+        protected int getScrollOffset() {
+            return scrollHandler().getScrollX();
+        }
+
+        @Override
+        protected void onDetachedFromWindow() {
+            mZoomRingController.setVisible(false);
+            mZoomButtonsController.setVisible(false);
+        }
+
+    }
+
+    static class ScrollHandler extends LinearLayout {
+        private Runnable mFirstLayoutCompletedCallback = null;
+        private Scroller mScrollerHelper;
+        private int mWidth = -1;
+
+        public ScrollHandler(Context context) {
+            super(context);
+            mScrollerHelper = new Scroller(context);
+        }
+
+        public ScrollHandler(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            mScrollerHelper = new Scroller(context);
+        }
+
+        public void setLayoutCompletedCallback(Runnable r) {
+            mFirstLayoutCompletedCallback = r;
+        }
+
+        public void startScrollTo(int newX, int newY) {
+            int oldX = getScrollX();
+            int oldY = getScrollY();
+
+            int deltaX = newX - oldX;
+            int deltaY = newY - oldY;
+
+            if (mWidth == -1) {
+                mWidth = findViewById(R.id.image2).getWidth();
+            }
+            int viewWidth = mWidth;
+
+            int duration = viewWidth > 0
+                    ? sBaseScrollDuration * Math.abs(deltaX) / viewWidth
+                    : 0;
+            mScrollerHelper.startScroll(oldX, oldY, deltaX, deltaY, duration);
+            invalidate();
+        }
+
+        @Override
+        public void computeScroll() {
+            if (mScrollerHelper.computeScrollOffset()) {
+                scrollTo(mScrollerHelper.getCurrX(), mScrollerHelper.getCurrY());
+                postInvalidate();  // So we draw again
+            }
+        }
+
+        @Override
+        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+            int width = right - left;
+            int x = 0;
+            for (View v : new View[] {
+                    findViewById(R.id.image1),
+                    findViewById(R.id.image2),
+                    findViewById(R.id.image3) }) {
+                v.layout(x, 0, x + width, bottom);
+                x += (width + sPadding);
+            }
+
+            findViewById(R.id.padding1).layout(width, 0, width + sPadding, bottom);
+            findViewById(R.id.padding2).layout(width+sPadding+width, 0, width+sPadding+width+sPadding, bottom);
+
+            if (changed) {
+                if (mFirstLayoutCompletedCallback != null) {
+                    mFirstLayoutCompletedCallback.run();
+                }
+            }
+        }
+    }
+
+    private void animateScrollTo(int xNew, int yNew) {
+        mScroller.startScrollTo(xNew, yNew);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu)
+    {
+        super.onCreateOptionsMenu(menu);
+
+        if (! mCameraReviewMode) {
+            MenuItem item = menu.add(Menu.CATEGORY_SECONDARY, 203, 0, R.string.slide_show);
+            item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+                public boolean onMenuItemClick(MenuItem item) {
+                    setMode(MODE_SLIDESHOW);
+                    mLastSlideShowImage = mCurrentPosition;
+                    loadNextImage(mCurrentPosition, 0, true);
+                    return true;
+                }
+            });
+            item.setIcon(android.R.drawable.ic_menu_slideshow);
+        }
+
+        final SelectedImageGetter selectedImageGetter = new SelectedImageGetter() {
+            public ImageManager.IImage getCurrentImage() {
+                return mAllImages.getImageAt(mCurrentPosition);
+            }
+
+            public Uri getCurrentImageUri() {
+                return mAllImages.getImageAt(mCurrentPosition).fullSizeImageUri();
+            }
+        };
+
+        mImageMenuRunnable = MenuHelper.addImageMenuItems(
+                menu,
+                MenuHelper.INCLUDE_ALL,
+                true,
+                ViewImage.this,
+                mHandler,
+                mDeletePhotoRunnable,
+                new MenuHelper.MenuInvoker() {
+                    public void run(MenuHelper.MenuCallback cb) {
+                        setMode(MODE_NORMAL);
+                        cb.run(selectedImageGetter.getCurrentImageUri(), selectedImageGetter.getCurrentImage());
+                        for (ImageViewTouchBase iv: mImageViews) {
+                            iv.recycleBitmaps();
+                            iv.setImageBitmap(null, true);
+                        }
+                        setImage(mCurrentPosition);
+                    }
+                });
+
+        if (true) {
+            MenuItem item = menu.add(Menu.CATEGORY_SECONDARY, 203, 1000, R.string.camerasettings);
+            item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+                public boolean onMenuItemClick(MenuItem item) {
+                    Intent preferences = new Intent();
+                    preferences.setClass(ViewImage.this, GallerySettings.class);
+                    startActivity(preferences);
+                    return true;
+                }
+            });
+            item.setAlphabeticShortcut('p');
+            item.setIcon(android.R.drawable.ic_menu_preferences);
+        }
+
+        // Hidden menu just so the shortcut will bring up the zoom controls
+        menu.add(Menu.CATEGORY_SECONDARY, 203, 0, R.string.camerasettings)      // the string resource is a placeholder
+        .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+            public boolean onMenuItemClick(MenuItem item) {
+                showOnScreenControls();
+                return true;
+            }
+        })
+        .setAlphabeticShortcut('z')
+        .setVisible(false);
+
+
+        return true;
+    }
+
+    protected Runnable mDeletePhotoRunnable = new Runnable() {
+        public void run() {
+            mAllImages.removeImageAt(mCurrentPosition);
+            if (mAllImages.getCount() == 0) {
+                finish();
+            } else {
+                if (mCurrentPosition == mAllImages.getCount()) {
+                    mCurrentPosition -= 1;
+                }
+            }
+            for (ImageViewTouchBase iv: mImageViews) {
+                iv.setImageBitmapResetBase(null, true, true);
+            }
+            setImage(mCurrentPosition);
+        }
+    };
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu)
+    {
+        super.onPrepareOptionsMenu(menu);
+        setMode(MODE_NORMAL);
+
+        if (mImageMenuRunnable != null) {
+            mImageMenuRunnable.gettingReadyToOpen(menu, mAllImages.getImageAt(mCurrentPosition));
+        }
+
+        return true;
+    }
+
+    @Override
+    public void onConfigurationChanged(android.content.res.Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        boolean changed = mCurrentOrientation != newConfig.orientation;
+        mCurrentOrientation = newConfig.orientation;
+        if (changed) {
+            if (mGetter != null) {
+                // kill off any background image fetching
+                mGetter.cancelCurrent();
+                mGetter.stop();
+            }
+            makeGetter();
+            mFirst = true;
+
+            // clear off the current set of images since we need to reload
+            // them at the right size
+            for (ImageViewTouchBase iv: mImageViews) {
+                iv.clear();
+            }
+            MenuHelper.requestOrientation(this, mPrefs);
+        }
+    }
+
+    @Override
+    public boolean onMenuItemSelected(int featureId, MenuItem item) {
+        boolean b = super.onMenuItemSelected(featureId, item);
+        if (mImageMenuRunnable != null)
+            mImageMenuRunnable.aboutToCall(item, mAllImages.getImageAt(mCurrentPosition));
+        return b;
+    }
+
+    /*
+     * Here's the loading strategy.  For any given image, load the thumbnail
+     * into memory and post a callback to display the resulting bitmap.
+     *
+     * Then proceed to load the full image bitmap.   Three things can
+     * happen at this point:
+     *
+     * 1.  the image fails to load because the UI thread decided
+     * to move on to a different image.  This "cancellation" happens
+     * by virtue of the UI thread closing the stream containing the
+     * image being decoded.  BitmapFactory.decodeStream returns null
+     * in this case.
+     *
+     * 2.  the image loaded successfully.  At that point we post
+     * a callback to the UI thread to actually show the bitmap.
+     *
+     * 3.  when the post runs it checks to see if the image that was
+     * loaded is still the one we want.  The UI may have moved on
+     * to some other image and if so we just drop the newly loaded
+     * bitmap on the floor.
+     */
+
+    interface ImageGetterCallback {
+        public void imageLoaded(int pos, int offset, Bitmap bitmap, boolean isThumb);
+        public boolean wantsThumbnail(int pos, int offset);
+        public boolean wantsFullImage(int pos, int offset);
+        public int fullImageSizeToUse(int pos, int offset);
+        public void completed(boolean wasCanceled);
+        public int [] loadOrder();
+    }
+
+    class ImageGetter {
+        // The thread which does the work.
+        private Thread mGetterThread;
+
+        // The base position that's being retrieved.  The actual images retrieved
+        // are this base plus each of the offets.
+        private int mCurrentPosition = -1;
+
+        // The callback to invoke for each image.
+        private ImageGetterCallback mCB;
+
+        // This is the loader cancelable that gets set while we're loading an image.
+        // If we change position we can cancel the current load using this.
+        private ImageManager.IGetBitmap_cancelable mLoad;
+
+        // True if we're canceling the current load.
+        private boolean mCancelCurrent = false;
+
+        // True when the therad should exit.
+        private boolean mDone = false;
+
+        // True when the loader thread is waiting for work.
+        private boolean mReady = false;
+
+        private void cancelCurrent() {
+            synchronized (this) {
+                if (!mReady) {
+                    mCancelCurrent = true;
+                    ImageManager.IGetBitmap_cancelable load = mLoad;
+                    if (load != null) {
+                        if (Config.LOGV)
+                            Log.v(TAG, "canceling load object");
+                        load.cancel();
+                    }
+                    mCancelCurrent = false;
+                }
+            }
+        }
+
+        public ImageGetter() {
+            mGetterThread = new Thread(new Runnable() {
+
+                private Runnable callback(final int position, final int offset, final boolean isThumb, final Bitmap bitmap) {
+                    return new Runnable() {
+                        public void run() {
+                            // check for inflight callbacks that aren't applicable any longer
+                            // before delivering them
+                            if (!isCanceled() && position == mCurrentPosition) {
+                                mCB.imageLoaded(position, offset, bitmap, isThumb);
+                            } else {
+                                if (bitmap != null)
+                                    bitmap.recycle();
+                            }
+                        }
+                    };
+                }
+
+                private Runnable completedCallback(final boolean wasCanceled) {
+                    return new Runnable() {
+                        public void run() {
+                            mCB.completed(wasCanceled);
+                        }
+                    };
+                }
+
+                public void run() {
+                    int lastPosition = -1;
+                    while (!mDone) {
+                        synchronized (ImageGetter.this) {
+                            mReady = true;
+                            ImageGetter.this.notify();
+
+                            if (mCurrentPosition == -1 || lastPosition == mCurrentPosition) {
+                                try {
+                                    ImageGetter.this.wait();
+                                } catch (InterruptedException ex) {
+                                    continue;
+                                }
+                            }
+
+                            lastPosition = mCurrentPosition;
+                            mReady = false;
+                        }
+
+                        if (lastPosition != -1) {
+                            int imageCount = mAllImages.getCount();
+
+                            int [] order = mCB.loadOrder();
+                            for (int i = 0; i < order.length; i++) {
+                                int offset = order[i];
+                                int imageNumber = lastPosition + offset;
+                                if (imageNumber >= 0 && imageNumber < imageCount) {
+                                    ImageManager.IImage image = mAllImages.getImageAt(lastPosition + offset);
+                                    if (image == null || isCanceled()) {
+                                        break;
+                                    }
+                                    if (mCB.wantsThumbnail(lastPosition, offset)) {
+                                        if (Config.LOGV)
+                                            Log.v(TAG, "starting THUMBNAIL load at offset " + offset);
+                                        Bitmap b = image.thumbBitmap();
+                                        mHandler.post(callback(lastPosition, offset, true, b));
+                                    }
+                                }
+                            }
+
+                            for (int i = 0; i < order.length; i++) {
+                                int offset = order[i];
+                                int imageNumber = lastPosition + offset;
+                                if (imageNumber >= 0 && imageNumber < imageCount) {
+                                    ImageManager.IImage image = mAllImages.getImageAt(lastPosition + offset);
+                                    if (mCB.wantsFullImage(lastPosition, offset)) {
+                                        if (Config.LOGV)
+                                            Log.v(TAG, "starting FULL IMAGE load at offset " + offset);
+                                        int sizeToUse = mCB.fullImageSizeToUse(lastPosition, offset);
+                                        if (image != null && !isCanceled()) {
+                                            mLoad = image.fullSizeBitmap_cancelable(sizeToUse);
+                                        }
+                                        if (mLoad != null) {
+                                            long t1;
+                                            if (Config.LOGV) t1 = System.currentTimeMillis();
+
+                                            Bitmap b = null;
+                                            try {
+                                                b = mLoad.get();
+                                            } catch (OutOfMemoryError e) {
+                                                Log.e(TAG, "couldn't load full size bitmap for " + "");
+                                            }
+                                            if (Config.LOGV && b != null) {
+                                                long t2 = System.currentTimeMillis();
+                                                Log.v(TAG, "loading full image for " + image.fullSizeImageUri()
+                                                        + " with requested size " + sizeToUse
+                                                        + " took " + (t2-t1)
+                                                        + " and returned a bitmap with size "
+                                                        + b.getWidth() + " / " + b.getHeight());
+                                            }
+
+                                            mLoad = null;
+                                            if (b != null) {
+                                                if (isCanceled()) {
+                                                    b.recycle();
+                                                } else {
+                                                    mHandler.post(callback(lastPosition, offset, false, b));
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                            mHandler.post(completedCallback(isCanceled()));
+                        }
+                    }
+                }
+            });
+            mGetterThread.setName("ImageGettter");
+            mGetterThread.start();
+        }
+
+        private boolean isCanceled() {
+            synchronized (this) {
+                return mCancelCurrent;
+            }
+        }
+
+        public void setPosition(int position, ImageGetterCallback cb) {
+            synchronized (this) {
+                if (!mReady) {
+                    try {
+                        mCancelCurrent = true;
+                        ImageManager.IGetBitmap_cancelable load = mLoad;
+                        if (load != null) {
+                            load.cancel();
+                        }
+                        // if the thread is waiting before loading the full size
+                        // image then this will free it up
+                        ImageGetter.this.notify();
+                        ImageGetter.this.wait();
+                        mCancelCurrent = false;
+                    } catch (InterruptedException ex) {
+                        // not sure what to do here
+                    }
+                }
+            }
+
+            mCurrentPosition = position;
+            mCB = cb;
+
+            synchronized (this) {
+                ImageGetter.this.notify();
+            }
+        }
+
+        public void stop() {
+            synchronized (this) {
+                mDone = true;
+                ImageGetter.this.notify();
+            }
+            try {
+                mGetterThread.join();
+            } catch (InterruptedException ex) {
+
+            }
+        }
+    }
+
+    private void setImage(int pos) {
+        if (!mLayoutComplete) {
+            return;
+        }
+
+        final boolean left = mCurrentPosition > pos;
+
+        mCurrentPosition = pos;
+
+        ImageViewTouchBase current = mImageViews[1];
+        current.mSuppMatrix.reset();
+        current.setImageMatrix(current.getImageViewMatrix());
+
+        if (false) {
+            Log.v(TAG, "before...");
+            for (ImageViewTouchBase ivtb : mImageViews)
+                ivtb.dump();
+        }
+
+        if (!mFirst) {
+            if (left) {
+                mImageViews[2].copyFrom(mImageViews[1]);
+                mImageViews[1].copyFrom(mImageViews[0]);
+            } else {
+                mImageViews[0].copyFrom(mImageViews[1]);
+                mImageViews[1].copyFrom(mImageViews[2]);
+            }
+        }
+        if (false) {
+            Log.v(TAG, "after copy...");
+            for (ImageViewTouchBase ivtb : mImageViews)
+                ivtb.dump();
+        }
+
+        for (ImageViewTouchBase ivt: mImageViews) {
+            ivt.mIsZooming = false;
+        }
+        int width = mImageViews[1].getWidth();
+        int from;
+        int to = width + sPadding;
+        if (mFirst) {
+            from = to;
+            mFirst = false;
+        } else {
+            from = left ? (width + sPadding) + mScroller.getScrollX()
+                        : mScroller.getScrollX() - (width + sPadding);
+        }
+
+        if (sAnimateTransitions) {
+            mScroller.scrollTo(from, 0);
+            animateScrollTo(to, 0);
+        } else {
+            mScroller.scrollTo(to, 0);
+        }
+
+        ImageGetterCallback cb = new ImageGetterCallback() {
+            public void completed(boolean wasCanceled) {
+                if (!mShowActionIcons) {
+                    mImageViews[1].setFocusableInTouchMode(true);
+                    mImageViews[1].requestFocus();
+                }
+            }
+
+            public boolean wantsThumbnail(int pos, int offset) {
+                ImageViewTouchBase ivt = mImageViews[1 + offset];
+                return ivt.mThumbBitmap == null;
+            }
+
+            public boolean wantsFullImage(int pos, int offset) {
+                ImageViewTouchBase ivt = mImageViews[1 + offset];
+                if (ivt.mBitmapDisplayed != null && !ivt.mBitmapIsThumbnail) {
+                    return false;
+                }
+                if (offset != 0) {
+                    return false;
+                }
+                return true;
+            }
+
+            public int fullImageSizeToUse(int pos, int offset) {
+                // TODO
+                // this number should be bigger so that we can zoom.  we may need to
+                // get fancier and read in the fuller size image as the user starts
+                // to zoom.  use -1 to get the full full size image.
+                // for now use 480 so we don't run out of memory
+                final int imageViewSize = 480;
+                return imageViewSize;
+            }
+
+            public int [] loadOrder() {
+                return sOrder_adjacents;
+            }
+
+            public void imageLoaded(int pos, int offset, Bitmap bitmap, boolean isThumb) {
+                ImageViewTouchBase ivt = mImageViews[1 + offset];
+                ivt.setImageBitmapResetBase(bitmap, isThumb, isThumb);
+            }
+        };
+
+        // Could be null if we're stopping a slide show in the course of pausing
+        if (mGetter != null) {
+            mGetter.setPosition(pos, cb);
+        }
+        showOnScreenControls();
+    }
+
+    @Override
+    public void onCreate(Bundle instanceState)
+    {
+        super.onCreate(instanceState);
+        Intent intent = getIntent();
+        mCameraReviewMode = intent.getBooleanExtra("com.android.camera.ReviewMode", false);
+        mFullScreenInNormalMode = intent.getBooleanExtra(MediaStore.EXTRA_FULL_SCREEN, true);
+        mShowActionIcons = intent.getBooleanExtra(MediaStore.EXTRA_SHOW_ACTION_ICONS, false);
+
+        mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+        mCurrentOrientation = getResources().getConfiguration().orientation;
+
+        setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT);
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        setContentView(R.layout.viewimage);
+
+        mImageViews[0] = (ImageViewTouch) findViewById(R.id.image1);
+        mImageViews[1] = (ImageViewTouch) findViewById(R.id.image2);
+        mImageViews[2] = (ImageViewTouch) findViewById(R.id.image3);
+
+        for(ImageViewTouch v : mImageViews) {
+            v.setEnableTrackballScroll(!mShowActionIcons);
+        }
+
+        mScroller = (ScrollHandler)findViewById(R.id.scroller);
+        makeGetter();
+
+        mAnimationIndex = -1;
+
+        mSlideShowInAnimation = new Animation[] {
+            makeInAnimation(R.anim.transition_in),
+            makeInAnimation(R.anim.slide_in),
+            makeInAnimation(R.anim.slide_in_vertical),
+        };
+
+        mSlideShowOutAnimation = new Animation[] {
+            makeOutAnimation(R.anim.transition_out),
+            makeOutAnimation(R.anim.slide_out),
+            makeOutAnimation(R.anim.slide_out_vertical),
+        };
+
+        mSlideShowImageViews[0] = (ImageViewTouch) findViewById(R.id.image1_slideShow);
+        mSlideShowImageViews[1] = (ImageViewTouch) findViewById(R.id.image2_slideShow);
+        for (ImageViewTouch v : mSlideShowImageViews) {
+            v.setImageBitmapResetBase(null, true, true);
+            v.setVisibility(View.INVISIBLE);
+            v.setEnableTrackballScroll(!mShowActionIcons);
+        }
+
+        mActionIconPanel = findViewById(R.id.action_icon_panel);
+        {
+            int[] pickIds = {R.id.attach, R.id.cancel};
+            int[] normalIds = {R.id.gallery, R.id.setas, R.id.share, R.id.discard};
+            int[] hideIds = pickIds;
+            int[] connectIds = normalIds;
+            if (isPickIntent()) {
+                hideIds = normalIds;
+                connectIds = pickIds;
+            }
+            for(int id : hideIds) {
+                mActionIconPanel.findViewById(id).setVisibility(View.GONE);
+            }
+            for(int id : connectIds) {
+                View view = mActionIconPanel.findViewById(id);
+                view.setOnClickListener(this);
+                Animation animation = new AlphaAnimation(0F, 1F);
+                animation.setDuration(500);
+                view.setAnimation(animation);
+            }
+        }
+        mShutterButton = findViewById(R.id.shutter_button);
+        mShutterButton.setOnClickListener(this);
+
+        Uri uri = getIntent().getData();
+
+        if (Config.LOGV)
+            Log.v(TAG, "uri is " + uri);
+        if (instanceState != null) {
+            if (instanceState.containsKey("uri")) {
+                uri = Uri.parse(instanceState.getString("uri"));
+            }
+        }
+        if (uri == null) {
+            finish();
+            return;
+        }
+        init(uri);
+
+        Bundle b = getIntent().getExtras();
+
+        boolean slideShow = b != null ? b.getBoolean("slideshow", false) : false;
+        if (slideShow) {
+            setMode(MODE_SLIDESHOW);
+            loadNextImage(mCurrentPosition, 0, true);
+        } else {
+            if (mFullScreenInNormalMode) {
+                getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+            }
+            if (mShowActionIcons) {
+                mActionIconPanel.setVisibility(View.VISIBLE);
+                mShutterButton.setVisibility(View.VISIBLE);
+            }
+        }
+
+        setupDismissOnScreenControlRunnable();
+
+        mNextImageView = findViewById(R.id.next_image);
+        mPrevImageView = findViewById(R.id.prev_image);
+        mNextImageView.setOnClickListener(this);
+        mPrevImageView.setOnClickListener(this);
+
+        if (mShowActionIcons) {
+            mNextImageView.setFocusable(true);
+            mPrevImageView.setFocusable(true);
+        }
+
+        setOrientation();
+    }
+
+    private void setOrientation() {
+        Intent intent = getIntent();
+        if (intent.hasExtra(MediaStore.EXTRA_SCREEN_ORIENTATION)) {
+            int orientation = intent.getIntExtra(MediaStore.EXTRA_SCREEN_ORIENTATION,
+                    ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+            if (orientation != getRequestedOrientation()) {
+                setRequestedOrientation(orientation);
+            }
+        } else {
+            MenuHelper.requestOrientation(this, mPrefs);
+        }
+    }
+
+    private Animation makeInAnimation(int id) {
+        Animation inAnimation = AnimationUtils.loadAnimation(this, id);
+        return inAnimation;
+    }
+
+    private Animation makeOutAnimation(int id) {
+        Animation outAnimation = AnimationUtils.loadAnimation(this, id);
+        return outAnimation;
+    }
+
+    private void setMode(int mode) {
+        if (mMode == mode) {
+            return;
+        }
+
+        findViewById(R.id.slideShowContainer).setVisibility(mode == MODE_SLIDESHOW ? View.VISIBLE : View.GONE);
+        findViewById(R.id.abs)               .setVisibility(mode == MODE_NORMAL    ? View.VISIBLE : View.GONE);
+
+        Window win = getWindow();
+        mMode = mode;
+        if (mode == MODE_SLIDESHOW) {
+            win.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN
+                    | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+            if (sSlideShowHidesStatusBar) {
+                win.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+            }
+            for (ImageViewTouchBase ivt: mImageViews) {
+                ivt.clear();
+            }
+            mActionIconPanel.setVisibility(View.GONE);
+            mShutterButton.setVisibility(View.GONE);
+
+            if (false) {
+                Log.v(TAG, "current is " + this.mSlideShowImageCurrent);
+                this.mSlideShowImageViews[0].dump();
+                this.mSlideShowImageViews[0].dump();
+            }
+
+            findViewById(R.id.slideShowContainer).getRootView().requestLayout();
+            mUseShuffleOrder   = mPrefs.getBoolean("pref_gallery_slideshow_shuffle_key", false);
+            mSlideShowLoop     = mPrefs.getBoolean("pref_gallery_slideshow_repeat_key", false);
+            try {
+                mAnimationIndex = Integer.parseInt(mPrefs.getString("pref_gallery_slideshow_transition_key", "0"));
+            } catch (Exception ex) {
+                Log.e(TAG, "couldn't parse preference: " + ex.toString());
+                mAnimationIndex = 0;
+            }
+            try {
+                mSlideShowInterval = Integer.parseInt(mPrefs.getString("pref_gallery_slideshow_interval_key", "3")) * 1000;
+            } catch (Exception ex) {
+                Log.e(TAG, "couldn't parse preference: " + ex.toString());
+                mSlideShowInterval = 3000;
+            }
+
+            if (Config.LOGV) {
+                Log.v(TAG, "read prefs...  shuffle: " + mUseShuffleOrder);
+                Log.v(TAG, "read prefs...     loop: " + mSlideShowLoop);
+                Log.v(TAG, "read prefs...  animidx: " + mAnimationIndex);
+                Log.v(TAG, "read prefs... interval: " + mSlideShowInterval);
+            }
+
+            if (mUseShuffleOrder) {
+                generateShuffleOrder();
+            }
+        } else {
+            if (Config.LOGV)
+                Log.v(TAG, "slide show mode off, mCurrentPosition == " + mCurrentPosition);
+            win.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+            if (mFullScreenInNormalMode) {
+                win.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+            } else {
+                win.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+            }
+
+            if (mGetter != null)
+                mGetter.cancelCurrent();
+
+            if (sSlideShowHidesStatusBar) {
+                win.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+            }
+            if (mShowActionIcons) {
+                mActionIconPanel.setVisibility(View.VISIBLE);
+                mShutterButton.setVisibility(View.VISIBLE);
+            }
+
+            ImageViewTouchBase dst = mImageViews[1];
+            dst.mLastXTouchPos = -1;
+            dst.mLastYTouchPos = -1;
+
+            for (ImageViewTouchBase ivt: mSlideShowImageViews) {
+                ivt.clear();
+            }
+
+            mShuffleOrder = null;
+
+            // mGetter null is a proxy for being paused
+            if (mGetter != null) {
+                mFirst = true;  // don't animate
+                setImage(mCurrentPosition);
+            }
+        }
+
+        // this line shouldn't be necessary but the view hierarchy doesn't
+        // seem to realize that the window layout changed
+        mScroller.requestLayout();
+    }
+
+    private void generateShuffleOrder() {
+        if (mShuffleOrder == null || mShuffleOrder.length != mAllImages.getCount()) {
+            mShuffleOrder = new int[mAllImages.getCount()];
+        }
+
+        for (int i = 0; i < mShuffleOrder.length; i++) {
+            mShuffleOrder[i] = i;
+        }
+
+        for (int i = mShuffleOrder.length - 1; i > 0; i--) {
+            int r = mRandom.nextInt(i);
+            int tmp = mShuffleOrder[r];
+            mShuffleOrder[r] = mShuffleOrder[i];
+            mShuffleOrder[i] = tmp;
+        }
+    }
+
+    private void loadNextImage(final int requestedPos, final long delay, final boolean firstCall) {
+        if (firstCall && mUseShuffleOrder) {
+            generateShuffleOrder();
+        }
+
+        final long targetDisplayTime = System.currentTimeMillis() + delay;
+
+        ImageGetterCallback cb = new ImageGetterCallback() {
+            public void completed(boolean wasCanceled) {
+            }
+
+            public boolean wantsThumbnail(int pos, int offset) {
+                return true;
+            }
+
+            public boolean wantsFullImage(int pos, int offset) {
+                return false;
+            }
+
+            public int [] loadOrder() {
+                return sOrder_slideshow;
+            }
+
+            public int fullImageSizeToUse(int pos, int offset) {
+                return 480; // TODO compute this
+            }
+
+            public void imageLoaded(final int pos, final int offset, final Bitmap bitmap, final boolean isThumb) {
+                long timeRemaining = Math.max(0, targetDisplayTime - System.currentTimeMillis());
+                mHandler.postDelayed(new Runnable() {
+                    public void run() {
+                        if (mMode == MODE_NORMAL) {
+                            return;
+                        }
+
+                        ImageViewTouchBase oldView = mSlideShowImageViews[mSlideShowImageCurrent];
+
+                        if (++mSlideShowImageCurrent == mSlideShowImageViews.length) {
+                            mSlideShowImageCurrent = 0;
+                        }
+
+                        ImageViewTouchBase newView = mSlideShowImageViews[mSlideShowImageCurrent];
+                        newView.setVisibility(View.VISIBLE);
+                        newView.setImageBitmapResetBase(bitmap, isThumb, isThumb);
+                        newView.bringToFront();
+
+                        int animation = 0;
+
+                        if (mAnimationIndex == -1) {
+                            int n = mRandom.nextInt(mSlideShowInAnimation.length);
+                            animation = n;
+                        } else {
+                            animation = mAnimationIndex;
+                        }
+
+                        Animation aIn = mSlideShowInAnimation[animation];
+                        newView.setAnimation(aIn);
+                        newView.setVisibility(View.VISIBLE);
+                        aIn.startNow();
+
+                        Animation aOut = mSlideShowOutAnimation[animation];
+                        oldView.setVisibility(View.INVISIBLE);
+                        oldView.setAnimation(aOut);
+                        aOut.startNow();
+
+                        mCurrentPosition = requestedPos;
+
+                        mHandler.post(new Runnable() {
+                            public void run() {
+                                if (mCurrentPosition == mLastSlideShowImage && !firstCall) {
+                                    if (mSlideShowLoop) {
+                                        if (mUseShuffleOrder) {
+                                            generateShuffleOrder();
+                                        }
+                                    } else {
+                                        setMode(MODE_NORMAL);
+                                        return;
+                                    }
+                                }
+
+                                if (Config.LOGV)
+                                    Log.v(TAG, "mCurrentPosition is now " + mCurrentPosition);
+                                loadNextImage((mCurrentPosition + 1) % mAllImages.getCount(), mSlideShowInterval, false);
+                            }
+                        });
+                    }
+                }, timeRemaining);
+            }
+        };
+        // Could be null if we're stopping a slide show in the course of pausing
+        if (mGetter != null) {
+            int pos = requestedPos;
+            if (mShuffleOrder != null) {
+                pos = mShuffleOrder[pos];
+            }
+            mGetter.setPosition(pos, cb);
+        }
+    }
+
+    private void makeGetter() {
+        mGetter = new ImageGetter();
+    }
+
+    private boolean desiredSortOrder() {
+        String sortOrder = mPrefs.getString("pref_gallery_sort_key", null);
+        boolean sortAscending = false;
+        if (sortOrder != null) {
+            sortAscending = sortOrder.equals("ascending");
+        }
+        if (mCameraReviewMode) {
+            // Force left-arrow older pictures, right-arrow newer pictures.
+            sortAscending = true;
+        }
+        return sortAscending;
+    }
+
+    private void init(Uri uri) {
+        mSortAscending = desiredSortOrder();
+        int sort = mSortAscending ? ImageManager.SORT_ASCENDING : ImageManager.SORT_DESCENDING;
+        mAllImages = ImageManager.makeImageList(uri, this, sort);
+
+        uri = uri.buildUpon().query(null).build();
+        // TODO smarter/faster here please
+        for (int i = 0; i < mAllImages.getCount(); i++) {
+            ImageManager.IImage image = mAllImages.getImageAt(i);
+            if (image.fullSizeImageUri().equals(uri)) {
+                mCurrentPosition = i;
+                mLastSlideShowImage = mCurrentPosition;
+                break;
+            }
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle b) {
+        super.onSaveInstanceState(b);
+        ImageManager.IImage image = mAllImages.getImageAt(mCurrentPosition);
+
+        if (image != null){
+            Uri uri = image.fullSizeImageUri();
+            String bucket = null;
+            if(getIntent()!= null && getIntent().getData()!=null)
+                bucket = getIntent().getData().getQueryParameter("bucketId");
+
+            if(bucket!=null)
+                uri = uri.buildUpon().appendQueryParameter("bucketId", bucket).build();
+
+            b.putString("uri", uri.toString());
+        }
+        if (mMode == MODE_SLIDESHOW)
+            b.putBoolean("slideshow", true);
+    }
+
+    @Override
+    public void onResume()
+    {
+        super.onResume();
+
+        // normally this will never be zero but if one "backs" into this
+        // activity after removing the sdcard it could be zero.  in that
+        // case just "finish" since there's nothing useful that can happen.
+        if (mAllImages.getCount() == 0) {
+            finish();
+        }
+
+        ImageManager.IImage image = mAllImages.getImageAt(mCurrentPosition);
+
+        if (desiredSortOrder() != mSortAscending) {
+            init(image.fullSizeImageUri());
+        }
+
+        if (mGetter == null) {
+            makeGetter();
+        }
+
+        for (ImageViewTouchBase iv: mImageViews) {
+            iv.setImageBitmap(null, true);
+        }
+
+        mFirst = true;
+        mScroller.setLayoutCompletedCallback(new Runnable() {
+            public void run() {
+                mLayoutComplete = true;
+                setImage(mCurrentPosition);
+            }
+         });
+        setImage(mCurrentPosition);
+
+        setOrientation();
+
+        // Show a tutorial for the new zoom interaction (the method ensure we only show it once)
+        ZoomRingController.showZoomTutorialOnce(this);
+    }
+
+    @Override
+    public void onPause()
+    {
+        super.onPause();
+
+        mGetter.cancelCurrent();
+        mGetter.stop();
+        mGetter = null;
+        setMode(MODE_NORMAL);
+
+        mAllImages.deactivate();
+
+        for (ImageViewTouch iv: mImageViews) {
+            iv.recycleBitmaps();
+            iv.setImageBitmap(null, true);
+        }
+
+        for (ImageViewTouch iv: mSlideShowImageViews) {
+            iv.recycleBitmaps();
+            iv.setImageBitmap(null, true);
+        }
+        ZoomRingController.finishZoomTutorial(this, false);
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+    }
+
+    public void onClick(View v) {
+        switch (v.getId()) {
+
+        case R.id.shutter_button: {
+            if (mCameraReviewMode) {
+                finish();
+            } else {
+                MenuHelper.gotoStillImageCapture(this);
+            }
+        }
+        break;
+
+        case R.id.gallery: {
+            MenuHelper.gotoCameraImageGallery(this);
+        }
+        break;
+
+        case R.id.discard: {
+            if (mCameraReviewMode) {
+                mDeletePhotoRunnable.run();
+            } else {
+                MenuHelper.deletePhoto(this, mDeletePhotoRunnable);
+            }
+        }
+        break;
+
+        case R.id.share: {
+            Uri u = mAllImages.getImageAt(mCurrentPosition).fullSizeImageUri();
+            Intent intent = new Intent();
+            intent.setAction(Intent.ACTION_SEND);
+            intent.setType("image/jpeg");
+            intent.putExtra(Intent.EXTRA_STREAM, u);
+            try {
+                startActivity(Intent.createChooser(intent, getText(R.string.sendImage)));
+            } catch (android.content.ActivityNotFoundException ex) {
+                Toast.makeText(this, R.string.no_way_to_share_image, Toast.LENGTH_SHORT).show();
+            }
+        }
+        break;
+
+        case R.id.setas: {
+            Uri u = mAllImages.getImageAt(mCurrentPosition).fullSizeImageUri();
+            Intent intent = new Intent(Intent.ACTION_ATTACH_DATA, u);
+            try {
+                startActivity(Intent.createChooser(intent, getText(R.string.setImage)));
+            } catch (android.content.ActivityNotFoundException ex) {
+                Toast.makeText(this, R.string.no_way_to_share_video, Toast.LENGTH_SHORT).show();
+            }
+        }
+        break;
+
+        case R.id.next_image: {
+            moveNextOrPrevious(1);
+        }
+        break;
+
+        case R.id.prev_image: {
+            moveNextOrPrevious(-1);
+        }
+        break;
+        }
+    }
+
+    private void moveNextOrPrevious(int delta) {
+        int nextImagePos = mCurrentPosition + delta;
+        if ((0 <= nextImagePos) && (nextImagePos < mAllImages.getCount())) {
+            setImage(nextImagePos);
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        switch (requestCode) {
+        case MenuHelper.RESULT_COMMON_MENU_CROP:
+            if (resultCode == RESULT_OK) {
+                // The CropImage activity passes back the Uri of the cropped image as
+                // the Action rather than the Data.
+                Uri dataUri = Uri.parse(data.getAction());
+                init(dataUri);
+            }
+            break;
+        }
+    }
+}
diff --git a/src/com/android/camera/Wallpaper.java b/src/com/android/camera/Wallpaper.java
new file mode 100644
index 0000000..5c7d0e5
--- /dev/null
+++ b/src/com/android/camera/Wallpaper.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2007 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.camera;
+
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.MediaStore;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+
+/**
+ * Wallpaper picker for the camera application. This just redirects to the standard pick action.
+ */
+public class Wallpaper extends Activity {
+    private static final String LOG_TAG = "Camera";
+    static final int PHOTO_PICKED = 1;
+    static final int CROP_DONE = 2;
+
+    static final int SHOW_PROGRESS = 0;
+    static final int FINISH = 1;
+
+    static final String sDoLaunchIcicle = "do_launch";
+    static final String sTempFilePathIcicle = "temp_file_path";
+
+    private ProgressDialog mProgressDialog = null;
+    private boolean mDoLaunch = true;
+    private String mTempFilePath;
+
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case SHOW_PROGRESS: {
+                    CharSequence c = getText(R.string.wallpaper);
+                    mProgressDialog = ProgressDialog.show(Wallpaper.this, "", c, true, false);
+                    break;
+                }
+
+                case FINISH: {
+                    closeProgressDialog();
+                    setResult(RESULT_OK);
+                    finish();
+                    break;
+                }
+            }
+        }
+    };
+
+    static class SetWallpaperThread extends Thread {
+        private final Bitmap mBitmap;
+        private final Handler mHandler;
+        private final Context mContext;
+        private final File mFile;
+
+        public SetWallpaperThread(Bitmap bitmap, Handler handler, Context context, File file) {
+            mBitmap = bitmap;
+            mHandler = handler;
+            mContext = context;
+            mFile = file;
+        }
+
+        @Override
+        public void run() {
+            try {
+                mContext.setWallpaper(mBitmap);
+            } catch (IOException e) {
+                Log.e(LOG_TAG, "Failed to set wallpaper.", e);
+            } finally {
+                mHandler.sendEmptyMessage(FINISH);
+                mFile.delete();
+            }
+        }
+    }
+
+    private synchronized void closeProgressDialog() {
+        if (mProgressDialog != null) {
+            mProgressDialog.dismiss();
+            mProgressDialog = null;
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        if (icicle != null) {
+            mDoLaunch = icicle.getBoolean(sDoLaunchIcicle);
+            mTempFilePath = icicle.getString(sTempFilePathIcicle);
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle icicle) {
+        icicle.putBoolean(sDoLaunchIcicle, mDoLaunch);
+        icicle.putString(sTempFilePathIcicle, mTempFilePath);
+    }
+
+    @Override
+    protected void onPause() {
+        closeProgressDialog();
+        super.onPause();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        if (!mDoLaunch) {
+            return;
+        }
+        Uri imageToUse = getIntent().getData();
+        if (imageToUse != null) {
+            Intent intent = new Intent();
+            intent.setClassName("com.android.camera", "com.android.camera.CropImage");
+            intent.setData(imageToUse);
+            formatIntent(intent);
+            startActivityForResult(intent, CROP_DONE);
+        } else {
+            Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
+            intent.setType("image/*");
+            intent.putExtra("crop", "true");
+            formatIntent(intent);
+            startActivityForResult(intent, PHOTO_PICKED);
+        }
+    }
+
+    protected void formatIntent(Intent intent) {
+        // TODO: A temporary file is NOT necessary
+        // The CropImage intent should be able to set the wallpaper directly
+        // without writing to a file, which we then need to read here to write
+        // it again as the final wallpaper, this is silly
+        File f = getFileStreamPath("temp-wallpaper");
+        (new File(f.getParent())).mkdirs();
+        mTempFilePath = f.toString();
+        f.delete();
+
+        int width = getWallpaperDesiredMinimumWidth();
+        int height = getWallpaperDesiredMinimumHeight();
+        intent.putExtra("outputX",         width);
+        intent.putExtra("outputY",         height);
+        intent.putExtra("aspectX",         width);
+        intent.putExtra("aspectY",         height);
+        intent.putExtra("scale",           true);
+        intent.putExtra("noFaceDetection", true);
+        intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.parse("file:/" + mTempFilePath));
+        intent.putExtra("outputFormat",    Bitmap.CompressFormat.PNG.name());
+        // TODO: we should have an extra called "setWallpaper" to ask CropImage to
+        // set the cropped image as a wallpaper directly
+        // This means the SetWallpaperThread should be moved out of this class to CropImage
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if ((requestCode == PHOTO_PICKED || requestCode == CROP_DONE) && (resultCode == RESULT_OK)
+                && (data != null)) {
+            try {
+                File tempFile = new File(mTempFilePath);
+                InputStream s = new FileInputStream(tempFile);
+                Bitmap bitmap = BitmapFactory.decodeStream(s);
+                if (bitmap == null) {
+                    Log.e(LOG_TAG, "Failed to set wallpaper.  Couldn't get bitmap for path " + mTempFilePath);
+                } else {
+                    if (android.util.Config.LOGV)
+                        Log.v(LOG_TAG, "bitmap size is " + bitmap.getWidth() + " / " + bitmap.getHeight());
+                    mHandler.sendEmptyMessage(SHOW_PROGRESS);
+                    new SetWallpaperThread(bitmap, mHandler, this, tempFile).start();
+                }
+                mDoLaunch = false;
+            } catch (FileNotFoundException ex) {
+
+            } catch (IOException ex) {
+
+            }
+        } else {
+            setResult(RESULT_CANCELED);
+            finish();
+        }
+    }
+}
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 0000000..f5972cb
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,19 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+LOCAL_CERTIFICATE := media
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CameraTests
+
+LOCAL_INSTRUMENTATION_FOR := Camera
+
+include $(BUILD_PACKAGE)
+
+
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
new file mode 100644
index 0000000..1b7abd2
--- /dev/null
+++ b/tests/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.camera.tests">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="CameraLaunchPerformance"
+        android:targetPackage="com.android.camera"
+        android:label="Camera Launch Performance">
+    </instrumentation>
+    
+    <instrumentation android:name=".CameraStressTestRunner"
+         android:targetPackage="com.android.camera"
+         android:label="Camera Stress Test InstrumentationRunner">
+     </instrumentation>
+
+</manifest> 
diff --git a/tests/src/com/android/camera/CameraLaunchPerformance.java b/tests/src/com/android/camera/CameraLaunchPerformance.java
new file mode 100644
index 0000000..8c76f00
--- /dev/null
+++ b/tests/src/com/android/camera/CameraLaunchPerformance.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2007 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.camera.tests;
+
+import android.app.Activity;
+import android.test.LaunchPerformanceBase;
+import android.os.Bundle;
+
+import java.util.Map;
+
+/**
+ * Instrumentation class for Camera launch performance testing.
+ */
+public class CameraLaunchPerformance extends LaunchPerformanceBase {
+ 
+    public static final String LOG_TAG = "CameraLaunchPerformance";
+
+    public CameraLaunchPerformance() {
+        super();
+    }
+
+    @Override
+    public void onCreate(Bundle arguments) {
+        super.onCreate(arguments);
+
+        mIntent.setClassName(getTargetContext(), "com.android.camera.Camera");
+        start();
+    }
+
+    /**
+     * Calls LaunchApp and finish.
+     */
+    @Override
+    public void onStart() {
+        super.onStart();
+        LaunchApp();
+        finish(Activity.RESULT_OK, mResults);
+    }
+}
diff --git a/tests/src/com/android/camera/CameraStressTestRunner.java b/tests/src/com/android/camera/CameraStressTestRunner.java
new file mode 100755
index 0000000..e34204c
--- /dev/null
+++ b/tests/src/com/android/camera/CameraStressTestRunner.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2009 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.camera.tests;
+
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+import com.android.camera.tests.stress.SwitchPreview;
+import com.android.camera.tests.stress.ImageCapture;
+
+import junit.framework.TestSuite;
+
+
+/**
+ * Instrumentation Test Runner for all Camera tests.
+ *
+ * Running all tests:
+ *
+ * adb shell am instrument -w \
+ *    com.android.camera.tests/.CameraStressTestRunner
+ */
+
+public class CameraStressTestRunner extends InstrumentationTestRunner {
+
+    @Override
+    public TestSuite getAllTests() {
+        TestSuite suite = new InstrumentationTestSuite(this);
+        suite.addTestSuite(SwitchPreview.class);
+        suite.addTestSuite(ImageCapture.class);
+        return suite;
+    }
+
+    @Override
+    public ClassLoader getLoader() {
+        return CameraStressTestRunner.class.getClassLoader();
+    }
+}
diff --git a/tests/src/com/android/camera/stress/ImageCapture.java b/tests/src/com/android/camera/stress/ImageCapture.java
new file mode 100755
index 0000000..dbb1f64
--- /dev/null
+++ b/tests/src/com/android/camera/stress/ImageCapture.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2009 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.camera.tests.stress;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import com.android.camera.Camera;
+
+/**
+ * Junit / Instrumentation test case for camera test
+ * 
+ */
+
+public class ImageCapture extends ActivityInstrumentationTestCase2 <Camera> {
+    private String TAG = "ImageCapture";
+    private static final int TOTAL_NUMBER_OF_IMAGECAPTURE = 100;
+    private static final int TOTAL_NUMBER_OF_VIDEOCAPTURE = 100;
+    private static final long WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN = 1000;
+    private static final long WAIT_FOR_VIDEO_CAPTURE_TO_BE_TAKEN = 50000; //50seconds
+    private static final long WAIT_FOR_PREVIEW = 1000; //1 seconds
+
+    public ImageCapture() {
+        super("com.android.camera", Camera.class);       
+    }
+    
+    @Override 
+    protected void setUp() throws Exception {  
+        getActivity();
+        super.setUp();     
+    }
+    
+    @Override 
+    protected void tearDown() throws Exception {     
+        super.tearDown();              
+    }
+        
+    @LargeTest
+    public void testImageCapture() {
+        Instrumentation inst = getInstrumentation();
+        try {
+            for (int i = 0; i < TOTAL_NUMBER_OF_IMAGECAPTURE; i++) {
+                Thread.sleep(WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN);
+                inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_UP);
+                inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+                Thread.sleep(WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN);
+            }
+        } catch (Exception e) {
+            Log.v(TAG, e.toString());
+        }
+            assertTrue("testImageCapture", true);
+    }
+    
+    @LargeTest
+    public void testVideoCapture() {
+        Instrumentation inst = getInstrumentation();
+        //Switch to the video mode
+        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
+        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+        try {
+            for (int i = 0; i < TOTAL_NUMBER_OF_VIDEOCAPTURE; i++) {
+                Thread.sleep(WAIT_FOR_PREVIEW);
+                inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_UP);
+                //record an video
+                inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+                Thread.sleep(WAIT_FOR_VIDEO_CAPTURE_TO_BE_TAKEN);
+                inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+                Thread.sleep(WAIT_FOR_PREVIEW);
+                inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+            }
+        } catch (Exception e) {
+            Log.v(TAG, e.toString());
+        }
+            assertTrue("testVideoCapture", true);
+    }
+
+}
+    
diff --git a/tests/src/com/android/camera/stress/SwitchPreview.java b/tests/src/com/android/camera/stress/SwitchPreview.java
new file mode 100755
index 0000000..c00e553
--- /dev/null
+++ b/tests/src/com/android/camera/stress/SwitchPreview.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2009 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.camera.tests.stress;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import com.android.camera.Camera;
+import com.android.camera.VideoCamera;
+
+/**
+ * Junit / Instrumentation test case for camera test
+ * 
+ */
+
+public class SwitchPreview extends ActivityInstrumentationTestCase2 <VideoCamera>{
+    private String TAG = "SwitchPreview";
+    private static final int TOTAL_NUMBER_OF_SWITCHING = 200;
+    private static final long WAIT_FOR_PREVIEW = 2000;
+    
+    
+    public SwitchPreview() {
+        super("com.android.camera", VideoCamera.class);      
+    }
+    
+    @Override 
+    protected void setUp() throws Exception {  
+        getActivity();
+        super.setUp();
+    }
+    
+    @Override 
+    protected void tearDown() throws Exception {   
+        getActivity().finish();
+        super.tearDown();              
+    }
+        
+    @LargeTest
+    public void testSwitchMode() {
+        //Switching the video and the video recorder mode
+        Instrumentation inst = getInstrumentation();
+        try{
+            for (int i=0; i< TOTAL_NUMBER_OF_SWITCHING; i++) {
+                Thread.sleep(WAIT_FOR_PREVIEW);
+                inst.sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
+                inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_LEFT);
+                inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+                Thread.sleep(WAIT_FOR_PREVIEW);
+            }
+        } catch (Exception e){
+            Log.v(TAG, e.toString());
+        }
+            assertTrue("testSwitchMode",true);
+    }
+}
+