am 14b961f6: Reconcile with jb-release

* commit '14b961f6ef4ab2177765c3e64676f0735d934158':
diff --git a/Android.mk b/Android.mk
index ea71b39..b0ebf89 100644
--- a/Android.mk
+++ b/Android.mk
@@ -18,7 +18,7 @@
 
 LOCAL_OVERRIDES_PACKAGES := Gallery Gallery3D GalleryNew3D
 
-#LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := current
 
 LOCAL_JNI_SHARED_LIBRARIES := libjni_mosaic libjni_eglfence
 
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 2a69ce1..c7a693b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -7,7 +7,7 @@
 
     <original-package android:name="com.android.gallery3d" />
 
-    <uses-sdk android:minSdkVersion="14" />
+    <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="16" />
 
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
@@ -322,6 +322,7 @@
                 <data android:scheme="package"/>
             </intent-filter>
         </receiver>
+        <service android:name="com.android.gallery3d.app.PackagesMonitor$AsyncService"/>
         <receiver android:name="com.android.camera.CameraButtonIntentReceiver">
             <intent-filter>
                 <action android:name="android.intent.action.CAMERA_BUTTON"/>
diff --git a/gallerycommon/Android.mk b/gallerycommon/Android.mk
index a942de2..fb81dd9 100644
--- a/gallerycommon/Android.mk
+++ b/gallerycommon/Android.mk
@@ -22,6 +22,6 @@
 
 LOCAL_MODULE := com.android.gallery3d.common2
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_SDK_VERSION := 8
+LOCAL_SDK_VERSION := current
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java b/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
new file mode 100644
index 0000000..85b0925
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.common;
+
+import android.os.Build;
+import android.provider.MediaStore.MediaColumns;
+import android.view.View;
+
+public class ApiHelper {
+    public static interface VERSION_CODES {
+        // These value are copied from Build.VERSION_CODES
+        public static final int GINGERBREAD_MR1 = 10;
+        public static final int HONEYCOMB = 11;
+        public static final int HONEYCOMB_MR1 = 12;
+        public static final int HONEYCOMB_MR2 = 13;
+        public static final int ICE_CREAM_SANDWICH = 14;
+        public static final int ICE_CREAM_SANDWICH_MR1 = 15;
+        public static final int JELLY_BEAN = 16;
+    }
+
+    public static final boolean HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE =
+            hasField(View.class, "SYSTEM_UI_FLAG_LAYOUT_STABLE");
+
+    public static final boolean HAS_VIEW_SYSTEM_UI_FLAG_HIDE_NAVIGATION =
+            hasField(View.class, "SYSTEM_UI_FLAG_HIDE_NAVIGATION");
+
+    public static final boolean HAS_MEDIA_COLUMNS_WIDTH_AND_HEIGHT =
+            hasField(MediaColumns.class, "WIDTH");
+
+    public static final boolean HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER =
+            Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
+
+    private static boolean hasField(Class<?> klass, String fieldName) {
+        try {
+            klass.getDeclaredField(fieldName);
+            return true;
+        } catch (NoSuchFieldException e) {
+            return false;
+        }
+    }
+}
diff --git a/gallerycommon/src/com/android/gallery3d/common/LongSparseArray.java b/gallerycommon/src/com/android/gallery3d/common/LongSparseArray.java
new file mode 100644
index 0000000..b3298e6
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/common/LongSparseArray.java
@@ -0,0 +1,368 @@
+/*
+ * 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.gallery3d.common;
+
+
+// Copied from android.util.LongSparseArray for unbundling
+
+/**
+ * SparseArray mapping longs to Objects.  Unlike a normal array of Objects,
+ * there can be gaps in the indices.  It is intended to be more efficient
+ * than using a HashMap to map Longs to Objects.
+ */
+public class LongSparseArray<E> implements Cloneable {
+    private static final Object DELETED = new Object();
+    private boolean mGarbage = false;
+
+    private long[] mKeys;
+    private Object[] mValues;
+    private int mSize;
+
+    /**
+     * Creates a new LongSparseArray containing no mappings.
+     */
+    public LongSparseArray() {
+        this(10);
+    }
+
+    /**
+     * Creates a new LongSparseArray containing no mappings that will not
+     * require any additional memory allocation to store the specified
+     * number of mappings.
+     */
+    public LongSparseArray(int initialCapacity) {
+        initialCapacity = idealLongArraySize(initialCapacity);
+
+        mKeys = new long[initialCapacity];
+        mValues = new Object[initialCapacity];
+        mSize = 0;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public LongSparseArray<E> clone() {
+        LongSparseArray<E> clone = null;
+        try {
+            clone = (LongSparseArray<E>) super.clone();
+            clone.mKeys = mKeys.clone();
+            clone.mValues = mValues.clone();
+        } catch (CloneNotSupportedException cnse) {
+            /* ignore */
+        }
+        return clone;
+    }
+
+    /**
+     * Gets the Object mapped from the specified key, or <code>null</code>
+     * if no such mapping has been made.
+     */
+    public E get(long key) {
+        return get(key, null);
+    }
+
+    /**
+     * Gets the Object mapped from the specified key, or the specified Object
+     * if no such mapping has been made.
+     */
+    @SuppressWarnings("unchecked")
+    public E get(long key, E valueIfKeyNotFound) {
+        int i = binarySearch(mKeys, 0, mSize, key);
+
+        if (i < 0 || mValues[i] == DELETED) {
+            return valueIfKeyNotFound;
+        } else {
+            return (E) mValues[i];
+        }
+    }
+
+    /**
+     * Removes the mapping from the specified key, if there was any.
+     */
+    public void delete(long key) {
+        int i = binarySearch(mKeys, 0, mSize, key);
+
+        if (i >= 0) {
+            if (mValues[i] != DELETED) {
+                mValues[i] = DELETED;
+                mGarbage = true;
+            }
+        }
+    }
+
+    /**
+     * Alias for {@link #delete(long)}.
+     */
+    public void remove(long key) {
+        delete(key);
+    }
+
+    /**
+     * Removes the mapping at the specified index.
+     */
+    public void removeAt(int index) {
+        if (mValues[index] != DELETED) {
+            mValues[index] = DELETED;
+            mGarbage = true;
+        }
+    }
+
+    private void gc() {
+        // Log.e("SparseArray", "gc start with " + mSize);
+
+        int n = mSize;
+        int o = 0;
+        long[] keys = mKeys;
+        Object[] values = mValues;
+
+        for (int i = 0; i < n; i++) {
+            Object val = values[i];
+
+            if (val != DELETED) {
+                if (i != o) {
+                    keys[o] = keys[i];
+                    values[o] = val;
+                    values[i] = null;
+                }
+
+                o++;
+            }
+        }
+
+        mGarbage = false;
+        mSize = o;
+
+        // Log.e("SparseArray", "gc end with " + mSize);
+    }
+
+    /**
+     * Adds a mapping from the specified key to the specified value,
+     * replacing the previous mapping from the specified key if there
+     * was one.
+     */
+    public void put(long key, E value) {
+        int i = binarySearch(mKeys, 0, mSize, key);
+
+        if (i >= 0) {
+            mValues[i] = value;
+        } else {
+            i = ~i;
+
+            if (i < mSize && mValues[i] == DELETED) {
+                mKeys[i] = key;
+                mValues[i] = value;
+                return;
+            }
+
+            if (mGarbage && mSize >= mKeys.length) {
+                gc();
+
+                // Search again because indices may have changed.
+                i = ~binarySearch(mKeys, 0, mSize, key);
+            }
+
+            if (mSize >= mKeys.length) {
+                int n = idealLongArraySize(mSize + 1);
+
+                long[] nkeys = new long[n];
+                Object[] nvalues = new Object[n];
+
+                // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
+                System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+                System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+
+                mKeys = nkeys;
+                mValues = nvalues;
+            }
+
+            if (mSize - i != 0) {
+                // Log.e("SparseArray", "move " + (mSize - i));
+                System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
+                System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
+            }
+
+            mKeys[i] = key;
+            mValues[i] = value;
+            mSize++;
+        }
+    }
+
+    /**
+     * Returns the number of key-value mappings that this LongSparseArray
+     * currently stores.
+     */
+    public int size() {
+        if (mGarbage) {
+            gc();
+        }
+
+        return mSize;
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the key from the <code>index</code>th key-value mapping that this
+     * LongSparseArray stores.
+     */
+    public long keyAt(int index) {
+        if (mGarbage) {
+            gc();
+        }
+
+        return mKeys[index];
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the value from the <code>index</code>th key-value mapping that this
+     * LongSparseArray stores.
+     */
+    @SuppressWarnings("unchecked")
+    public E valueAt(int index) {
+        if (mGarbage) {
+            gc();
+        }
+
+        return (E) mValues[index];
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, sets a new
+     * value for the <code>index</code>th key-value mapping that this
+     * LongSparseArray stores.
+     */
+    public void setValueAt(int index, E value) {
+        if (mGarbage) {
+            gc();
+        }
+
+        mValues[index] = value;
+    }
+
+    /**
+     * Returns the index for which {@link #keyAt} would return the
+     * specified key, or a negative number if the specified
+     * key is not mapped.
+     */
+    public int indexOfKey(long key) {
+        if (mGarbage) {
+            gc();
+        }
+
+        return binarySearch(mKeys, 0, mSize, key);
+    }
+
+    /**
+     * Returns an index for which {@link #valueAt} would return the
+     * specified key, or a negative number if no keys map to the
+     * specified value.
+     * Beware that this is a linear search, unlike lookups by key,
+     * and that multiple keys can map to the same value and this will
+     * find only one of them.
+     */
+    public int indexOfValue(E value) {
+        if (mGarbage) {
+            gc();
+        }
+
+        for (int i = 0; i < mSize; i++)
+            if (mValues[i] == value)
+                return i;
+
+        return -1;
+    }
+
+    /**
+     * Removes all key-value mappings from this LongSparseArray.
+     */
+    public void clear() {
+        int n = mSize;
+        Object[] values = mValues;
+
+        for (int i = 0; i < n; i++) {
+            values[i] = null;
+        }
+
+        mSize = 0;
+        mGarbage = false;
+    }
+
+    /**
+     * Puts a key/value pair into the array, optimizing for the case where
+     * the key is greater than all existing keys in the array.
+     */
+    public void append(long key, E value) {
+        if (mSize != 0 && key <= mKeys[mSize - 1]) {
+            put(key, value);
+            return;
+        }
+
+        if (mGarbage && mSize >= mKeys.length) {
+            gc();
+        }
+
+        int pos = mSize;
+        if (pos >= mKeys.length) {
+            int n = idealLongArraySize(pos + 1);
+
+            long[] nkeys = new long[n];
+            Object[] nvalues = new Object[n];
+
+            // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
+            System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+            System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+
+            mKeys = nkeys;
+            mValues = nvalues;
+        }
+
+        mKeys[pos] = key;
+        mValues[pos] = value;
+        mSize = pos + 1;
+    }
+
+    private static int binarySearch(long[] a, int start, int len, long key) {
+        int high = start + len, low = start - 1, guess;
+
+        while (high - low > 1) {
+            guess = (high + low) / 2;
+
+            if (a[guess] < key)
+                low = guess;
+            else
+                high = guess;
+        }
+
+        if (high == start + len)
+            return ~(start + len);
+        else if (a[high] == key)
+            return high;
+        else
+            return ~high;
+    }
+
+    private static int idealByteArraySize(int need) {
+        for (int i = 4; i < 32; i++)
+            if (need <= (1 << i) - 12)
+                return (1 << i) - 12;
+
+        return need;
+    }
+
+    public static int idealLongArraySize(int need) {
+        return idealByteArraySize(need * 8) / 8;
+    }
+}
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 899c1e6..0000c61 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -445,4 +445,10 @@
 
     <!-- Web address for gallery help.  DO NOT TRANSLATE -->
     <string name="help_url_gallery_main" translatable="false"></string>
+
+    <!-- The tilte of a dialog showing there is no external storage. [CHAR LIMIT=20] -->
+    <string name="no_external_storage_title">No Storage</string>
+
+    <!-- The message of a dialog showing there is no external storage. [CHAR LIMIT=none] -->
+    <string name="no_external_storage">No external storage available</string>
 </resources>
diff --git a/src/com/android/gallery3d/app/AbstractGalleryActivity.java b/src/com/android/gallery3d/app/AbstractGalleryActivity.java
index 144485d..0987ac3 100644
--- a/src/com/android/gallery3d/app/AbstractGalleryActivity.java
+++ b/src/com/android/gallery3d/app/AbstractGalleryActivity.java
@@ -143,8 +143,8 @@
             };
             mAlertDialog = new AlertDialog.Builder(this)
                     .setIconAttribute(android.R.attr.alertDialogIcon)
-                    .setTitle("No Storage")
-                    .setMessage("No external storage available.")
+                    .setTitle(R.string.no_external_storage_title)
+                    .setMessage(R.string.no_external_storage)
                     .setNegativeButton(android.R.string.cancel, onClick)
                     .setOnCancelListener(onCancel)
                     .show();
diff --git a/src/com/android/gallery3d/app/CropImage.java b/src/com/android/gallery3d/app/CropImage.java
index 4f450d8..f264697 100644
--- a/src/com/android/gallery3d/app/CropImage.java
+++ b/src/com/android/gallery3d/app/CropImage.java
@@ -16,6 +16,7 @@
 
 package com.android.gallery3d.app;
 
+import android.annotation.TargetApi;
 import android.app.ActionBar;
 import android.app.ProgressDialog;
 import android.app.WallpaperManager;
@@ -45,6 +46,7 @@
 import android.widget.Toast;
 
 import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.common.BitmapUtils;
 import com.android.gallery3d.common.Utils;
 import com.android.gallery3d.data.DataManager;
@@ -96,10 +98,6 @@
     private static final int DEFAULT_COMPRESS_QUALITY = 90;
     private static final String TIME_STAMP_NAME = "'IMG'_yyyyMMdd_HHmmss";
 
-    // Change these to Images.Media.WIDTH/HEIGHT after they are unhidden.
-    private static final String WIDTH = "width";
-    private static final String HEIGHT = "height";
-
     public static final String KEY_RETURN_DATA = "return-data";
     public static final String KEY_CROPPED_RECT = "cropped-rect";
     public static final String KEY_ASPECT_X = "aspectX";
@@ -371,6 +369,15 @@
         }
     }
 
+    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+    private static void setImageSize(ContentValues values, int width, int height) {
+        // The two fields are available since ICS but got published in JB
+        if (ApiHelper.HAS_MEDIA_COLUMNS_WIDTH_AND_HEIGHT) {
+            values.put(Images.Media.WIDTH, width);
+            values.put(Images.Media.HEIGHT, height);
+        }
+    }
+
     private Uri savePicasaImage(JobContext jc, Bitmap cropped) {
         if (!DOWNLOAD_BUCKET.isDirectory() && !DOWNLOAD_BUCKET.mkdirs()) {
             throw new RuntimeException("cannot create download folder");
@@ -395,8 +402,7 @@
         values.put(Images.Media.ORIENTATION, 0);
         values.put(Images.Media.DATA, output.getAbsolutePath());
         values.put(Images.Media.SIZE, output.length());
-        values.put(WIDTH, cropped.getWidth());
-        values.put(HEIGHT, cropped.getHeight());
+        setImageSize(values, cropped.getWidth(), cropped.getHeight());
 
         double latitude = PicasaSource.getLatitude(mMediaItem);
         double longitude = PicasaSource.getLongitude(mMediaItem);
@@ -434,8 +440,8 @@
         values.put(Images.Media.ORIENTATION, 0);
         values.put(Images.Media.DATA, output.getAbsolutePath());
         values.put(Images.Media.SIZE, output.length());
-        values.put(WIDTH, cropped.getWidth());
-        values.put(HEIGHT, cropped.getHeight());
+
+        setImageSize(values, cropped.getWidth(), cropped.getHeight());
 
         if (GalleryUtils.isValidLocation(localImage.latitude, localImage.longitude)) {
             values.put(Images.Media.LATITUDE, localImage.latitude);
@@ -467,8 +473,8 @@
         values.put(Images.Media.ORIENTATION, 0);
         values.put(Images.Media.DATA, output.getAbsolutePath());
         values.put(Images.Media.SIZE, output.length());
-        values.put(WIDTH, cropped.getWidth());
-        values.put(HEIGHT, cropped.getHeight());
+
+        setImageSize(values, cropped.getWidth(), cropped.getHeight());
 
         return getContentResolver().insert(
                 Images.Media.EXTERNAL_CONTENT_URI, values);
diff --git a/src/com/android/gallery3d/app/MovieActivity.java b/src/com/android/gallery3d/app/MovieActivity.java
index 78fe1ee..5f4db1d 100644
--- a/src/com/android/gallery3d/app/MovieActivity.java
+++ b/src/com/android/gallery3d/app/MovieActivity.java
@@ -16,6 +16,7 @@
 
 package com.android.gallery3d.app;
 
+import android.annotation.TargetApi;
 import android.app.ActionBar;
 import android.app.Activity;
 import android.content.AsyncQueryHandler;
@@ -27,6 +28,7 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.media.AudioManager;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
 import android.provider.MediaStore;
 import android.provider.OpenableColumns;
@@ -39,6 +41,7 @@
 import android.widget.ShareActionProvider;
 
 import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.common.Utils;
 
 /**
@@ -59,6 +62,15 @@
     private Uri mUri;
     private boolean mTreatUpAsBack;
 
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+    private void setSystemUiVisibility(View rootView) {
+        if (ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) {
+            rootView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+        }
+    }
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -68,9 +80,9 @@
 
         setContentView(R.layout.movie_view);
         View rootView = findViewById(R.id.movie_view_root);
-        rootView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
-                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
-                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+
+        setSystemUiVisibility(rootView);
+
         Intent intent = getIntent();
         initializeActionBar(intent);
         mFinishOnCompletion = intent.getBooleanExtra(
diff --git a/src/com/android/gallery3d/app/MoviePlayer.java b/src/com/android/gallery3d/app/MoviePlayer.java
index 11b40bd..98d7a64 100644
--- a/src/com/android/gallery3d/app/MoviePlayer.java
+++ b/src/com/android/gallery3d/app/MoviePlayer.java
@@ -16,6 +16,7 @@
 
 package com.android.gallery3d.app;
 
+import android.annotation.TargetApi;
 import android.app.AlertDialog;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -37,6 +38,7 @@
 import android.widget.VideoView;
 
 import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.common.BlobCache;
 import com.android.gallery3d.util.CacheManager;
 import com.android.gallery3d.util.GalleryUtils;
@@ -98,9 +100,10 @@
     };
 
     private final Runnable mRemoveBackground = new Runnable() {
+        @SuppressWarnings("deprecation")
         @Override
         public void run() {
-            mRootView.setBackground(null);
+            mRootView.setBackgroundDrawable(null);
         }
     };
 
@@ -148,6 +151,37 @@
             }
         }, BLACK_TIMEOUT);
 
+        setOnSystemUiVisibilityChangeListener();
+        // Hide system UI by default
+        showSystemUi(false);
+
+        mAudioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver();
+        mAudioBecomingNoisyReceiver.register();
+
+        Intent i = new Intent(SERVICECMD);
+        i.putExtra(CMDNAME, CMDPAUSE);
+        movieActivity.sendBroadcast(i);
+
+        if (savedInstance != null) { // this is a resumed activity
+            mVideoPosition = savedInstance.getInt(KEY_VIDEO_POSITION, 0);
+            mResumeableTime = savedInstance.getLong(KEY_RESUMEABLE_TIME, Long.MAX_VALUE);
+            mVideoView.start();
+            mVideoView.suspend();
+            mHasPaused = true;
+        } else {
+            final Integer bookmark = mBookmarker.getBookmark(mUri);
+            if (bookmark != null) {
+                showResumeDialog(movieActivity, bookmark);
+            } else {
+                startVideo();
+            }
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+    private void setOnSystemUiVisibilityChangeListener() {
+        if (!ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_HIDE_NAVIGATION) return;
+
         // When the user touches the screen or uses some hard key, the framework
         // will change system ui visibility from invisible to visible. We show
         // the media control and enable system UI (e.g. ActionBar) to be visible at this point
@@ -176,37 +210,15 @@
                 }
             }
         });
-
-        // Hide system UI by default
-        showSystemUi(false);
-
-        mAudioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver();
-        mAudioBecomingNoisyReceiver.register();
-
-        Intent i = new Intent(SERVICECMD);
-        i.putExtra(CMDNAME, CMDPAUSE);
-        movieActivity.sendBroadcast(i);
-
-        if (savedInstance != null) { // this is a resumed activity
-            mVideoPosition = savedInstance.getInt(KEY_VIDEO_POSITION, 0);
-            mResumeableTime = savedInstance.getLong(KEY_RESUMEABLE_TIME, Long.MAX_VALUE);
-            mVideoView.start();
-            mVideoView.suspend();
-            mHasPaused = true;
-        } else {
-            final Integer bookmark = mBookmarker.getBookmark(mUri);
-            if (bookmark != null) {
-                showResumeDialog(movieActivity, bookmark);
-            } else {
-                startVideo();
-            }
-        }
     }
 
+    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
     private void showSystemUi(boolean visible) {
+        if (!ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) return;
+
         int flag = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
-                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
+                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
         if (!visible) {
             flag |= View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN
                     | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -496,7 +508,7 @@
             DataInputStream dis = new DataInputStream(
                     new ByteArrayInputStream(data));
 
-            String uriString = dis.readUTF(dis);
+            String uriString = DataInputStream.readUTF(dis);
             int bookmark = dis.readInt();
             int duration = dis.readInt();
 
diff --git a/src/com/android/gallery3d/app/PackagesMonitor.java b/src/com/android/gallery3d/app/PackagesMonitor.java
index e4bb8ee..d8aa8c9 100644
--- a/src/com/android/gallery3d/app/PackagesMonitor.java
+++ b/src/com/android/gallery3d/app/PackagesMonitor.java
@@ -16,6 +16,7 @@
 
 package com.android.gallery3d.app;
 
+import android.app.IntentService;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -34,23 +35,23 @@
 
     @Override
     public void onReceive(final Context context, final Intent intent) {
-        final PendingResult result = goAsync();
-        new Thread("GalleryPackagesMonitorAsync") {
-            @Override
-            public void run() {
-                try {
-                    onReceiveAsync(context, intent);
-                } catch (Throwable t) {
-                    Log.e("PackagesMonitor", "onReceiveAsync", t);
-                } finally {
-                    result.finish();
-                }
-            }
-        }.start();
+        intent.setClass(context, AsyncService.class);
+        context.startService(intent);
+    }
+
+    public static class AsyncService extends IntentService {
+        public AsyncService() {
+            super("GalleryPackagesMonitorAsync");
+        }
+
+        @Override
+        protected void onHandleIntent(Intent intent) {
+            onReceiveAsync(this, intent);
+        }
     }
 
     // Runs in a background thread.
-    private void onReceiveAsync(Context context, Intent intent) {
+    private static void onReceiveAsync(Context context, Intent intent) {
         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
 
         int version = prefs.getInt(KEY_PACKAGES_VERSION, 1);
diff --git a/src/com/android/gallery3d/app/Wallpaper.java b/src/com/android/gallery3d/app/Wallpaper.java
index c08c1d7..1ece66c 100644
--- a/src/com/android/gallery3d/app/Wallpaper.java
+++ b/src/com/android/gallery3d/app/Wallpaper.java
@@ -16,11 +16,16 @@
 
 package com.android.gallery3d.app;
 
+import android.annotation.TargetApi;
 import android.app.Activity;
 import android.content.Intent;
 import android.graphics.Point;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
+import android.view.Display;
+
+import com.android.gallery3d.common.ApiHelper;
 
 /**
  * Wallpaper picker for the gallery application. This just redirects to the
@@ -57,6 +62,18 @@
         }
     }
 
+    @SuppressWarnings("deprecation")
+    @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB_MR2)
+    private Point getDefaultDisplaySize(Point size) {
+        Display d = getWindowManager().getDefaultDisplay();
+        if (Build.VERSION.SDK_INT >= ApiHelper.VERSION_CODES.HONEYCOMB_MR2) {
+            d.getSize(size);
+        } else {
+            size.set(d.getWidth(), d.getHeight());
+        }
+        return size;
+    }
+
     @SuppressWarnings("fallthrough")
     @Override
     protected void onResume() {
@@ -78,8 +95,7 @@
             case STATE_PHOTO_PICKED: {
                 int width = getWallpaperDesiredMinimumWidth();
                 int height = getWallpaperDesiredMinimumHeight();
-                Point size = new Point();
-                getWindowManager().getDefaultDisplay().getSize(size);
+                Point size = getDefaultDisplaySize(new Point());
                 float spotlightX = (float) size.x / width;
                 float spotlightY = (float) size.y / height;
                 Intent request = new Intent(CropImage.ACTION_CROP)
diff --git a/src/com/android/gallery3d/data/LocalImage.java b/src/com/android/gallery3d/data/LocalImage.java
index c3095a0..316e324 100644
--- a/src/com/android/gallery3d/data/LocalImage.java
+++ b/src/com/android/gallery3d/data/LocalImage.java
@@ -16,6 +16,7 @@
 
 package com.android.gallery3d.data;
 
+import android.annotation.TargetApi;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.database.Cursor;
@@ -26,9 +27,11 @@
 import android.net.Uri;
 import android.provider.MediaStore.Images;
 import android.provider.MediaStore.Images.ImageColumns;
+import android.provider.MediaStore.MediaColumns;
 import android.util.Log;
 
 import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.common.BitmapUtils;
 import com.android.gallery3d.util.GalleryUtils;
 import com.android.gallery3d.util.ThreadPool.Job;
@@ -74,10 +77,22 @@
             ImageColumns.ORIENTATION,   // 9
             ImageColumns.BUCKET_ID,     // 10
             ImageColumns.SIZE,          // 11
-            ImageColumns.WIDTH,         // 12
-            ImageColumns.HEIGHT         // 13
+            "0",                        // 12
+            "0"                         // 13
     };
 
+    static {
+        updateWidthAndHeightProjection();
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+    private static void updateWidthAndHeightProjection() {
+        if (ApiHelper.HAS_MEDIA_COLUMNS_WIDTH_AND_HEIGHT) {
+            PROJECTION[INDEX_WIDTH] = MediaColumns.WIDTH;
+            PROJECTION[INDEX_HEIGHT] = MediaColumns.HEIGHT;
+        }
+    }
+
     private final GalleryApp mApplication;
 
     public int rotation;
diff --git a/src/com/android/gallery3d/gadget/PhotoAppWidgetProvider.java b/src/com/android/gallery3d/gadget/PhotoAppWidgetProvider.java
index 2b36f6b..9813902 100644
--- a/src/com/android/gallery3d/gadget/PhotoAppWidgetProvider.java
+++ b/src/com/android/gallery3d/gadget/PhotoAppWidgetProvider.java
@@ -70,6 +70,7 @@
         super.onUpdate(context, appWidgetManager, appWidgetIds);
     }
 
+    @SuppressWarnings("deprecation")
     private static RemoteViews buildStackWidget(Context context, int widgetId, Entry entry) {
         RemoteViews views = new RemoteViews(
                 context.getPackageName(), R.layout.appwidget_main);
@@ -80,7 +81,10 @@
         intent.putExtra(WidgetService.EXTRA_ALBUM_PATH, entry.albumPath);
         intent.setData(Uri.parse("widget://gallery/" + widgetId));
 
-        views.setRemoteAdapter(R.id.appwidget_stack_view, intent);
+        // We use the deprecated API for backward compatibility
+        // The new API is available in ICE_CREAM_SANDWICH (15)
+        views.setRemoteAdapter(widgetId, R.id.appwidget_stack_view, intent);
+
         views.setEmptyView(R.id.appwidget_stack_view, R.id.appwidget_empty_view);
 
         Intent clickIntent = new Intent(context, WidgetClickHandler.class);
diff --git a/src/com/android/gallery3d/ui/GLRootView.java b/src/com/android/gallery3d/ui/GLRootView.java
index 99ed8cb..478cb4f 100644
--- a/src/com/android/gallery3d/ui/GLRootView.java
+++ b/src/com/android/gallery3d/ui/GLRootView.java
@@ -16,6 +16,7 @@
 
 package com.android.gallery3d.ui;
 
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.Matrix;
 import android.graphics.PixelFormat;
@@ -29,6 +30,7 @@
 
 import com.android.gallery3d.R;
 import com.android.gallery3d.anim.CanvasAnimation;
+import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.common.Utils;
 import com.android.gallery3d.util.GalleryUtils;
 import com.android.gallery3d.util.Profile;
@@ -536,12 +538,15 @@
     }
 
     @Override
+    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
     public void setLightsOutMode(boolean enabled) {
-        int flags = enabled
-                ? SYSTEM_UI_FLAG_LOW_PROFILE
-                | SYSTEM_UI_FLAG_FULLSCREEN
-                | SYSTEM_UI_FLAG_LAYOUT_STABLE
-                : 0;
+        int flags = 0;
+        if (enabled) {
+            flags = SYSTEM_UI_FLAG_LOW_PROFILE;
+            if (ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) {
+                flags |= (SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE);
+            }
+        }
         setSystemUiVisibility(flags);
     }
 
diff --git a/src/com/android/gallery3d/ui/PositionController.java b/src/com/android/gallery3d/ui/PositionController.java
index d556caf..6d33470 100644
--- a/src/com/android/gallery3d/ui/PositionController.java
+++ b/src/com/android/gallery3d/ui/PositionController.java
@@ -110,11 +110,11 @@
     private Listener mListener;
     private volatile Rect mOpenAnimationRect;
 
-    // Use a large enough value, so we won't see the gray shadown in the beginning.
+    // Use a large enough value, so we won't see the gray shadow in the beginning.
     private int mViewW = 1200;
     private int mViewH = 1200;
 
-    // A scaling guesture is in progress.
+    // A scaling gesture is in progress.
     private boolean mInScale;
     // The focus point of the scaling gesture, relative to the center of the
     // picture in bitmap pixels.
@@ -178,7 +178,7 @@
     private RangeArray<Gap> mTempGaps =
         new RangeArray<Gap>(-BOX_MAX, BOX_MAX - 1);
 
-    // The output of the PositionController. Available throught getPosition().
+    // The output of the PositionController. Available through getPosition().
     private RangeArray<Rect> mRects = new RangeArray<Rect>(-BOX_MAX, BOX_MAX);
 
     // The direction of a new picture should appear. New pictures pop from top
@@ -211,7 +211,7 @@
         mListener = listener;
         mPageScroller = new FlingScroller();
         mFilmScroller = new OverScroller(context,
-                null /* default interpolator */, false /* no flywheel */);
+                null /* default interpolator */, 0, 0, false /* no flywheel */);
 
         // Initialize the areas.
         initPlatform();
@@ -519,7 +519,7 @@
         Platform p = mPlatform;
 
         // We want to keep the focus point (on the bitmap) the same as when we
-        // begin the scale guesture, that is,
+        // begin the scale gesture, that is,
         //
         // (focusX' - currentX') / scale' = (focusX - currentX) / scale
         //
@@ -1005,7 +1005,7 @@
     // N N N N N N N -- all new boxes
     // -3 -2 -1 0 1 2 3 -- nothing changed
     // -2 -1 0 1 2 3 N -- focus goes to the next box
-    // N -3 -2 -1 0 1 2 -- focuse goes to the previous box
+    // N -3 -2 -1 0 1 2 -- focus goes to the previous box
     // -3 -2 -1 1 2 3 N -- the focused box was deleted.
     //
     // hasPrev/hasNext indicates if there are previous/next boxes for the
@@ -1019,7 +1019,7 @@
 
         RangeIntArray from = new RangeIntArray(fromIndex, -BOX_MAX, BOX_MAX);
 
-        // 1. Get the absolute X coordiates for the boxes.
+        // 1. Get the absolute X coordinates for the boxes.
         layoutAndSetPosition();
         for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
             Box b = mBoxes.get(i);
@@ -1366,7 +1366,7 @@
         public int mAnimationKind;
         public int mAnimationDuration;
 
-        // This should be overidden in subclass to change the animation values
+        // This should be overridden in subclass to change the animation values
         // give the progress value in [0, 1].
         protected abstract boolean interpolate(float progress);
         public abstract boolean startSnapback();
diff --git a/src/com/android/gallery3d/ui/TileImageView.java b/src/com/android/gallery3d/ui/TileImageView.java
index 0c6086d..fc61efd 100644
--- a/src/com/android/gallery3d/ui/TileImageView.java
+++ b/src/com/android/gallery3d/ui/TileImageView.java
@@ -21,9 +21,10 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.FloatMath;
-import android.util.LongSparseArray;
 
 import com.android.gallery3d.app.GalleryContext;
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.common.LongSparseArray;
 import com.android.gallery3d.common.Utils;
 import com.android.gallery3d.data.BitmapPool;
 import com.android.gallery3d.data.DecodeUtils;
@@ -48,7 +49,9 @@
     private static final int UPLOAD_LIMIT = 1;
 
     private static final BitmapPool sTilePool =
-            new BitmapPool(BITMAP_SIZE, BITMAP_SIZE, 128);
+            ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER
+            ? new BitmapPool(BITMAP_SIZE, BITMAP_SIZE, 128)
+            : null;
 
     /*
      *  This is the tile state in the CPU side.
@@ -378,7 +381,7 @@
             }
         }
         setScreenNail(null);
-        sTilePool.clear();
+        if (sTilePool != null) sTilePool.clear();
     }
 
     public void prepareTextures() {
@@ -487,7 +490,7 @@
             if (tile.mTileState == STATE_RECYCLING) {
                 tile.mTileState = STATE_RECYCLED;
                 if (tile.mDecodedTile != null) {
-                    sTilePool.recycle(tile.mDecodedTile);
+                    if (sTilePool != null) sTilePool.recycle(tile.mDecodedTile);
                     tile.mDecodedTile = null;
                 }
                 mRecycledQueue.push(tile);
@@ -515,7 +518,7 @@
         }
         tile.mTileState = STATE_RECYCLED;
         if (tile.mDecodedTile != null) {
-            sTilePool.recycle(tile.mDecodedTile);
+            if (sTilePool != null) sTilePool.recycle(tile.mDecodedTile);
             tile.mDecodedTile = null;
         }
         mRecycledQueue.push(tile);
@@ -653,7 +656,7 @@
 
         @Override
         protected void onFreeBitmap(Bitmap bitmap) {
-            sTilePool.recycle(bitmap);
+            if (sTilePool != null) sTilePool.recycle(bitmap);
         }
 
         boolean decode() {
diff --git a/src/com/android/gallery3d/ui/TileImageViewAdapter.java b/src/com/android/gallery3d/ui/TileImageViewAdapter.java
index 0b4ac03..4865e5c 100644
--- a/src/com/android/gallery3d/ui/TileImageViewAdapter.java
+++ b/src/com/android/gallery3d/ui/TileImageViewAdapter.java
@@ -20,8 +20,10 @@
 import android.graphics.Bitmap.Config;
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapRegionDecoder;
+import android.graphics.Canvas;
 import android.graphics.Rect;
 
+import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.common.Utils;
 import com.android.gallery3d.data.BitmapPool;
 
@@ -108,6 +110,11 @@
     @Override
     public Bitmap getTile(int level, int x, int y, int tileSize,
             int borderSize, BitmapPool pool) {
+
+        if (!ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER) {
+            return getTileWithoutReusingBitmap(level, x, y, tileSize, borderSize);
+        }
+
         int b = borderSize << level;
         int t = tileSize << level;
 
@@ -158,6 +165,49 @@
         return bitmap;
     }
 
+    private Bitmap getTileWithoutReusingBitmap(
+            int level, int x, int y, int tileSize, int borderSize) {
+        int b = borderSize << level;
+        int t = tileSize << level;
+        Rect wantRegion = new Rect(x - b, y - b, x + t + b, y + t + b);
+
+        BitmapRegionDecoder regionDecoder;
+        Rect overlapRegion;
+
+        synchronized (this) {
+            regionDecoder = mRegionDecoder;
+            if (regionDecoder == null) return null;
+            overlapRegion = new Rect(0, 0, mImageWidth, mImageHeight);
+            Utils.assertTrue(overlapRegion.intersect(wantRegion));
+        }
+
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inPreferredConfig = Config.ARGB_8888;
+        options.inPreferQualityOverSpeed = true;
+        options.inSampleSize =  (1 << level);
+        Bitmap bitmap = null;
+
+        // In CropImage, we may call the decodeRegion() concurrently.
+        synchronized (regionDecoder) {
+            bitmap = regionDecoder.decodeRegion(overlapRegion, options);
+        }
+
+        if (bitmap == null) {
+            Log.w(TAG, "fail in decoding region");
+        }
+
+        if (wantRegion.equals(overlapRegion)) return bitmap;
+
+        int s = tileSize + 2 * borderSize;
+        Bitmap result = Bitmap.createBitmap(s, s, Config.ARGB_8888);
+        Canvas canvas = new Canvas(result);
+        canvas.drawBitmap(bitmap,
+                (overlapRegion.left - wantRegion.left) >> level,
+                (overlapRegion.top - wantRegion.top) >> level, null);
+        return result;
+    }
+
+
     @Override
     public ScreenNail getScreenNail() {
         return mScreenNail;
diff --git a/src/com/android/gallery3d/util/GalleryUtils.java b/src/com/android/gallery3d/util/GalleryUtils.java
index 1d70914..1291ee9 100644
--- a/src/com/android/gallery3d/util/GalleryUtils.java
+++ b/src/com/android/gallery3d/util/GalleryUtils.java
@@ -16,7 +16,6 @@
 
 package com.android.gallery3d.util;
 
-import android.app.Activity;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
@@ -24,7 +23,6 @@
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
 import android.net.Uri;
 import android.os.ConditionVariable;
 import android.os.Environment;