mobisocial.musubi.ImageGalleryActivity.java Source code

Java tutorial

Introduction

Here is the source code for mobisocial.musubi.ImageGalleryActivity.java

Source

/*
 * Copyright 2012 The Stanford MobiSocial Laboratory
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package mobisocial.musubi;

import java.io.IOException;
import java.io.InputStream;

import mobisocial.musubi.model.MObject;
import mobisocial.musubi.model.helpers.IdentitiesManager;
import mobisocial.musubi.model.helpers.ObjectManager;
import mobisocial.musubi.obj.action.EditPhotoAction;
import mobisocial.musubi.obj.action.SharePhotoAction;
import mobisocial.musubi.objects.PictureObj;
import mobisocial.musubi.ui.MusubiBaseActivity;
import mobisocial.musubi.util.CommonLayouts;
import mobisocial.musubi.util.InstrumentedActivity;
import mobisocial.musubi.util.PhotoTaker;
import mobisocial.musubi.util.SimpleCursorLoader;
import mobisocial.musubi.util.SlowGallery;
import mobisocial.socialkit.musubi.DbObj;
import mobisocial.socialkit.musubi.Musubi;

import org.json.JSONException;
import org.json.JSONObject;
import org.mobisocial.corral.CorralDownloadClient;
import org.mobisocial.corral.CorralDownloadHandler;

import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.support.v4.view.MenuItem;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.CursorAdapter;
import android.widget.Gallery;
import android.widget.ImageView;

/**
 * A gallery for viewing all photos in a feed.
 *
 */
