Merge "Low-level exif parser" into gb-ub-photos-arches
diff --git a/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java b/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
index 2937ed2..0d349f3 100644
--- a/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
+++ b/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
@@ -109,7 +109,10 @@
     public static final boolean HAS_TIME_LAPSE_RECORDING =
             Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
 
-    public static final boolean HAS_CAMCORDER_ZOOM =
+    public static final boolean HAS_ZOOM_WHEN_RECORDING =
+            Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH;
+
+    public static final boolean HAS_CAMERA_FOCUS_AREA =
             Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH;
 
     public static int getIntFieldIfExists(Class<?> klass, String fieldName,
diff --git a/src/com/android/gallery3d/app/ActivityState.java b/src/com/android/gallery3d/app/ActivityState.java
index c870855..ca0816c 100644
--- a/src/com/android/gallery3d/app/ActivityState.java
+++ b/src/com/android/gallery3d/app/ActivityState.java
@@ -19,12 +19,15 @@
 import android.app.ActionBar;
 import android.app.Activity;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Configuration;
 import android.os.BatteryManager;
 import android.os.Bundle;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.Window;
@@ -57,6 +60,9 @@
         public Intent resultData;
     }
 
+    protected boolean mHapticsEnabled;
+    private ContentResolver mContentResolver;
+
     private boolean mDestroyed = false;
     private boolean mPlugged = false;
     boolean mIsFinishing = false;
@@ -71,6 +77,7 @@
     void initialize(GalleryActivity activity, Bundle data) {
         mActivity = activity;
         mData = data;
+        mContentResolver = activity.getAndroidContext().getContentResolver();
     }
 
     public Bundle getData() {
@@ -167,6 +174,14 @@
             filter.addAction(Intent.ACTION_BATTERY_CHANGED);
             activity.registerReceiver(mPowerIntentReceiver, filter);
         }
+
+        try {
+            mHapticsEnabled = Settings.System.getInt(mContentResolver,
+                    Settings.System.HAPTIC_FEEDBACK_ENABLED) != 0;
+        } catch (SettingNotFoundException e) {
+            mHapticsEnabled = false;
+        }
+
         onResume();
 
         // the transition store should be cleared after resume;
diff --git a/src/com/android/gallery3d/app/AlbumDataLoader.java b/src/com/android/gallery3d/app/AlbumDataLoader.java
index a99cf93..71a5756 100644
--- a/src/com/android/gallery3d/app/AlbumDataLoader.java
+++ b/src/com/android/gallery3d/app/AlbumDataLoader.java
@@ -26,7 +26,7 @@
 import com.android.gallery3d.data.MediaItem;
 import com.android.gallery3d.data.MediaObject;
 import com.android.gallery3d.data.MediaSet;
-import com.android.gallery3d.ui.AlbumSlotRenderer;
+import com.android.gallery3d.data.Path;
 import com.android.gallery3d.ui.SynchronizedHandler;
 
 import java.util.ArrayList;
@@ -133,6 +133,18 @@
         return mSize;
     }
 
