Merge "Share pack historical sorting using wrong keys." into klp-dev
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 789dcc5..42e3b50 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2622,11 +2622,7 @@
              add printers to this print service. -->
         <attr name="addPrintersActivity" format="string"/>
         <!-- Fully qualified class name of an activity with advanced print options
-             specific to this print service. If this activity is specified the system
-             will allow the user a choice to open it given the currently selected printer
-             has advanced options which is specified by the print service via
-             {@link android.print.PrinterInfo.Builder#setHasAdvancedOptions(boolean)}.
-             -->
+             specific to this print service. -->
         <attr name="advancedPrintOptionsActivity" format="string"/>
         <!-- The vendor name if this print service is vendor specific. -->
         <attr name="vendor" format="string"/>
diff --git a/docs/downloads/training/BitmapFun.zip b/docs/downloads/training/BitmapFun.zip
index 48bea78..8668897 100644
--- a/docs/downloads/training/BitmapFun.zip
+++ b/docs/downloads/training/BitmapFun.zip
Binary files differ
diff --git a/docs/html/training/displaying-bitmaps/cache-bitmap.jd b/docs/html/training/displaying-bitmaps/cache-bitmap.jd
index ad084c2..ff9c3a0 100644
--- a/docs/html/training/displaying-bitmaps/cache-bitmap.jd
+++ b/docs/html/training/displaying-bitmaps/cache-bitmap.jd
@@ -188,8 +188,8 @@
 image gallery application.</p>
 
 <p>The sample code of this class uses a {@code DiskLruCache} implementation that is pulled from the 
-<a href="https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/libcore/io/DiskLruCache.java">Android source</a>. Here’s updated example code that adds a disk cache in addition
-to the existing memory cache:</p>
+<a href="https://android.googlesource.com/platform/libcore/+/jb-mr2-release/luni/src/main/java/libcore/io/DiskLruCache.java">Android source</a>.
+Here’s updated example code that adds a disk cache in addition to the existing memory cache:</p>
 
 <pre>
 private DiskLruCache mDiskLruCache;
diff --git a/docs/html/training/displaying-bitmaps/load-bitmap.jd b/docs/html/training/displaying-bitmaps/load-bitmap.jd
index 633ffd2..938901f 100644
--- a/docs/html/training/displaying-bitmaps/load-bitmap.jd
+++ b/docs/html/training/displaying-bitmaps/load-bitmap.jd
@@ -97,7 +97,8 @@
 is decoded with an {@link android.graphics.BitmapFactory.Options#inSampleSize} of 4 produces a
 bitmap of approximately 512x384. Loading this into memory uses 0.75MB rather than 12MB for the full
 image (assuming a bitmap configuration of {@link android.graphics.Bitmap.Config ARGB_8888}). Here’s
-a method to calculate a the sample size value based on a target width and height:</p>
+a method to calculate a sample size value that is a power of two based on a target width and
+height:</p>
 
 <pre>
 public static int calculateInSampleSize(
@@ -107,26 +108,26 @@
     final int width = options.outWidth;
     int inSampleSize = 1;
 
-    if (height > reqHeight || width > reqWidth) {
+    if (height &gt; reqHeight || width &gt; reqWidth) {
 
-        // Calculate ratios of height and width to requested height and width
-        final int heightRatio = Math.round((float) height / (float) reqHeight);
-        final int widthRatio = Math.round((float) width / (float) reqWidth);
+        final int halfHeight = height / 2;
+        final int halfWidth = width / 2;
 
-        // Choose the smallest ratio as inSampleSize value, this will guarantee
-        // a final image with both dimensions larger than or equal to the
-        // requested height and width.
-        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
+        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
+        // height and width larger than the requested height and width.
+        while ((halfHeight / inSampleSize) &gt; reqHeight
+                && (halfWidth / inSampleSize) &gt; reqWidth) {
+            inSampleSize *= 2;
+        }
     }
 
     return inSampleSize;
 }
 </pre>
 
-<p class="note"><strong>Note:</strong> Using powers of 2 for {@link
-android.graphics.BitmapFactory.Options#inSampleSize} values is faster and more efficient for the
-decoder. However, if you plan to cache the resized versions in memory or on disk, it’s usually still
-worth decoding to the most appropriate image dimensions to save space.</p>
+<p class="note"><strong>Note:</strong> A power of two value is calculated because the decoder uses
+a final value by rounding down to the nearest power of two, as per the {@link
+android.graphics.BitmapFactory.Options#inSampleSize} documentation.</p>
 
 <p>To use this method, first decode with {@link
 android.graphics.BitmapFactory.Options#inJustDecodeBounds} set to {@code true}, pass the options
diff --git a/docs/html/training/displaying-bitmaps/manage-memory.jd b/docs/html/training/displaying-bitmaps/manage-memory.jd
index 60ac2e6..0e1279e 100644
--- a/docs/html/training/displaying-bitmaps/manage-memory.jd
+++ b/docs/html/training/displaying-bitmaps/manage-memory.jd
@@ -56,7 +56,7 @@
 which is stored in the Dalvik heap. The pixel data in native memory is
 not released in a predictable manner, potentially causing an application
 to briefly exceed its memory limits and crash.
-<strong>As of Android 3.0 (API Level 11), the pixel data is stored on the
+<strong>As of Android 3.0 (API level 11), the pixel data is stored on the
 Dalvik heap along with the associated bitmap.</strong></li>
 
 </ul>
@@ -140,27 +140,16 @@
 
 <h2 id="inBitmap">Manage Memory on Android 3.0 and Higher</h2>
 
-<p>Android 3.0 (API Level 11) introduces the
+<p>Android 3.0 (API level 11) introduces the
 {@link android.graphics.BitmapFactory.Options#inBitmap BitmapFactory.Options.inBitmap}
 field. If this option is set, decode methods that take the 
 {@link android.graphics.BitmapFactory.Options Options} object
 will attempt to reuse an existing bitmap when loading content. This means
 that the bitmap's memory is reused, resulting in improved performance, and
-removing both memory allocation and de-allocation. There are some caveats in using
-{@link android.graphics.BitmapFactory.Options#inBitmap}:</p>
-<ul>
-  <li>The reused bitmap must be of the same size as the source content (to make
-sure that the same amount of memory is used), and in JPEG or PNG format
-(whether as a resource or as a stream).</li>
-
-
-<li>The {@link android.graphics.Bitmap.Config configuration} of the reused bitmap
-overrides the setting of
-{@link android.graphics.BitmapFactory.Options#inPreferredConfig}, if set. </li>
-
-  <li>You should always use the returned bitmap of the decode method,
-because you can't assume that reusing the bitmap worked (for example, if there is
-a size mismatch).</li>
+removing both memory allocation and de-allocation. However, there are certain restrictions with how
+{@link android.graphics.BitmapFactory.Options#inBitmap} can be used. In particular, before Android
+4.4 (API level 19), only equal sized bitmaps are supported. For details, please see the
+{@link android.graphics.BitmapFactory.Options#inBitmap} documentation.
 
 <h3>Save a bitmap for later use</h3>
 
@@ -283,14 +272,39 @@
 satisfies the size criteria to be used for
 {@link android.graphics.BitmapFactory.Options#inBitmap}:</p>
 
-<pre>private static boolean canUseForInBitmap(
+<pre>static boolean canUseForInBitmap(
         Bitmap candidate, BitmapFactory.Options targetOptions) {
-    int width = targetOptions.outWidth / targetOptions.inSampleSize;
-    int height = targetOptions.outHeight / targetOptions.inSampleSize;
 
-    // Returns true if "candidate" can be used for inBitmap re-use with
-    // "targetOptions".
-    return candidate.getWidth() == width && candidate.getHeight() == height;
+    if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.KITKAT) {
+        // From Android 4.4 (KitKat) onward we can re-use if the byte size of
+        // the new bitmap is smaller than the reusable bitmap candidate
+        // allocation byte count.
+        int width = targetOptions.outWidth / targetOptions.inSampleSize;
+        int height = targetOptions.outHeight / targetOptions.inSampleSize;
+        int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
+        return byteCount &lt;= candidate.getAllocationByteCount();
+    }
+
+    // On earlier versions, the dimensions must match exactly and the inSampleSize must be 1
+    return candidate.getWidth() == targetOptions.outWidth
+            && candidate.getHeight() == targetOptions.outHeight
+            && targetOptions.inSampleSize == 1;
+}
+
+/**
+ * A helper function to return the byte usage per pixel of a bitmap based on its configuration.
+ */
+static int getBytesPerPixel(Config config) {
+    if (config == Config.ARGB_8888) {
+        return 4;
+    } else if (config == Config.RGB_565) {
+        return 2;
+    } else if (config == Config.ARGB_4444) {
+        return 2;
+    } else if (config == Config.ALPHA_8) {
+        return 1;
+    }
+    return 1;
 }</pre>
 
 </body>
diff --git a/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java b/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java
index 5f64018..b5774f4 100644
--- a/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java
+++ b/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java
@@ -31,11 +31,13 @@
 import android.util.Log;
 
 import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.exif.ExifInterface;
 import com.android.gallery3d.glrenderer.BasicTexture;
 import com.android.gallery3d.glrenderer.BitmapTexture;
 import com.android.photos.views.TiledImageRenderer;
 
 import java.io.BufferedInputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 
@@ -53,7 +55,176 @@
     private static final int GL_SIZE_LIMIT = 2048;
     // This must be no larger than half the size of the GL_SIZE_LIMIT
     // due to decodePreview being allowed to be up to 2x the size of the target
-    private static final int MAX_PREVIEW_SIZE = 1024;
+    public static final int MAX_PREVIEW_SIZE = GL_SIZE_LIMIT / 2;
+
+    public static abstract class BitmapSource {
+        private BitmapRegionDecoder mDecoder;
+        private Bitmap mPreview;
+        private int mPreviewSize;
+        private int mRotation;
+        public BitmapSource(int previewSize) {
+            mPreviewSize = previewSize;
+        }
+        public void loadInBackground() {
+            ExifInterface ei = new ExifInterface();
+            if (readExif(ei)) {
+                Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
+                if (ori != null) {
+                    mRotation = ExifInterface.getRotationForOrientationValue(ori.shortValue());
+                }
+            }
+            mDecoder = loadBitmapRegionDecoder();
+            int width = mDecoder.getWidth();
+            int height = mDecoder.getHeight();
+            if (mPreviewSize != 0) {
+                int previewSize = Math.min(mPreviewSize, MAX_PREVIEW_SIZE);
+                BitmapFactory.Options opts = new BitmapFactory.Options();
+                opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
+                opts.inPreferQualityOverSpeed = true;
+
+                float scale = (float) previewSize / Math.max(width, height);
+                opts.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
+                opts.inJustDecodeBounds = false;
+                mPreview = loadPreviewBitmap(opts);
+            }
+        }
+
+        public BitmapRegionDecoder getBitmapRegionDecoder() {
+            return mDecoder;
+        }
+
+        public Bitmap getPreviewBitmap() {
+            return mPreview;
+        }
+
+        public int getPreviewSize() {
+            return mPreviewSize;
+        }
+
+        public int getRotation() {
+            return mRotation;
+        }
+
+        public abstract boolean readExif(ExifInterface ei);
+        public abstract BitmapRegionDecoder loadBitmapRegionDecoder();
+        public abstract Bitmap loadPreviewBitmap(BitmapFactory.Options options);
+    }
+
+    public static class FilePathBitmapSource extends BitmapSource {
+        private String mPath;
+        public FilePathBitmapSource(String path, int previewSize) {
+            super(previewSize);
+            mPath = path;
+        }
+        @Override
+        public BitmapRegionDecoder loadBitmapRegionDecoder() {
+            try {
+                return BitmapRegionDecoder.newInstance(mPath, true);
+            } catch (IOException e) {
+                Log.w("BitmapRegionTileSource", "getting decoder failed", e);
+                return null;
+            }
+        }
+        @Override
+        public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
+            return BitmapFactory.decodeFile(mPath, options);
+        }
+        @Override
+        public boolean readExif(ExifInterface ei) {
+            try {
+                ei.readExif(mPath);
+                return true;
+            } catch (IOException e) {
+                Log.w("BitmapRegionTileSource", "getting decoder failed", e);
+                return false;
+            }
+        }
+    }
+
+    public static class UriBitmapSource extends BitmapSource {
+        private Context mContext;
+        private Uri mUri;
+        public UriBitmapSource(Context context, Uri uri, int previewSize) {
+            super(previewSize);
+            mContext = context;
+            mUri = uri;
+        }
+        private InputStream regenerateInputStream() throws FileNotFoundException {
+            InputStream is = mContext.getContentResolver().openInputStream(mUri);
+            return new BufferedInputStream(is);
+        }
+        @Override
+        public BitmapRegionDecoder loadBitmapRegionDecoder() {
+            try {
+                return BitmapRegionDecoder.newInstance(regenerateInputStream(), true);
+            } catch (FileNotFoundException e) {
+                Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
+                return null;
+            } catch (IOException e) {
+                Log.e("BitmapRegionTileSource", "Failure while reading URI " + mUri, e);
+                return null;
+            }
+        }
+        @Override
+        public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
+            try {
+                return BitmapFactory.decodeStream(regenerateInputStream(), null, options);
+            } catch (FileNotFoundException e) {
+                Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
+                return null;
+            }
+        }
+        @Override
+        public boolean readExif(ExifInterface ei) {
+            try {
+                ei.readExif(regenerateInputStream());
+                return true;
+            } catch (FileNotFoundException e) {
+                Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
+                return false;
+            } catch (IOException e) {
+                Log.e("BitmapRegionTileSource", "Failure while reading URI " + mUri, e);
+                return false;
+            }
+        }
+    }
+
+    public static class ResourceBitmapSource extends BitmapSource {
+        private Resources mRes;
+        private int mResId;
+        public ResourceBitmapSource(Resources res, int resId, int previewSize) {
+            super(previewSize);
+            mRes = res;
+            mResId = resId;
+        }
+        private InputStream regenerateInputStream() {
+            InputStream is = mRes.openRawResource(mResId);
+            return new BufferedInputStream(is);
+        }
+        @Override
+        public BitmapRegionDecoder loadBitmapRegionDecoder() {
+            try {
+                return BitmapRegionDecoder.newInstance(regenerateInputStream(), true);
+            } catch (IOException e) {
+                Log.e("BitmapRegionTileSource", "Error reading resource", e);
+                return null;
+            }
+        }
+        @Override
+        public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
+            return BitmapFactory.decodeResource(mRes, mResId, options);
+        }
+        @Override
+        public boolean readExif(ExifInterface ei) {
+            try {
+                ei.readExif(regenerateInputStream());
+                return true;
+            } catch (IOException e) {
+                Log.e("BitmapRegionTileSource", "Error reading resource", e);
+                return false;
+            }
+        }
+    }
 
     BitmapRegionDecoder mDecoder;
     int mWidth;
@@ -68,50 +239,23 @@
     private BitmapFactory.Options mOptions;
     private Canvas mCanvas;
 
-    public BitmapRegionTileSource(Context context, String path, int previewSize, int rotation) {
-        this(null, context, path, null, 0, previewSize, rotation);
-    }
-
-    public BitmapRegionTileSource(Context context, Uri uri, int previewSize, int rotation) {
-        this(null, context, null, uri, 0, previewSize, rotation);
-    }
-
-    public BitmapRegionTileSource(Resources res,
-            Context context, int resId, int previewSize, int rotation) {
-        this(res, context, null, null, resId, previewSize, rotation);
-    }
-
-    private BitmapRegionTileSource(Resources res,
-            Context context, String path, Uri uri, int resId, int previewSize, int rotation) {
+    public BitmapRegionTileSource(Context context, BitmapSource source) {
         mTileSize = TiledImageRenderer.suggestedTileSize(context);
-        mRotation = rotation;
-        try {
-            if (path != null) {
-                mDecoder = BitmapRegionDecoder.newInstance(path, true);
-            } else if (uri != null) {
-                InputStream is = context.getContentResolver().openInputStream(uri);
-                BufferedInputStream bis = new BufferedInputStream(is);
-                mDecoder = BitmapRegionDecoder.newInstance(bis, true);
-            } else {
-                InputStream is = res.openRawResource(resId);
-                BufferedInputStream bis = new BufferedInputStream(is);
-                mDecoder = BitmapRegionDecoder.newInstance(bis, true);
-            }
-            mWidth = mDecoder.getWidth();
-            mHeight = mDecoder.getHeight();
-        } catch (IOException e) {
-            Log.w("BitmapRegionTileSource", "ctor failed", e);
-        }
+        mRotation = source.getRotation();
+        mDecoder = source.getBitmapRegionDecoder();
+        mWidth = mDecoder.getWidth();
+        mHeight = mDecoder.getHeight();
         mOptions = new BitmapFactory.Options();
         mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
         mOptions.inPreferQualityOverSpeed = true;
         mOptions.inTempStorage = new byte[16 * 1024];
+        int previewSize = source.getPreviewSize();
         if (previewSize != 0) {
             previewSize = Math.min(previewSize, MAX_PREVIEW_SIZE);
             // Although this is the same size as the Bitmap that is likely already
             // loaded, the lifecycle is different and interactions are on a different
             // thread. Thus to simplify, this source will decode its own bitmap.
-            Bitmap preview = decodePreview(res, context, path, uri, resId, previewSize);
+            Bitmap preview = decodePreview(source, previewSize);
             if (preview.getWidth() <= GL_SIZE_LIMIT && preview.getHeight() <= GL_SIZE_LIMIT) {
                 mPreview = new BitmapTexture(preview);
             } else {
@@ -215,33 +359,15 @@
      * Note that the returned bitmap may have a long edge that's longer
      * than the targetSize, but it will always be less than 2x the targetSize
      */
-    private Bitmap decodePreview(
-            Resources res, Context context, String file, Uri uri, int resId, int targetSize) {
-        float scale = (float) targetSize / Math.max(mWidth, mHeight);
-        mOptions.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
-        mOptions.inJustDecodeBounds = false;
-
-        Bitmap result = null;
-        if (file != null) {
-            result = BitmapFactory.decodeFile(file, mOptions);
-        } else if (uri != null) {
-            try {
-                InputStream is = context.getContentResolver().openInputStream(uri);
-                BufferedInputStream bis = new BufferedInputStream(is);
-                result = BitmapFactory.decodeStream(bis, null, mOptions);
-            } catch (IOException e) {
-                Log.w("BitmapRegionTileSource", "getting preview failed", e);
-            }
-        } else {
-            result = BitmapFactory.decodeResource(res, resId, mOptions);
-        }
+    private Bitmap decodePreview(BitmapSource source, int targetSize) {
+        Bitmap result = source.getPreviewBitmap();
         if (result == null) {
             return null;
         }
 
         // We need to resize down if the decoder does not support inSampleSize
         // or didn't support the specified inSampleSize (some decoders only do powers of 2)
-        scale = (float) targetSize / (float) (Math.max(result.getWidth(), result.getHeight()));
+        float scale = (float) targetSize / (float) (Math.max(result.getWidth(), result.getHeight()));
 
         if (scale <= 0.5) {
             result = BitmapUtils.resizeBitmapByScale(result, scale, true);
diff --git a/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java b/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java
index 1209e56..220f567 100644
--- a/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java
+++ b/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java
@@ -37,7 +37,6 @@
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
-import android.util.FloatMath;
 import android.util.Log;
 import android.view.Display;
 import android.view.View;
@@ -96,9 +95,6 @@
             return;
         }
 
-        int rotation = getRotationFromExif(this, imageUri);
-        mCropView.setTileSource(new BitmapRegionTileSource(this, imageUri, 1024, rotation), null);
-        mCropView.setTouchEnabled(true);
         // Action bar
         // Show the custom action bar view
         final ActionBar actionBar = getActionBar();
@@ -111,6 +107,46 @@
                         cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone);
                     }
                 });
+
+        // Load image in background
+        setCropViewTileSource(
+                new BitmapRegionTileSource.UriBitmapSource(this, imageUri, 1024), true, false);
+    }
+
+    public void setCropViewTileSource(final BitmapRegionTileSource.BitmapSource bitmapSource,
+            final boolean touchEnabled, final boolean moveToLeft) {
+        final Context context = WallpaperCropActivity.this;
+        final View progressView = findViewById(R.id.loading);
+        final AsyncTask<Void, Void, Void> loadBitmapTask = new AsyncTask<Void, Void, Void>() {
+            protected Void doInBackground(Void...args) {
+                if (!isCancelled()) {
+                    bitmapSource.loadInBackground();
+                }
+                return null;
+            }
+            protected void onPostExecute(Void arg) {
+                if (!isCancelled()) {
+                    progressView.setVisibility(View.INVISIBLE);
+                    mCropView.setTileSource(
+                            new BitmapRegionTileSource(context, bitmapSource), null);
+                    mCropView.setTouchEnabled(touchEnabled);
+                    if (moveToLeft) {
+                        mCropView.moveToLeft();
+                    }
+                }
+            }
+        };
+        // We don't want to show the spinner every time we load an image, because that would be
+        // annoying; instead, only start showing the spinner if loading the image has taken
+        // longer than 1 sec (ie 1000 ms)
+        progressView.postDelayed(new Runnable() {
+            public void run() {
+                if (loadBitmapTask.getStatus() != AsyncTask.Status.FINISHED) {
+                    progressView.setVisibility(View.VISIBLE);
+                }
+            }
+        }, 1000);
+        loadBitmapTask.execute();
     }
 
     public boolean enableRotation() {