//TODO: if someone deletes a picture from the feed while it is being shown, weird
//things happen
public class ImageGalleryActivity extends MusubiBaseActivity
        implements LoaderCallbacks<Cursor>, InstrumentedActivity, OnItemSelectedListener {
    public static final String EXTRA_DEFAULT_OBJECT_ID = "objectId";
    static final String EXTRA_SELECTION = "selection";
    private static final String TAG = "imageGallery";
    private static final boolean DBG = false;

    private Gallery mGallery;
    private ImageGalleryAdapter mAdapter;
    private Uri mFeedUri;
    private long mInitialObjId;
    private int mInitialSelection = -1;
    private CorralDownloadClient mCorralClient;
    private CorralHandler mCorralHandler;
    private Musubi mMusubi;
    int mScreenWidth;
    int mScreenHeight;
    long mCurrentObjId;
    SQLiteOpenHelper mDatabaseSource;
    ObjectManager mObjectManager;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
        if (getSupportActionBar() != null) {
            getSupportActionBar().hide();
        }
        DisplayMetrics dm = getResources().getDisplayMetrics();
        mScreenWidth = dm.widthPixels;
        mScreenHeight = dm.heightPixels;

        mCorralClient = CorralDownloadClient.getInstance(this);

        mFeedUri = getIntent().getData();
        mInitialObjId = getIntent().getLongExtra(EXTRA_DEFAULT_OBJECT_ID, -1);

        mGallery = new SlowGallery(this);
        mGallery.setBackgroundColor(Color.BLACK);
        mGallery.setOnItemSelectedListener(this);
        addContentView(mGallery, CommonLayouts.FULL_SCREEN);
        if (savedInstanceState != null) {
            mInitialSelection = savedInstanceState.getInt(EXTRA_SELECTION);
        }
        mMusubi = App.getMusubi(this);
        mDatabaseSource = App.getDatabaseSource(this);
        mObjectManager = new ObjectManager(mDatabaseSource);
        getSupportLoaderManager().initLoader(0, null, this);
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt(EXTRA_SELECTION, mGallery.getSelectedItemPosition());
    }

    @Override
    protected void onNewIntent(Intent intent) {
        if (MusubiBaseActivity.isTVModeEnabled(this)) {
            mGallery.setSelection(0);
        }
    }

    // Cursor must be ordered ASC.
    // The sort order and search order are opposite!
    private static int binarySearch(Cursor c, long id, int colId) {
        long test;
        int first = 0;
        int max = c.getCount();
        while (first < max) {
            int mid = (first + max) / 2;
            c.moveToPosition(mid);
            test = c.getLong(colId);
            if (id < test) {
                max = mid;
            } else if (id > test) {
                first = mid + 1;
            } else {
                return mid;
            }
        }
        return 0;
    }

    @Override
    protected void onResume() {
        super.onResume();

        mCorralHandler = new CorralHandler();
        mCorralHandler.start();
    }

    @Override
    protected void onPause() {
        super.onPause();
        cleanCorralImage();
        shutdownCorralThread();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

    private void shutdownCorralThread() {
        if (mCorralHandler != null) {
            Message msg = mCorralHandler.obtainQuitMessage();
            mCorralHandler.mHandler.sendMessage(msg);
            mCorralHandler = null;
        }
    }

    private class ImageGalleryAdapter extends CursorAdapter {
        private final Context mContext;
        private final int mInitialSelection;
        private final int COL_ID;

        public int getInitialSelection() {
            return mInitialSelection;
        }

        private ImageGalleryAdapter(Context context, Cursor c, int init) {
            super(context, c);
            mContext = context;
            mInitialSelection = init;
            COL_ID = c.getColumnIndexOrThrow(MObject.COL_ID);
        }

        @Override
        public void bindView(View view, Context context, Cursor cursor) {
            DbObj obj = mMusubi.objForId(cursor.getLong(0));
            //shouldn't happen unless someone deletes a picture.
            if (obj == null)
                return;
            ImageView im = (ImageView) view;
            im.setTag(cursor.getLong(COL_ID));

            byte[] bytes = obj.getRaw();
            if (bytes == null) {
                Log.e(TAG, "Null image bytes for " + im.getTag(), new Throwable());
                return;
            }
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inPurgeable = true;
            options.inInputShareable = true;
            Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
            im.setImageBitmap(bitmap);
        }

        @Override
        public View newView(Context context, Cursor cursor, ViewGroup parent) {
            ImageView im = new ImageView(mContext);
            im.setLayoutParams(
                    new Gallery.LayoutParams(Gallery.LayoutParams.MATCH_PARENT, Gallery.LayoutParams.MATCH_PARENT));
            im.setScaleType(ImageView.ScaleType.FIT_CENTER);
            im.setBackgroundColor(Color.BLACK);
            return im;
        }
    }

    private static final int MENU_EDIT = 3;
    private static final int MENU_SHARE = 4;
    private static final int MENU_SET_PROFILE = 5;

    @Override
    public boolean onCreateOptionsMenu(android.support.v4.view.Menu menu) {
        menu.add(0, MENU_EDIT, 1, "Edit");
        menu.add(0, MENU_SHARE, 2, "Share");
        menu.add(0, MENU_SET_PROFILE, 3, "Set as Profile"); // XXX Bug prevents last menu entry
        // from showing up on phones without a hardware menu button.
        return true;
    }

    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case MENU_SET_PROFILE: {
            new Thread() {
                public void run() {
                    long objId = (Long) mGallery.getSelectedView().getTag();
                    DbObj obj = mMusubi.objForId(objId);
                    byte[] picBytes = obj.getRaw();
                    IdentitiesManager idMan = new IdentitiesManager(
                            App.getDatabaseSource(ImageGalleryActivity.this));
                    idMan.updateMyProfileThumbnail(ImageGalleryActivity.this, picBytes, true);
                    toast("Set profile picture.");
                };
            }.start();
            return true;
        }
        case MENU_SHARE: {
            long objId = (Long) mGallery.getSelectedView().getTag();
            DbObj obj = mMusubi.objForId(objId);
            new SharePhotoAction().actOn(this, new PictureObj(), obj);
            return true;
        }
        case MENU_EDIT: {
            long objId = (Long) mGallery.getSelectedView().getTag();
            DbObj obj = mMusubi.objForId(objId);
            doActivityForResult(new EditPhotoAction.EditCallout(this, obj));
            return true;
        }
        default:
            return false;
        }
    }

    Uri loadFromCorral(CorralHandler handler, final DbObj obj) {
        if (MusubiBaseActivity.isDeveloperModeEnabled(this)) {
            try {
                // Fetch via remote
                return CorralDownloadHandler.startOrFetchDownload(this, obj).getResult();
            } catch (InterruptedException e) {
                Log.i(TAG, "Failed to get hd content", e);
            }
        }
        // Local-only
        return mCorralClient.getAvailableContentUri(obj);
    }

    private void cleanCorralImage() {
        if (mCorralImage != null && mCorralImage.getBitmap() != null) {
            mCorralView.setImageDrawable(mOriginalImage);
            mCorralImage.getBitmap().recycle();
            mCorralImage = null;
            mOriginalImage = null;
            mCorralView = null;
        }
    }

    void injectImage(Uri fileUri, final ImageView imageView, final DbObj mObj) {
        try {
            if ((Long) imageView.getTag() == mObj.getLocalId()) {
                if (DBG)
                    Log.d(TAG, "Opening HD file " + fileUri);

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if ((Long) imageView.getTag() == mObj.getLocalId()) {
                            cleanCorralImage();
                        }
                    }
                });

                final BitmapDrawable image = getBitmap(fileUri);
                if (image == null) {
                    return;
                }

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if ((Long) imageView.getTag() == mObj.getLocalId()) {
                            mCorralImage = image;
                            mOriginalImage = (BitmapDrawable) imageView.getDrawable();
                            mCorralView = imageView;
                            imageView.setImageDrawable(image);
                        }
                    }
                });
            }
        } catch (OutOfMemoryError e) {
            App.getUsageMetrics(this).report(e);
            Log.e(TAG, "error loading data from corral", e);
        }
    }

    class RotatedBitmapDrawable extends BitmapDrawable {
        float mRotation;

        public RotatedBitmapDrawable(Bitmap b, float rotation) {
            super(b);
            mRotation = rotation;
        }

        @Override
        public int getIntrinsicHeight() {
            if (mRotation > 89 && mRotation < 91 || mRotation > 269 && mRotation < 271) {
                return super.getIntrinsicWidth();
            } else {
                return super.getIntrinsicHeight();
            }
        }

        @Override
        public int getIntrinsicWidth() {
            if (mRotation > 89 && mRotation < 91 || mRotation > 269 && mRotation < 271) {
                return super.getIntrinsicHeight();
            } else {
                return super.getIntrinsicWidth();
            }
        }

        @Override
        public void draw(Canvas canvas) {
            int saveCount = canvas.save();

            Rect bounds = super.getBounds();

            canvas.rotate(mRotation, bounds.centerX(), bounds.centerY());
            if (mRotation > 89 && mRotation < 91 || mRotation > 269 && mRotation < 271) {
                canvas.scale((float) getIntrinsicHeight() / getIntrinsicWidth(),
                        (float) getIntrinsicWidth() / getIntrinsicHeight(), bounds.centerX(), bounds.centerY());
            }

            super.draw(canvas);

            canvas.restoreToCount(saveCount);
        }
    }

    //get rotated bitmap allowing the data to be shared
    BitmapDrawable getBitmap(Uri uri) {
        InputStream in = null;
        try {
            in = getContentResolver().openInputStream(uri);

            // Decode image size
            BitmapFactory.Options o = new BitmapFactory.Options();
            o.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(in, null, o);
            in.close();

            in = getContentResolver().openInputStream(uri);

            float rotation = PhotoTaker.rotationForImage(this, uri);

            DisplayMetrics dm = getResources().getDisplayMetrics();
            int screen_width = dm.widthPixels;
            int screen_height = dm.heightPixels;
            int scale = 1;
            if (rotation > 89 && rotation < 91 || rotation > 269 && rotation < 271) {
                int t = screen_width;
                screen_width = screen_height;
                screen_height = t;
            }
            while (o.outWidth / (scale + 1) >= screen_width && o.outHeight / (scale + 1) >= screen_height) {
                scale++;
            }

            o = new BitmapFactory.Options();
            o.inPurgeable = true;
            o.inInputShareable = true;
            o.inSampleSize = scale;
            Bitmap b = BitmapFactory.decodeStream(in, null, o);
            return new RotatedBitmapDrawable(b, rotation);
        } catch (IOException e) {
            Log.e(TAG, e.getMessage(), e);
            return null;
        }
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        return new SimpleCursorLoader(this) {

            @Override
            public Cursor loadInBackground() {
                long feedId = Long.parseLong(mFeedUri.getLastPathSegment());
                return mObjectManager.getTypedIdCursorForFeed(PictureObj.TYPE, feedId);
            }
        };
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        if (mAdapter == null) {
            int init = binarySearch(cursor, mInitialObjId, 0);
            cursor.moveToPosition(-1);
            mAdapter = new ImageGalleryAdapter(this, cursor, init);
            mGallery.setAdapter(mAdapter);
            mGallery.setSelection((mInitialSelection == -1) ? mAdapter.getInitialSelection() : mInitialSelection);
        } else {
            mAdapter.changeCursor(cursor);
        }
    }

    @Override
    public void onLoaderReset(Loader<Cursor> arg0) {
    }

    BitmapDrawable mCorralImage;
    BitmapDrawable mOriginalImage;
    ImageView mCorralView;

    @Override
    public void onItemSelected(AdapterView<?> adapter, View view, int position, long id) {
        if (view.getTag() == null) {
            return;
        }
        mCurrentObjId = (Long) view.getTag();
        DbObj obj = mMusubi.objForId(mCurrentObjId);
        //should happen unless someone deletes an obj from the feed.
        if (obj == null) {
            return;
        }
        if (obj.getJson() != null) {
            //this can get called after onPause so we have to check that there is a corral handler still
            if (obj.getJson().has(CorralDownloadClient.OBJ_LOCAL_URI) && mCorralHandler != null) {
                Message msg = mCorralHandler.obtainLoadImageMessage();
                msg.obj = new CorralArg(mCurrentObjId, (ImageView) view);
                mCorralHandler.mHandler.sendMessage(msg);
            }
        }
    }

    @Override
    public void onNothingSelected(AdapterView<?> arg0) {
    }

    class CorralHandler extends Thread {
        final int LOAD_IMAGE = 0;
        final int QUIT = 1;
        public Handler mHandler;
        boolean mLoaded = false;
        boolean mQuit = false;

        public void run() {
            Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
            Looper.prepare();

            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    if (hasMessages(QUIT)) {
                        //don't handle other buffered loads if possible
                        mQuit = true;
                        getLooper().quit();
                        return;
                    }
                    switch (msg.what) {
                    case LOAD_IMAGE:
                        CorralArg arg = (CorralArg) msg.obj;
                        if (arg.objId != mCurrentObjId) {
                            return;
                        }
                        DbObj obj = mMusubi.objForId(arg.objId);
                        if (obj == null) {
                            Log.w(TAG, "null object: " + arg.objId);
                            return;
                        }
                        Uri fileUri = loadFromCorral(CorralHandler.this, obj);
                        if (fileUri == null)
                            break;
                        if (hasMessages(LOAD_IMAGE) || hasMessages(QUIT))
                            break;
                        injectImage(fileUri, arg.imageView, obj);
                        arg.imageView = null;
                        arg.objId = -1;
                        break;
                    case QUIT:
                        mQuit = true;
                        getLooper().quit();
                        break;
                    }

                }
            };
            mLoaded = true;
            Looper.loop();
        }

        public Message obtainQuitMessage() {
            prepareHandler();
            Message msg = mHandler.obtainMessage();
            msg.what = QUIT;
            return msg;
        }

        public Message obtainLoadImageMessage() {
            prepareHandler();
            Message msg = mHandler.obtainMessage();
            msg.what = LOAD_IMAGE;
            return msg;
        }

        void prepareHandler() {
            while (!mLoaded) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                }
            }
        }
    }

    class CorralArg {
        long objId;
        ImageView imageView;

        public CorralArg(long objId, ImageView image) {
            this.objId = objId;
            this.imageView = image;
        }
    }
}