+    // Returns the index of the MediaItem with the given path or
+    // -1 if the path is not cached
+    public int findItem(Path id) {
+        for (int i = mContentStart; i < mContentEnd; i++) {
+            MediaItem item = mData[i % DATA_CACHE_SIZE];
+            if (item != null && id == item.getPath()) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
     private void clearSlot(int slotIndex) {
         mData[slotIndex] = null;
         mItemVersion[slotIndex] = MediaObject.INVALID_DATA_VERSION;
diff --git a/src/com/android/gallery3d/app/AlbumPage.java b/src/com/android/gallery3d/app/AlbumPage.java
index e773691..b4e600b 100644
--- a/src/com/android/gallery3d/app/AlbumPage.java
+++ b/src/com/android/gallery3d/app/AlbumPage.java
@@ -240,7 +240,6 @@
             MediaItem item = mAlbumDataAdapter.get(slotIndex);
             if (item == null) return; // Item not ready yet, ignore the click
             mSelectionManager.toggle(item.getPath());
-            mDetailsSource.findIndex(slotIndex);
             mSlotView.invalidate();
         } else {
             // Show pressed-up animation for the single-tap.
@@ -311,7 +310,6 @@
         if (item == null) return;
         mSelectionManager.setAutoLeaveSelectionMode(true);
         mSelectionManager.toggle(item.getPath());
-        mDetailsSource.findIndex(slotIndex);
         mSlotView.invalidate();
     }
 
@@ -613,7 +611,7 @@
         switch (mode) {
             case SelectionManager.ENTER_SELECTION_MODE: {
                 mActionMode = mActionModeHandler.startActionMode();
-                mVibrator.vibrate(100);
+                if(mHapticsEnabled) mVibrator.vibrate(100);
                 break;
             }
             case SelectionManager.LEAVE_SELECTION_MODE: {
@@ -699,25 +697,14 @@
             return mAlbumDataAdapter.size();
         }
 
-        public int getIndex() {
-            return mIndex;
-        }
-
-        // If requested index is out of active window, suggest a valid index.
-        // If there is no valid index available, return -1.
-        public int findIndex(int indexHint) {
-            if (mAlbumDataAdapter.isActive(indexHint)) {
-                mIndex = indexHint;
-            } else {
-                mIndex = mAlbumDataAdapter.getActiveStart();
-                if (!mAlbumDataAdapter.isActive(mIndex)) {
-                    return -1;
-                }
-            }
+        public int setIndex() {
+            Path id = mSelectionManager.getSelected(false).get(0);
+            mIndex = mAlbumDataAdapter.findItem(id);
             return mIndex;
         }
 
         public MediaDetails getDetails() {
+            // this relies on setIndex() being called beforehand
             MediaObject item = mAlbumDataAdapter.get(mIndex);
             if (item != null) {
                 mAlbumView.setHighlightItemPath(item.getPath());
diff --git a/src/com/android/gallery3d/app/AlbumSetDataLoader.java b/src/com/android/gallery3d/app/AlbumSetDataLoader.java
index 39d4a8b..33a30bc 100644
--- a/src/com/android/gallery3d/app/AlbumSetDataLoader.java
+++ b/src/com/android/gallery3d/app/AlbumSetDataLoader.java
@@ -27,6 +27,7 @@
 import com.android.gallery3d.data.MediaItem;
 import com.android.gallery3d.data.MediaObject;
 import com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.data.Path;
 import com.android.gallery3d.ui.SynchronizedHandler;
 
 import java.util.Arrays;
@@ -149,6 +150,19 @@
         return mSize;
     }
 
+    // Returns the index of the MediaSet with the given path or
+    // -1 if the path is not cached
+    public int findSet(Path id) {
+        int length = mData.length;
+        for (int i = mContentStart; i < mContentEnd; i++) {
+            MediaSet set = mData[i % length];
+            if (set != null && id == set.getPath()) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
     private void clearSlot(int slotIndex) {
         mData[slotIndex] = null;
         mCoverItem[slotIndex] = null;
diff --git a/src/com/android/gallery3d/app/AlbumSetPage.java b/src/com/android/gallery3d/app/AlbumSetPage.java
index aa72eb8..1df0734 100644
--- a/src/com/android/gallery3d/app/AlbumSetPage.java
+++ b/src/com/android/gallery3d/app/AlbumSetPage.java
@@ -24,7 +24,6 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.Vibrator;
-import android.provider.MediaStore;
 import android.view.ActionMode;
 import android.view.Menu;
 import android.view.MenuInflater;
@@ -35,6 +34,7 @@
 import com.android.gallery3d.common.Utils;
 import com.android.gallery3d.data.DataManager;
 import com.android.gallery3d.data.MediaDetails;
+import com.android.gallery3d.data.MediaItem;
 import com.android.gallery3d.data.MediaObject;
 import com.android.gallery3d.data.MediaSet;
 import com.android.gallery3d.data.Path;
@@ -250,7 +250,6 @@
         if (set == null) return;
         mSelectionManager.setAutoLeaveSelectionMode(true);
         mSelectionManager.toggle(set.getPath());
-        mDetailsSource.findIndex(slotIndex);
         mSlotView.invalidate();
     }
 
@@ -533,7 +532,7 @@
             case SelectionManager.ENTER_SELECTION_MODE: {
                 mActionBar.disableClusterMenu(true);
                 mActionMode = mActionModeHandler.startActionMode();
-                mVibrator.vibrate(100);
+                if(mHapticsEnabled) mVibrator.vibrate(100);
                 break;
             }
             case SelectionManager.LEAVE_SELECTION_MODE: {
@@ -627,22 +626,9 @@
         }
 
         @Override
-        public int getIndex() {
-            return mIndex;
-        }
-
-        // If requested index is out of active window, suggest a valid index.
-        // If there is no valid index available, return -1.
-        @Override
-        public int findIndex(int indexHint) {
-            if (mAlbumSetDataAdapter.isActive(indexHint)) {
-                mIndex = indexHint;
-            } else {
-                mIndex = mAlbumSetDataAdapter.getActiveStart();
-                if (!mAlbumSetDataAdapter.isActive(mIndex)) {
-                    return -1;
-                }
-            }
+        public int setIndex() {
+            Path id = mSelectionManager.getSelected(false).get(0);
+            mIndex = mAlbumSetDataAdapter.findSet(id);
             return mIndex;
         }
 
diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java
index 3b6a770..950f8c5 100644
--- a/src/com/android/gallery3d/app/PhotoPage.java
+++ b/src/com/android/gallery3d/app/PhotoPage.java
@@ -352,7 +352,7 @@
         updateMenuOperations();
         updateTitle();
         if (mShowDetails) {
-            mDetailsHelper.reloadDetails(mModel.getCurrentIndex());
+            mDetailsHelper.reloadDetails();
         }
         if ((photo.getSupportedOperations() & MediaItem.SUPPORT_SHARE) != 0) {
             updateShareURI(photo.getPath());
@@ -616,7 +616,7 @@
                 if (mShowDetails) {
                     hideDetails();
                 } else {
-                    showDetails(currentIndex);
+                    showDetails();
                 }
                 return true;
             }
@@ -647,7 +647,7 @@
         mDetailsHelper.hide();
     }
 
-    private void showDetails(int index) {
+    private void showDetails() {
         mShowDetails = true;
         if (mDetailsHelper == null) {
             mDetailsHelper = new DetailsHelper(mActivity, mRootPane, new MyDetailsSource());
@@ -658,7 +658,6 @@
                 }
             });
         }
-        mDetailsHelper.reloadDetails(index);
         mDetailsHelper.show();
     }
 
@@ -914,7 +913,6 @@
     }
 
     private class MyDetailsSource implements DetailsSource {
-        private int mIndex;
 
         @Override
         public MediaDetails getDetails() {
@@ -927,14 +925,8 @@
         }
 
         @Override
-        public int findIndex(int indexHint) {
-            mIndex = indexHint;
-            return indexHint;
-        }
-
-        @Override
-        public int getIndex() {
-            return mIndex;
+        public int setIndex() {
+            return mModel.getCurrentIndex();
         }
     }
 }
diff --git a/src/com/android/gallery3d/data/MtpClient.java b/src/com/android/gallery3d/data/MtpClient.java
index 2d58df2..dd65f9e 100644
--- a/src/com/android/gallery3d/data/MtpClient.java
+++ b/src/com/android/gallery3d/data/MtpClient.java
@@ -16,6 +16,7 @@
 
 package com.android.gallery3d.data;
 
+import android.annotation.TargetApi;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -31,6 +32,8 @@
 import android.mtp.MtpStorageInfo;
 import android.util.Log;
 
+import com.android.gallery3d.common.ApiHelper;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -40,6 +43,7 @@
  * It listens for MTP devices being attached and removed from the USB host bus
  * and notifies the application when the MTP device list changes.
  */
+@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB_MR1)
 public class MtpClient {
 
     private static final String TAG = "MtpClient";
diff --git a/src/com/android/gallery3d/data/MtpContext.java b/src/com/android/gallery3d/data/MtpContext.java
index 0749020..056211c 100644
--- a/src/com/android/gallery3d/data/MtpContext.java
+++ b/src/com/android/gallery3d/data/MtpContext.java
@@ -1,5 +1,6 @@
 package com.android.gallery3d.data;
 
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.media.MediaScannerConnection;
 import android.media.MediaScannerConnection.MediaScannerConnectionClient;
@@ -10,6 +11,7 @@
 import android.widget.Toast;
 
 import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.util.BucketNames;
 import com.android.gallery3d.util.GalleryUtils;
 
@@ -17,6 +19,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
+@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB_MR1)
 public class MtpContext implements MtpClient.Listener {
     private static final String TAG = "MtpContext";
 
diff --git a/src/com/android/gallery3d/data/MtpDevice.java b/src/com/android/gallery3d/data/MtpDevice.java
index f43ae2b..b233b3c 100644
--- a/src/com/android/gallery3d/data/MtpDevice.java
+++ b/src/com/android/gallery3d/data/MtpDevice.java
@@ -16,6 +16,7 @@
 
 package com.android.gallery3d.data;
 
+import android.annotation.TargetApi;
 import android.hardware.usb.UsbDevice;
 import android.mtp.MtpConstants;
 import android.mtp.MtpObjectInfo;
@@ -24,10 +25,12 @@
 import android.util.Log;
 
 import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.common.ApiHelper;
 
 import java.util.ArrayList;
 import java.util.List;
 
+@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB_MR1)
 public class MtpDevice extends MediaSet {
     private static final String TAG = "MtpDevice";
 
diff --git a/src/com/android/gallery3d/data/MtpDeviceSet.java b/src/com/android/gallery3d/data/MtpDeviceSet.java
index 1f26511..bc4bc63 100644
--- a/src/com/android/gallery3d/data/MtpDeviceSet.java
+++ b/src/com/android/gallery3d/data/MtpDeviceSet.java
@@ -16,6 +16,7 @@
 
 package com.android.gallery3d.data;
 
+import android.annotation.TargetApi;
 import android.mtp.MtpDeviceInfo;
 import android.net.Uri;
 import android.os.Handler;
@@ -23,6 +24,7 @@
 
 import com.android.gallery3d.R;
 import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.util.Future;
 import com.android.gallery3d.util.FutureListener;
 import com.android.gallery3d.util.MediaSetUtils;
@@ -34,6 +36,7 @@
 import java.util.List;
 
 // MtpDeviceSet -- MtpDevice -- MtpImage
+@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB_MR1)
 public class MtpDeviceSet extends MediaSet
         implements FutureListener<ArrayList<MediaSet>> {
     private static final String TAG = "MtpDeviceSet";
diff --git a/src/com/android/gallery3d/data/MtpImage.java b/src/com/android/gallery3d/data/MtpImage.java
index 96b4d9f..1d0f88d 100644
--- a/src/com/android/gallery3d/data/MtpImage.java
+++ b/src/com/android/gallery3d/data/MtpImage.java
@@ -16,6 +16,7 @@
 
 package com.android.gallery3d.data;
 
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.BitmapRegionDecoder;
@@ -25,6 +26,7 @@
 import android.util.Log;
 
 import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.provider.GalleryProvider;
 import com.android.gallery3d.util.ThreadPool;
 import com.android.gallery3d.util.ThreadPool.Job;
@@ -33,6 +35,7 @@
 import java.text.DateFormat;
 import java.util.Date;
 
+@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB_MR1)
 public class MtpImage extends MediaItem {
     private static final String TAG = "MtpImage";
 
diff --git a/src/com/android/gallery3d/ui/DetailsHelper.java b/src/com/android/gallery3d/ui/DetailsHelper.java
index 245145f..f86ffbe 100644
--- a/src/com/android/gallery3d/ui/DetailsHelper.java
+++ b/src/com/android/gallery3d/ui/DetailsHelper.java
@@ -29,8 +29,7 @@
 
     public interface DetailsSource {
         public int size();
-        public int getIndex();
-        public int findIndex(int indexHint);
+        public int setIndex();
         public MediaDetails getDetails();
     }
 
@@ -39,7 +38,7 @@
     }
 
     public interface DetailsViewContainer {
-        public void reloadDetails(int indexHint);
+        public void reloadDetails();
         public void setCloseListener(CloseListener listener);
         public void show();
         public void hide();
@@ -58,8 +57,8 @@
         }
     }
 
-    public void reloadDetails(int indexHint) {
-        mContainer.reloadDetails(indexHint);
+    public void reloadDetails() {
+        mContainer.reloadDetails();
     }
 
     public void setCloseListener(CloseListener listener) {
diff --git a/src/com/android/gallery3d/ui/DialogDetailsView.java b/src/com/android/gallery3d/ui/DialogDetailsView.java
index 60ced03..c90ab40 100644
--- a/src/com/android/gallery3d/ui/DialogDetailsView.java
+++ b/src/com/android/gallery3d/ui/DialogDetailsView.java
@@ -60,7 +60,7 @@
     }
 
     public void show() {
-        reloadDetails(mSource.getIndex());
+        reloadDetails();
         mDialog.show();
     }
 
@@ -68,8 +68,8 @@
         mDialog.hide();
     }
 
-    public void reloadDetails(int indexHint) {
-        int index = mSource.findIndex(indexHint);
+    public void reloadDetails() {
+        int index = mSource.setIndex();
         if (index == -1) return;
         MediaDetails details = mSource.getDetails();
         if (details != null) {
diff --git a/src/com/android/gallery3d/ui/PhotoView.java b/src/com/android/gallery3d/ui/PhotoView.java
index e44905d..2525433 100644
--- a/src/com/android/gallery3d/ui/PhotoView.java
+++ b/src/com/android/gallery3d/ui/PhotoView.java
@@ -1565,6 +1565,7 @@
 
     public boolean switchWithCaptureAnimation(int offset) {
         GLRoot root = getGLRoot();
+        if(root == null) return false;
         root.lockRenderThread();
         try {
             return switchWithCaptureAnimationLocked(offset);
diff --git a/src/com/android/gallery3d/ui/ScrollerHelper.java b/src/com/android/gallery3d/ui/ScrollerHelper.java
index 1ff633a..aa68d19 100644
--- a/src/com/android/gallery3d/ui/ScrollerHelper.java
+++ b/src/com/android/gallery3d/ui/ScrollerHelper.java
@@ -18,8 +18,8 @@
 
 import android.content.Context;
 import android.view.ViewConfiguration;
-import android.widget.OverScroller;
 
+import com.android.gallery3d.common.OverScroller;
 import com.android.gallery3d.common.Utils;
 
 public class ScrollerHelper {