Merge "Load wallpaper images on a bg thread" into klp-dev

This commit is contained in:
Michael Jurka
2013-10-29 11:41:47 +00:00
committed by Android (Google) Code Review
2 changed files with 222 additions and 60 deletions

View File

@ -31,11 +31,13 @@ import android.os.Build.VERSION_CODES;
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 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
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 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
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 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
* 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);

View File

@ -37,7 +37,6 @@ import android.graphics.RectF;
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 @@ public class WallpaperCropActivity extends Activity {
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 @@ public class WallpaperCropActivity extends Activity {
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() {