Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2b5297a4 authored by Michael Jurka's avatar Michael Jurka Committed by Android (Google) Code Review
Browse files

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

parents 7fd5c53c 5271ea16
Loading
Loading
Loading
Loading
+182 −56
Original line number Diff line number Diff line
@@ -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);
            }
        mRotation = source.getRotation();
        mDecoder = source.getBitmapRegionDecoder();
        mWidth = mDecoder.getWidth();
        mHeight = mDecoder.getHeight();
        } catch (IOException e) {
            Log.w("BitmapRegionTileSource", "ctor failed", e);
        }
        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);
+40 −4
Original line number Diff line number Diff line
@@ -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() {