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

Commit 619a180b authored by Michael Jurka's avatar Michael Jurka
Browse files

Make wallpaper picker/cropper more robust

- don't crash if image passed to wallpaper picker
is invalid
- close input streams correctly

Bug: 11413915
Bug: 11380658
Bug: 11362731

Change-Id: I973e6bdc532d24a64efd6d174e89fdac626d7ee3
parent 88400d52
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -30,6 +30,14 @@
    <string name="folder_name"></string>
    <!-- Button label on Wallpaper picker screen; user selects this button to set a specific wallpaper -->
    <string name="wallpaper_instructions">Set wallpaper</string>
    <!-- Error message when an image is selected as a wallpaper,
         but the wallpaper picker cannot load it -->
    <string name="image_load_fail">Coudn\'t load image</string>
    <!-- Error message when an image is selected as a wallpaper,
         but the wallpaper cropper cannot load it. The user will
         usually see this when using another app and trying to set
         an image as the wallpaper -->
    <string name="wallpaper_load_fail">Couldn\'t load image as wallpaper</string>
    <!-- Shown when wallpapers are selected in Wallpaper picker -->
    <!-- String indicating how many media item(s) is(are) selected
            eg. 1 selected [CHAR LIMIT=30] -->
+1 −1
Original line number Diff line number Diff line
@@ -62,7 +62,7 @@ public class SavedWallpaperImages extends BaseAdapter implements ListAdapter {
            File file = new File(a.getFilesDir(), imageFilename);
            BitmapRegionTileSource.FilePathBitmapSource bitmapSource =
                    new BitmapRegionTileSource.FilePathBitmapSource(file.getAbsolutePath(), 1024);
            a.setCropViewTileSource(bitmapSource, false, true);
            a.setCropViewTileSource(bitmapSource, false, true, null);
        }
        @Override
        public void onSave(WallpaperPickerActivity a) {
+66 −31
Original line number Diff line number Diff line
@@ -41,10 +41,12 @@ import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
import android.widget.Toast;

import com.android.gallery3d.common.Utils;
import com.android.gallery3d.exif.ExifInterface;
import com.android.photos.BitmapRegionTileSource;
import com.android.photos.BitmapRegionTileSource.BitmapSource;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
@@ -109,12 +111,24 @@ public class WallpaperCropActivity extends Activity {
                });

        // Load image in background
        setCropViewTileSource(
                new BitmapRegionTileSource.UriBitmapSource(this, imageUri, 1024), true, false);
        final BitmapRegionTileSource.UriBitmapSource bitmapSource =
                new BitmapRegionTileSource.UriBitmapSource(this, imageUri, 1024);
        Runnable onLoad = new Runnable() {
            public void run() {
                if (bitmapSource.getLoadingState() != BitmapSource.State.LOADED) {
                    Toast.makeText(WallpaperCropActivity.this,
                            getString(R.string.wallpaper_load_fail),
                            Toast.LENGTH_LONG).show();
                    finish();
                }
            }
        };
        setCropViewTileSource(bitmapSource, true, false, onLoad);
    }

    public void setCropViewTileSource(final BitmapRegionTileSource.BitmapSource bitmapSource,
            final boolean touchEnabled, final boolean moveToLeft) {
    public void setCropViewTileSource(
            final BitmapRegionTileSource.BitmapSource bitmapSource, final boolean touchEnabled,
            final boolean moveToLeft, final Runnable postExecute) {
        final Context context = WallpaperCropActivity.this;
        final View progressView = findViewById(R.id.loading);
        final AsyncTask<Void, Void, Void> loadBitmapTask = new AsyncTask<Void, Void, Void>() {
@@ -127,6 +141,7 @@ public class WallpaperCropActivity extends Activity {
            protected void onPostExecute(Void arg) {
                if (!isCancelled()) {
                    progressView.setVisibility(View.INVISIBLE);
                    if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
                        mCropView.setTileSource(
                                new BitmapRegionTileSource(context, bitmapSource), null);
                        mCropView.setTouchEnabled(touchEnabled);
@@ -135,6 +150,10 @@ public class WallpaperCropActivity extends Activity {
                        }
                    }
                }
                if (postExecute != null) {
                    postExecute.run();
                }
            }
        };
        // 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
@@ -235,10 +254,12 @@ public class WallpaperCropActivity extends Activity {
                InputStream is = context.getContentResolver().openInputStream(uri);
                BufferedInputStream bis = new BufferedInputStream(is);
                ei.readExif(bis);
                bis.close();
            } else {
                InputStream is = res.openRawResource(resId);
                BufferedInputStream bis = new BufferedInputStream(is);
                ei.readExif(bis);
                bis.close();
            }
            Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
            if (ori != null) {
@@ -408,7 +429,6 @@ public class WallpaperCropActivity extends Activity {
        String mInFilePath;
        byte[] mInImageBytes;
        int mInResId = 0;
        InputStream mInStream;
        RectF mCropBounds = null;
        int mOutWidth, mOutHeight;
        int mRotation;
@@ -481,37 +501,36 @@ public class WallpaperCropActivity extends Activity {
        }

        // Helper to setup input stream
        private void regenerateInputStream() {
        private InputStream regenerateInputStream() {
            if (mInUri == null && mInResId == 0 && mInFilePath == null && mInImageBytes == null) {
                Log.w(LOGTAG, "cannot read original file, no input URI, resource ID, or " +
                        "image byte array given");
            } else {
                Utils.closeSilently(mInStream);
                try {
                    if (mInUri != null) {
                        mInStream = new BufferedInputStream(
                        return new BufferedInputStream(
                                mContext.getContentResolver().openInputStream(mInUri));
                    } else if (mInFilePath != null) {
                        mInStream = mContext.openFileInput(mInFilePath);
                        return mContext.openFileInput(mInFilePath);
                    } else if (mInImageBytes != null) {
                        mInStream = new BufferedInputStream(
                                new ByteArrayInputStream(mInImageBytes));
                        return new BufferedInputStream(new ByteArrayInputStream(mInImageBytes));
                    } else {
                        mInStream = new BufferedInputStream(
                                mResources.openRawResource(mInResId));
                        return new BufferedInputStream(mResources.openRawResource(mInResId));
                    }
                } catch (FileNotFoundException e) {
                    Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
                }
            }
            return null;
        }

        public Point getImageBounds() {
            regenerateInputStream();
            if (mInStream != null) {
            InputStream is = regenerateInputStream();
            if (is != null) {
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inJustDecodeBounds = true;
                BitmapFactory.decodeStream(mInStream, null, options);
                BitmapFactory.decodeStream(is, null, options);
                Utils.closeSilently(is);
                if (options.outWidth != 0 && options.outHeight != 0) {
                    return new Point(options.outWidth, options.outHeight);
                }
@@ -529,22 +548,26 @@ public class WallpaperCropActivity extends Activity {
        public boolean cropBitmap() {
            boolean failure = false;

            regenerateInputStream();

            WallpaperManager wallpaperManager = null;
            if (mSetWallpaper) {
                wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext());
            }
            if (mSetWallpaper && mNoCrop && mInStream != null) {


            if (mSetWallpaper && mNoCrop) {
                try {
                    wallpaperManager.setStream(mInStream);
                    InputStream is = regenerateInputStream();
                    if (is != null) {
                        wallpaperManager.setStream(is);
                        Utils.closeSilently(is);
                    }
                } catch (IOException e) {
                    Log.w(LOGTAG, "cannot write stream to wallpaper", e);
                    failure = true;
                }
                return !failure;
            }
            if (mInStream != null) {
            } else {
                // Find crop bounds (scaled to original image size)
                Rect roundedTrueCrop = new Rect();
                Matrix rotateMatrix = new Matrix();
@@ -557,6 +580,11 @@ public class WallpaperCropActivity extends Activity {
                    mCropBounds = new RectF(roundedTrueCrop);

                    Point bounds = getImageBounds();
                    if (bounds == null) {
                        Log.w(LOGTAG, "cannot get bounds for image");
                        failure = true;
                        return false;
                    }

                    float[] rotatedBounds = new float[] { bounds.x, bounds.y };
                    rotateMatrix.mapPoints(rotatedBounds);
@@ -567,7 +595,6 @@ public class WallpaperCropActivity extends Activity {
                    inverseRotateMatrix.mapRect(mCropBounds);
                    mCropBounds.offset(bounds.x/2, bounds.y/2);

                    regenerateInputStream();
                }

                mCropBounds.roundOut(roundedTrueCrop);
@@ -585,7 +612,14 @@ public class WallpaperCropActivity extends Activity {
                // Attempt to open a region decoder
                BitmapRegionDecoder decoder = null;
                try {
                    decoder = BitmapRegionDecoder.newInstance(mInStream, true);
                    InputStream is = regenerateInputStream();
                    if (is == null) {
                        Log.w(LOGTAG, "cannot get input stream for uri=" + mInUri.toString());
                        failure = true;
                        return false;
                    }
                    decoder = BitmapRegionDecoder.newInstance(is, false);
                    Utils.closeSilently(is);
                } catch (IOException e) {
                    Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
                }
@@ -603,14 +637,15 @@ public class WallpaperCropActivity extends Activity {

                if (crop == null) {
                    // BitmapRegionDecoder has failed, try to crop in-memory
                    regenerateInputStream();
                    InputStream is = regenerateInputStream();
                    Bitmap fullSize = null;
                    if (mInStream != null) {
                    if (is != null) {
                        BitmapFactory.Options options = new BitmapFactory.Options();
                        if (scaleDownSampleSize > 1) {
                            options.inSampleSize = scaleDownSampleSize;
                        }
                        fullSize = BitmapFactory.decodeStream(mInStream, null, options);
                        fullSize = BitmapFactory.decodeStream(is, null, options);
                        Utils.closeSilently(is);
                    }
                    if (fullSize != null) {
                        mCropBounds.left /= scaleDownSampleSize;
+51 −23
Original line number Diff line number Diff line
@@ -67,8 +67,10 @@ import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.Toast;

import com.android.photos.BitmapRegionTileSource;
import com.android.photos.BitmapRegionTileSource.BitmapSource;

import java.io.File;
import java.io.FileOutputStream;
@@ -84,7 +86,7 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
    private static final String TEMP_WALLPAPER_TILES = "TEMP_WALLPAPER_TILES";
    private static final String DEFAULT_WALLPAPER_THUMBNAIL_FILENAME = "default_thumb.jpg";

    private View mSelectedThumb;
    private View mSelectedTile;
    private boolean mIgnoreNextTap;
    private OnClickListener mThumbnailOnClickListener;

@@ -128,13 +130,39 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {

    public static class UriWallpaperInfo extends WallpaperTileInfo {
        private Uri mUri;
        private boolean mFirstClick = true;
        private BitmapRegionTileSource.UriBitmapSource mBitmapSource;
        public UriWallpaperInfo(Uri uri) {
            mUri = uri;
        }
        @Override
        public void onClick(WallpaperPickerActivity a) {
            a.setCropViewTileSource(new BitmapRegionTileSource.UriBitmapSource(
                    a, mUri, BitmapRegionTileSource.MAX_PREVIEW_SIZE), true, false);
        public void onClick(final WallpaperPickerActivity a) {
            final Runnable onLoad;
            if (!mFirstClick) {
                onLoad = null;
            } else {
                mFirstClick = false;
                onLoad = new Runnable() {
                    public void run() {
                        if (mBitmapSource != null &&
                                mBitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
                            mView.setVisibility(View.VISIBLE);
                            a.selectTile(mView);
                        } else {
                            ViewGroup parent = (ViewGroup) mView.getParent();
                            if (parent != null) {
                                parent.removeView(mView);
                                Toast.makeText(a,
                                        a.getString(R.string.image_load_fail),
                                        Toast.LENGTH_SHORT).show();
                            }
                        }
                    }
                };
            }
            mBitmapSource = new BitmapRegionTileSource.UriBitmapSource(
                    a, mUri, BitmapRegionTileSource.MAX_PREVIEW_SIZE);
            a.setCropViewTileSource(mBitmapSource, true, false, onLoad);
        }
        @Override
        public void onSave(final WallpaperPickerActivity a) {
@@ -304,17 +332,8 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
                    return;
                }
                WallpaperTileInfo info = (WallpaperTileInfo) v.getTag();
                if (info.isSelectable()) {
                    if (mSelectedThumb != null) {
                        mSelectedThumb.setSelected(false);
                        mSelectedThumb = null;
                    }
                    mSelectedThumb = v;
                    v.setSelected(true);
                    // TODO: Remove this once the accessibility framework and
                    // services have better support for selection state.
                    v.announceForAccessibility(
                            getString(R.string.announce_selection, v.getContentDescription()));
                if (info.isSelectable() && v.getVisibility() == View.VISIBLE) {
                    selectTile(v);
                }
                info.onClick(WallpaperPickerActivity.this);
            }
@@ -440,8 +459,8 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
                new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (mSelectedThumb != null) {
                            WallpaperTileInfo info = (WallpaperTileInfo) mSelectedThumb.getTag();
                        if (mSelectedTile != null) {
                            WallpaperTileInfo info = (WallpaperTileInfo) mSelectedTile.getTag();
                            info.onSave(WallpaperPickerActivity.this);
                        }
                    }
@@ -520,15 +539,23 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
                    CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i);
                    c.setChecked(false);
                }
                mSelectedThumb.setSelected(true);
                mSelectedTile.setSelected(true);
                mActionMode = null;
            }
        };
    }
    @Override
    public void setCropViewTileSource(final BitmapRegionTileSource.BitmapSource bitmapSource,
            final boolean touchEnabled, boolean moveToLeft) {
        super.setCropViewTileSource(bitmapSource, touchEnabled, moveToLeft);

    private void selectTile(View v) {
        if (mSelectedTile != null) {
            mSelectedTile.setSelected(false);
            mSelectedTile = null;
        }
        mSelectedTile = v;
        v.setSelected(true);
        // TODO: Remove this once the accessibility framework and
        // services have better support for selection state.
        v.announceForAccessibility(
                getString(R.string.announce_selection, v.getContentDescription()));
    }

    private void initializeScrollForRtl() {
@@ -692,8 +719,9 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
    private void addTemporaryWallpaperTile(final Uri uri) {
        mTempWallpaperTiles.add(uri);
        // Add a tile for the image picked from Gallery
        FrameLayout pickedImageThumbnail = (FrameLayout) getLayoutInflater().
        final FrameLayout pickedImageThumbnail = (FrameLayout) getLayoutInflater().
                inflate(R.layout.wallpaper_picker_item, mWallpapersView, false);
        pickedImageThumbnail.setVisibility(View.GONE);
        setWallpaperItemPaddingToZero(pickedImageThumbnail);
        mWallpapersView.addView(pickedImageThumbnail, 0);

+69 −40
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import android.os.Build.VERSION_CODES;
import android.util.Log;

import com.android.gallery3d.common.BitmapUtils;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.exif.ExifInterface;
import com.android.gallery3d.glrenderer.BasicTexture;
import com.android.gallery3d.glrenderer.BitmapTexture;
@@ -62,10 +63,12 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
        private Bitmap mPreview;
        private int mPreviewSize;
        private int mRotation;
        public enum State { NOT_LOADED, LOADED, ERROR_LOADING };
        private State mState = State.NOT_LOADED;
        public BitmapSource(int previewSize) {
            mPreviewSize = previewSize;
        }
        public void loadInBackground() {
        public boolean loadInBackground() {
            ExifInterface ei = new ExifInterface();
            if (readExif(ei)) {
                Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
@@ -74,6 +77,10 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
                }
            }
            mDecoder = loadBitmapRegionDecoder();
            if (mDecoder == null) {
                mState = State.ERROR_LOADING;
                return false;
            } else {
                int width = mDecoder.getWidth();
                int height = mDecoder.getHeight();
                if (mPreviewSize != 0) {
@@ -87,6 +94,13 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
                    opts.inJustDecodeBounds = false;
                    mPreview = loadPreviewBitmap(opts);
                }
                mState = State.LOADED;
                return true;
            }
        }

        public State getLoadingState() {
            return mState;
        }

        public BitmapRegionDecoder getBitmapRegionDecoder() {
@@ -156,7 +170,10 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
        @Override
        public BitmapRegionDecoder loadBitmapRegionDecoder() {
            try {
                return BitmapRegionDecoder.newInstance(regenerateInputStream(), true);
                InputStream is = regenerateInputStream();
                BitmapRegionDecoder regionDecoder = BitmapRegionDecoder.newInstance(is, false);
                Utils.closeSilently(is);
                return regionDecoder;
            } catch (FileNotFoundException e) {
                Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
                return null;
@@ -168,7 +185,10 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
        @Override
        public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
            try {
                return BitmapFactory.decodeStream(regenerateInputStream(), null, options);
                InputStream is = regenerateInputStream();
                Bitmap b = BitmapFactory.decodeStream(is, null, options);
                Utils.closeSilently(is);
                return b;
            } catch (FileNotFoundException e) {
                Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
                return null;
@@ -177,13 +197,15 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
        @Override
        public boolean readExif(ExifInterface ei) {
            try {
                ei.readExif(regenerateInputStream());
                InputStream is = regenerateInputStream();
                ei.readExif(is);
                Utils.closeSilently(is);
                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);
                Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
                return false;
            }
        }
@@ -204,7 +226,10 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
        @Override
        public BitmapRegionDecoder loadBitmapRegionDecoder() {
            try {
                return BitmapRegionDecoder.newInstance(regenerateInputStream(), true);
                InputStream is = regenerateInputStream();
                BitmapRegionDecoder regionDecoder = BitmapRegionDecoder.newInstance(is, true);
                Utils.closeSilently(is);
                return regionDecoder;
            } catch (IOException e) {
                Log.e("BitmapRegionTileSource", "Error reading resource", e);
                return null;
@@ -217,7 +242,9 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
        @Override
        public boolean readExif(ExifInterface ei) {
            try {
                ei.readExif(regenerateInputStream());
                InputStream is = regenerateInputStream();
                ei.readExif(is);
                Utils.closeSilently(is);
                return true;
            } catch (IOException e) {
                Log.e("BitmapRegionTileSource", "Error reading resource", e);
@@ -243,6 +270,7 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
        mTileSize = TiledImageRenderer.suggestedTileSize(context);
        mRotation = source.getRotation();
        mDecoder = source.getBitmapRegionDecoder();
        if (mDecoder != null) {
            mWidth = mDecoder.getWidth();
            mHeight = mDecoder.getHeight();
            mOptions = new BitmapFactory.Options();
@@ -267,6 +295,7 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
                }
            }
        }
    }

    @Override
    public int getTileSize() {