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 > reqHeight || width > 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) > reqHeight
+ && (halfWidth / inSampleSize) > 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 >= 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 <= 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() {