FileDocCategorySizeDatePackage
GalleryPicker.javaAPI DocAndroid 1.5 API28257Wed May 06 22:42:42 BST 2009com.android.camera

GalleryPicker.java

/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * 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 com.android.camera;

import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.StatFs;
import android.preference.PreferenceManager;
import android.provider.MediaStore.Images;
import android.util.Config;
import android.util.Log;
import android.util.SparseArray;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.MenuItem.OnMenuItemClickListener;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class GalleryPicker extends Activity {
    static private final String TAG = "GalleryPicker";

    private View mNoImagesView;
    GridView mGridView;
    Drawable mFrameGalleryMask;
    Drawable mCellOutline;
    Drawable mVideoOverlay;

    BroadcastReceiver mReceiver;
    GalleryPickerAdapter mAdapter;

    Dialog mMediaScanningDialog;

    SharedPreferences mPrefs;

    boolean mPausing = false;

    private static long LOW_STORAGE_THRESHOLD = 1024 * 1024 * 2;

    public GalleryPicker() {
    }

    private void rebake(boolean unmounted, boolean scanning) {
        if (mMediaScanningDialog != null) {
            mMediaScanningDialog.cancel();
            mMediaScanningDialog = null;
        }
        if (scanning) {
            mMediaScanningDialog = ProgressDialog.show(
                    this,
                    null,
                    getResources().getString(R.string.wait),
                    true,
                    true);
        }
        if (mAdapter != null) {
            mAdapter.notifyDataSetChanged();
            mAdapter.init(!unmounted && !scanning);
        }

        if (!unmounted) {
            // Warn the user if space is getting low
            Thread t = new Thread(new Runnable() {
                public void run() {

                    // Check available space only if we are writable
                    if (ImageManager.hasStorage()) {
                        String storageDirectory = Environment.getExternalStorageDirectory().toString();
                        StatFs stat = new StatFs(storageDirectory);
                        long remaining = (long)stat.getAvailableBlocks() * (long)stat.getBlockSize();
                        if (remaining < LOW_STORAGE_THRESHOLD) {

                            mHandler.post(new Runnable() {
                                public void run() {
                                    Toast.makeText(GalleryPicker.this.getApplicationContext(),
                                        R.string.not_enough_space, 5000).show();
                                }
                            });
                        }
                    }
                }
            });
            t.start();
        }

        // If we just have zero or one folder, open it. (We shouldn't have just one folder
        // any more, but we can have zero folders.)
        mNoImagesView.setVisibility(View.GONE);
        if (!scanning) {
            int numItems = mAdapter.mItems.size();
            if (numItems == 0) {
                mNoImagesView.setVisibility(View.VISIBLE);
            } else if (numItems == 1) {
                mAdapter.mItems.get(0).launch(this);
                finish();
                return;
            }
        }
    }

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        mPrefs = PreferenceManager.getDefaultSharedPreferences(this);

        setContentView(R.layout.gallerypicker);

        mNoImagesView = findViewById(R.id.no_images);
        mGridView = (GridView) findViewById(R.id.albums);
        mGridView.setSelector(android.R.color.transparent);

        mReceiver = new BroadcastReceiver() {

            @Override
            public void onReceive(Context context, Intent intent) {
                if (Config.LOGV) Log.v(TAG, "onReceiveIntent " + intent.getAction());
                String action = intent.getAction();
                if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
                    // SD card available
                    // TODO put up a "please wait" message
                    // TODO also listen for the media scanner finished message
                } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
                    // SD card unavailable
                    if (Config.LOGV) Log.v(TAG, "sd card no longer available");
                    Toast.makeText(GalleryPicker.this, getResources().getString(R.string.wait), 5000);
                    rebake(true, false);
                } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
                    Toast.makeText(GalleryPicker.this, getResources().getString(R.string.wait), 5000);
                    rebake(false, true);
                } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
                    if (Config.LOGV)
                        Log.v(TAG, "rebake because of ACTION_MEDIA_SCANNER_FINISHED");
                    rebake(false, false);
                } else if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
                    if (Config.LOGV)
                        Log.v(TAG, "rebake because of ACTION_MEDIA_EJECT");
                    rebake(true, false);
                }
            }
        };

        mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                launchFolderGallery(position);
            }
        });
        mGridView.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
            public void onCreateContextMenu(ContextMenu menu, View v, final ContextMenu.ContextMenuInfo menuInfo) {
                int position = ((AdapterContextMenuInfo)menuInfo).position;
                menu.setHeaderTitle(mAdapter.baseTitleForPosition(position));
                if ((mAdapter.getIncludeMediaTypes(position) & ImageManager.INCLUDE_IMAGES) != 0) {
                    menu.add(0, 207, 0, R.string.slide_show)
                    .setOnMenuItemClickListener(new OnMenuItemClickListener() {
                        public boolean onMenuItemClick(MenuItem item) {
                            AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo;
                            int position = info.position;

                            Uri targetUri;
                            synchronized (mAdapter.mItems) {
                                if (position < 0 || position >= mAdapter.mItems.size()) {
                                    return true;
                                }
                                // the mFirstImageUris list includes the "all" uri
                                targetUri = mAdapter.mItems.get(position).mFirstImageUri;
                            }
                            if (targetUri != null && position > 0) {
                                targetUri = targetUri.buildUpon().appendQueryParameter("bucketId",
                                        mAdapter.mItems.get(info.position).mId).build();
                            }
    //                      Log.v(TAG, "URI to launch slideshow " + targetUri);
                            Intent intent = new Intent(Intent.ACTION_VIEW, targetUri);
                            intent.putExtra("slideshow", true);
                            startActivity(intent);
                            return true;
                        }
                    });
                }
                menu.add(0, 208, 0, R.string.view)
                .setOnMenuItemClickListener(new OnMenuItemClickListener() {
                    public boolean onMenuItemClick(MenuItem item) {
                        AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo;
                        launchFolderGallery(info.position);
                        return true;
                    }
                });
            }
        });
 
        ImageManager.ensureOSXCompatibleFolder();
    }

    private void launchFolderGallery(int position) {
        mAdapter.mItems.get(position).launch(this);
    }

    class ItemInfo {
        Bitmap bitmap;
        int count;
    }

    static class Item implements Comparable<Item>{
        // The type is also used as the sort order
        public final static int TYPE_NONE = -1;
        public final static int TYPE_ALL_IMAGES = 0;
        public final static int TYPE_ALL_VIDEOS = 1;
        public final static int TYPE_CAMERA_IMAGES = 2;
        public final static int TYPE_CAMERA_VIDEOS = 3;
        public final static int TYPE_NORMAL_FOLDERS = 4;

        public int mType;
        public String mId;
        public String mName;
        public Uri mFirstImageUri;
        public ItemInfo mThumb;

        public Item(int type, String id, String name) {
            mType = type;
            mId = id;
            mName = name;
        }

        public boolean needsBucketId() {
            return mType >= TYPE_CAMERA_IMAGES;
        }

        public void launch(Activity activity) {
            android.net.Uri uri = Images.Media.INTERNAL_CONTENT_URI;
            if (needsBucketId()) {
                uri = uri.buildUpon().appendQueryParameter("bucketId",mId).build();
            }
            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
            intent.putExtra("windowTitle", mName);
            intent.putExtra("mediaTypes", getIncludeMediaTypes());
            activity.startActivity(intent);
        }

        public int getIncludeMediaTypes() {
            return convertItemTypeToIncludedMediaType(mType);
        }

        public static int convertItemTypeToIncludedMediaType(int itemType) {
            switch (itemType) {
            case TYPE_ALL_IMAGES:
            case TYPE_CAMERA_IMAGES:
                return ImageManager.INCLUDE_IMAGES;
            case TYPE_ALL_VIDEOS:
            case TYPE_CAMERA_VIDEOS:
                return ImageManager.INCLUDE_VIDEOS;
            case TYPE_NORMAL_FOLDERS:
            default:
                return     ImageManager.INCLUDE_IMAGES | ImageManager.INCLUDE_VIDEOS;
            }
        }

        public int getOverlay() {
            switch (mType) {
            case TYPE_ALL_IMAGES:
            case TYPE_CAMERA_IMAGES:
                return R.drawable.frame_overlay_gallery_camera;
            case TYPE_ALL_VIDEOS:
            case TYPE_CAMERA_VIDEOS:
                return R.drawable.frame_overlay_gallery_video;
            case TYPE_NORMAL_FOLDERS:
                return R.drawable.frame_overlay_gallery_folder;
            default:
                return     -1;
            }
        }

        // sort based on the sort order, then the case-insensitive display name, then the id.
        public int compareTo(Item other) {
            int x = mType - other.mType;
            if (x == 0) {
                x = mName.compareToIgnoreCase(other.mName);
                if (x == 0) {
                    x = mId.compareTo(other.mId);
                }
            }
            return x;
        }
    }

    class GalleryPickerAdapter extends BaseAdapter {
        ArrayList<Item> mItems = new ArrayList<Item>();

        boolean mDone = false;
        CameraThread mWorkerThread;

        public void init(boolean assumeMounted) {
            mItems.clear();

            ImageManager.IImageList images;
            if (assumeMounted) {
                images = ImageManager.instance().allImages(
                        GalleryPicker.this,
                        getContentResolver(),
                        ImageManager.DataLocation.ALL,
                        ImageManager.INCLUDE_IMAGES | ImageManager.INCLUDE_VIDEOS,
                        ImageManager.SORT_DESCENDING);
            } else {
                images = ImageManager.instance().emptyImageList();
            }

            if (mWorkerThread != null) {
                try {
                    mDone = true;
                    if (Config.LOGV)
                        Log.v(TAG, "about to call join on thread " + mWorkerThread.getId());
                    mWorkerThread.join();
                } finally {
                    mWorkerThread = null;
                }
            }

            String cameraItem = ImageManager.CAMERA_IMAGE_BUCKET_ID;
            final HashMap<String, String> hashMap = images.getBucketIds();
            String cameraBucketId = null;
            for (Map.Entry<String, String> entry: hashMap.entrySet()) {
                String key = entry.getKey();
                if (key == null) {
                    continue;
                }
                if (key.equals(cameraItem)) {
                    cameraBucketId = key;
                } else {
                    mItems.add(new Item(Item.TYPE_NORMAL_FOLDERS, key, entry.getValue()));
                }
            }
            images.deactivate();
            notifyDataSetInvalidated();

            // Conditionally add all-images and all-videos folders.
            addBucket(Item.TYPE_ALL_IMAGES, null,
                    Item.TYPE_CAMERA_IMAGES, cameraBucketId, R.string.all_images);
            addBucket(Item.TYPE_ALL_VIDEOS, null,
                    Item.TYPE_CAMERA_VIDEOS, cameraBucketId, R.string.all_videos);

            if (cameraBucketId != null) {
                addBucket(Item.TYPE_CAMERA_IMAGES, cameraBucketId,
                        R.string.gallery_camera_bucket_name);
                addBucket(Item.TYPE_CAMERA_VIDEOS, cameraBucketId,
                        R.string.gallery_camera_videos_bucket_name);
            }

            java.util.Collections.sort(mItems);

            mDone = false;
            mWorkerThread = new CameraThread(new Runnable() {
                public void run() {
                    try {
                        // no images, nothing to do
                        if (mItems.size() == 0)
                            return;

                        for (int i = 0; i < mItems.size() && !mDone; i++) {
                            final Item item = mItems.get(i);
                            ImageManager.IImageList list = createImageList(
                                    item.getIncludeMediaTypes(), item.mId);
                            try {
                                if (mPausing) {
                                    break;
                                }
                                if (list.getCount() > 0)
                                    item.mFirstImageUri = list.getImageAt(0).fullSizeImageUri();

                                final Bitmap b = makeMiniThumbBitmap(142, 142, list);
                                final int pos = i;
                                final int count = list.getCount();
                                final Thread currentThread = Thread.currentThread();
                                mHandler.post(new Runnable() {
                                    public void run() {
                                        if (mPausing || currentThread != mWorkerThread.realThread()) {
                                            if (b != null) {
                                                b.recycle();
                                            }
                                            return;
                                        }

                                        ItemInfo info = new ItemInfo();
                                        info.bitmap = b;
                                        info.count = count;
                                        item.mThumb = info;

                                        final GridView grid = GalleryPicker.this.mGridView;
                                        final int firstVisible = grid.getFirstVisiblePosition();

                                        // Minor optimization -- only notify if the specified position is visible
                                        if ((pos >= firstVisible) && (pos < firstVisible + grid.getChildCount())) {
                                            GalleryPickerAdapter.this.notifyDataSetChanged();
                                        }
                                    }
                                });
                            } finally {
                                list.deactivate();
                            }
                        }
                    } catch (Exception ex) {
                        Log.e(TAG, "got exception generating collage views ", ex);
                    }
                }
            });
            mWorkerThread.start();
            mWorkerThread.toBackground();
        }

        /**
         * Add a bucket, but only if it's interesting.
         * Interesting means non-empty and not duplicated by the
         * corresponding camera bucket.
         */
        private void addBucket(int itemType, String bucketId,
                int cameraItemType, String cameraBucketId,
                int labelId) {
            int itemCount = bucketItemCount(
                    Item.convertItemTypeToIncludedMediaType(itemType), bucketId);
            if (itemCount == 0) {
                return; // Bucket is empty, so don't show it.
            }
            int cameraItemCount = 0;
            if (cameraBucketId != null) {
                cameraItemCount = bucketItemCount(
                        Item.convertItemTypeToIncludedMediaType(cameraItemType), cameraBucketId);
            }
            if (cameraItemCount == itemCount) {
                return; // Bucket is the same as the camera bucket, so don't show it.
            }
            mItems.add(new Item(itemType, bucketId, getResources().getString(labelId)));
        }

        /**
         * Add a bucket, but only if it's interesting.
         * Interesting means non-empty.
         */
        private void addBucket(int itemType, String bucketId,
                int labelId) {
            if (!isEmptyBucket(Item.convertItemTypeToIncludedMediaType(itemType), bucketId)) {
                mItems.add(new Item(itemType, bucketId, getResources().getString(labelId)));
            }
        }

        public int getCount() {
            return mItems.size();
        }

        public Object getItem(int position) {
            return null;
        }

        public long getItemId(int position) {
            return position;
        }

        private String baseTitleForPosition(int position) {
            return mItems.get(position).mName;
        }

        private int getIncludeMediaTypes(int position) {
            return mItems.get(position).getIncludeMediaTypes();
        }

        public View getView(final int position, View convertView, ViewGroup parent) {
            View v;

            if (convertView == null) {
                LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                v = vi.inflate(R.layout.gallery_picker_item, null);
            } else {
                v = convertView;
            }

            TextView titleView = (TextView) v.findViewById(R.id.title);

            GalleryPickerItem iv = (GalleryPickerItem) v.findViewById(R.id.thumbnail);
            iv.setOverlay(mItems.get(position).getOverlay());
            ItemInfo info = mItems.get(position).mThumb;
            if (info != null) {
                iv.setImageBitmap(info.bitmap);
                String title = baseTitleForPosition(position) + " (" + info.count + ")";
                titleView.setText(title);
            } else {
                iv.setImageResource(android.R.color.transparent);
                titleView.setText(baseTitleForPosition(position));
            }

            return v;
        }
    };

    @Override
    public void onPause() {
        super.onPause();
        mPausing = true;
        unregisterReceiver(mReceiver);

        // free up some ram
        mAdapter = null;
        mGridView.setAdapter(null);
        System.gc();
    }

    @Override
    public void onResume() {
        super.onResume();
        mPausing = false;

        mAdapter = new GalleryPickerAdapter();
        mGridView.setAdapter(mAdapter);
        setBackgrounds(getResources());

        boolean scanning = ImageManager.isMediaScannerScanning(this);
        rebake(false, scanning);

        // install an intent filter to receive SD card related events.
        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
        intentFilter.addAction(Intent.ACTION_MEDIA_EJECT);
        intentFilter.addDataScheme("file");

        registerReceiver(mReceiver, intentFilter);
        MenuHelper.requestOrientation(this, mPrefs);
    }



    private void setBackgrounds(Resources r) {
        mFrameGalleryMask = r.getDrawable(R.drawable.frame_gallery_preview_album_mask);

        mCellOutline = r.getDrawable(android.R.drawable.gallery_thumb);
        mVideoOverlay = r.getDrawable(R.drawable.ic_gallery_video_overlay);
    }

    Handler mHandler = new Handler();

    private void placeImage(Bitmap image, Canvas c, Paint paint, int imageWidth, int widthPadding, int imageHeight, int heightPadding, int offsetX, int offsetY, int pos) {
        int row = pos / 2;
        int col = pos - (row * 2);

        int xPos = (col * (imageWidth + widthPadding)) - offsetX;
        int yPos = (row * (imageHeight + heightPadding)) - offsetY;

        c.drawBitmap(image, xPos, yPos, paint);
    }

    private Bitmap makeMiniThumbBitmap(int width, int height, ImageManager.IImageList images) {
        int count = images.getCount();
        // We draw three different version of the folder image depending on the number of images in the folder.
        //    For a single image, that image draws over the whole folder.
        //    For two or three images, we draw the two most recent photos.
        //    For four or more images, we draw four photos.
        final int padding = 4;
        int imageWidth = width;
        int imageHeight = height;
        int offsetWidth = 0;
        int offsetHeight = 0;

        imageWidth = (imageWidth - padding) / 2;     // 2 here because we show two images
        imageHeight = (imageHeight - padding) / 2;   // per row and column

        final Paint  p = new Paint();
        final Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        final Canvas c = new Canvas(b);

        final Matrix m = new Matrix();

        // draw the whole canvas as transparent
        p.setColor(0x00000000);
        c.drawPaint(p);

        // draw the mask normally
        p.setColor(0xFFFFFFFF);
        mFrameGalleryMask.setBounds(0, 0, width, height);
        mFrameGalleryMask.draw(c);

        Paint pdpaint = new Paint();
        pdpaint.setXfermode(new android.graphics.PorterDuffXfermode(
                                    android.graphics.PorterDuff.Mode.SRC_IN));

        pdpaint.setStyle(Paint.Style.FILL);
        c.drawRect(0, 0, width, height, pdpaint);

        for (int i = 0; i < 4; i++) {
            if (mPausing) {
                return null;
            }

            Bitmap temp = null;
            ImageManager.IImage image = i < count ? images.getImageAt(i) : null;

            if (image != null) {
                temp = image.miniThumbBitmap();
            }

            if (temp != null) {
                if (ImageManager.isVideo(image)) {
                    Bitmap newMap = temp.copy(temp.getConfig(), true);
                    Canvas overlayCanvas = new Canvas(newMap);
                    int overlayWidth = mVideoOverlay.getIntrinsicWidth();
                    int overlayHeight = mVideoOverlay.getIntrinsicHeight();
                    int left = (newMap.getWidth() - overlayWidth) / 2;
                    int top = (newMap.getHeight() - overlayHeight) / 2;
                    Rect newBounds = new Rect(left, top, left + overlayWidth, top + overlayHeight);
                    mVideoOverlay.setBounds(newBounds);
                    mVideoOverlay.draw(overlayCanvas);
                    temp.recycle();
                    temp = newMap;
                }

                Bitmap temp2 = ImageLoader.transform(m, temp, imageWidth, imageHeight, true);
                if (temp2 != temp)
                    temp.recycle();
                temp = temp2;
            }

            Bitmap thumb = Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888);
            Canvas tempCanvas = new Canvas(thumb);
            if (temp != null)
                tempCanvas.drawBitmap(temp, new Matrix(), new Paint());
            mCellOutline.setBounds(0, 0, imageWidth, imageHeight);
            mCellOutline.draw(tempCanvas);

            placeImage(thumb, c, pdpaint, imageWidth, padding, imageHeight, padding, offsetWidth, offsetHeight, i);

            thumb.recycle();

            if (temp != null)
                temp.recycle();
        }

        return b;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);

        MenuHelper.addCaptureMenuItems(menu, this);

        menu.add(0, 0, 5, R.string.camerasettings)
        .setOnMenuItemClickListener(new OnMenuItemClickListener() {
            public boolean onMenuItemClick(MenuItem item) {
                Intent preferences = new Intent();
                preferences.setClass(GalleryPicker.this, GallerySettings.class);
                startActivity(preferences);
                return true;
            }
        })
        .setAlphabeticShortcut('p')
        .setIcon(android.R.drawable.ic_menu_preferences);

        return true;
    }

    private boolean isEmptyBucket(int mediaTypes, String bucketId) {
        // TODO: Find a more efficient way of calculating this
        ImageManager.IImageList list = createImageList(mediaTypes, bucketId);
        try {
            return list.isEmpty();
        }
        finally {
            list.deactivate();
        }
    }

    private int bucketItemCount(int mediaTypes, String bucketId) {
        // TODO: Find a more efficient way of calculating this
        ImageManager.IImageList list = createImageList(mediaTypes, bucketId);
        try {
            return list.getCount();
        }
        finally {
            list.deactivate();
        }
    }
    private ImageManager.IImageList createImageList(int mediaTypes, String bucketId) {
        return ImageManager.instance().allImages(
                this,
                getContentResolver(),
                ImageManager.DataLocation.ALL,
                mediaTypes,
                ImageManager.SORT_DESCENDING,
                bucketId);
    }
}