| /* |
| * 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 com.android.gallery.R; |
| |
| import com.android.camera.gallery.IImage; |
| import com.android.camera.gallery.IImageList; |
| |
| import android.app.Activity; |
| import android.app.Dialog; |
| import android.app.ProgressDialog; |
| import android.content.BroadcastReceiver; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.res.Resources; |
| import android.database.ContentObserver; |
| import android.graphics.Bitmap; |
| import android.graphics.Canvas; |
| import android.graphics.Matrix; |
| import android.graphics.Paint; |
| import android.graphics.PorterDuff; |
| import android.graphics.PorterDuffXfermode; |
| 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.provider.MediaStore; |
| import android.provider.MediaStore.Images; |
| import android.util.Log; |
| 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.ContextMenu.ContextMenuInfo; |
| 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; |
| |
| /** |
| * The GalleryPicker activity. |
| */ |
| public class GalleryPicker extends NoSearchActivity { |
| private static final String TAG = "GalleryPicker"; |
| |
| Handler mHandler = new Handler(); // handler for the main thread |
| Thread mWorkerThread; |
| BroadcastReceiver mReceiver; |
| ContentObserver mDbObserver; |
| GridView mGridView; |
| GalleryPickerAdapter mAdapter; // mAdapter is only accessed in main thread. |
| boolean mScanning; |
| boolean mUnmounted; |
| |
| @Override |
| public void onCreate(Bundle icicle) { |
| super.onCreate(icicle); |
| |
| setContentView(R.layout.gallerypicker); |
| |
| mGridView = (GridView) findViewById(R.id.albums); |
| |
| 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 ContextMenuInfo menuInfo) { |
| onCreateGalleryPickerContextMenu(menu, menuInfo); |
| } |
| }); |
| |
| mReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| onReceiveMediaBroadcast(intent); |
| } |
| }; |
| |
| mDbObserver = new ContentObserver(mHandler) { |
| @Override |
| public void onChange(boolean selfChange) { |
| rebake(false, ImageManager.isMediaScannerScanning( |
| getContentResolver())); |
| } |
| }; |
| |
| ImageManager.ensureOSXCompatibleFolder(); |
| } |
| |
| Dialog mMediaScanningDialog; |
| |
| // Display a dialog if the storage is being scanned now. |
| public void updateScanningDialog(boolean scanning) { |
| boolean prevScanning = (mMediaScanningDialog != null); |
| if (prevScanning == scanning && mAdapter.mItems.size() == 0) return; |
| // Now we are certain the state is changed. |
| if (prevScanning) { |
| mMediaScanningDialog.cancel(); |
| mMediaScanningDialog = null; |
| } else if (scanning && mAdapter.mItems.size() == 0) { |
| mMediaScanningDialog = ProgressDialog.show( |
| this, |
| null, |
| getResources().getString(R.string.wait), |
| true, |
| true); |
| } |
| } |
| |
| private View mNoImagesView; |
| |
| // Show/Hide the "no images" icon and text. Load resources on demand. |
| private void showNoImagesView() { |
| if (mNoImagesView == null) { |
| ViewGroup root = (ViewGroup) findViewById(R.id.root); |
| getLayoutInflater().inflate(R.layout.gallerypicker_no_images, root); |
| mNoImagesView = findViewById(R.id.no_images); |
| } |
| mNoImagesView.setVisibility(View.VISIBLE); |
| } |
| |
| private void hideNoImagesView() { |
| if (mNoImagesView != null) { |
| mNoImagesView.setVisibility(View.GONE); |
| } |
| } |
| |
| // The storage status is changed, restart the worker or show "no images". |
| private void rebake(boolean unmounted, boolean scanning) { |
| if (unmounted == mUnmounted && scanning == mScanning) return; |
| abortWorker(); |
| mUnmounted = unmounted; |
| mScanning = scanning; |
| updateScanningDialog(mScanning); |
| if (mUnmounted) { |
| showNoImagesView(); |
| } else { |
| hideNoImagesView(); |
| startWorker(); |
| } |
| } |
| |
| // This is called when we receive media-related broadcast. |
| private void onReceiveMediaBroadcast(Intent intent) { |
| String action = intent.getAction(); |
| if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) { |
| // SD card available |
| // TODO put up a "please wait" message |
| } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) { |
| // SD card unavailable |
| rebake(true, false); |
| } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) { |
| rebake(false, true); |
| } else if (action.equals( |
| Intent.ACTION_MEDIA_SCANNER_FINISHED)) { |
| rebake(false, false); |
| } else if (action.equals(Intent.ACTION_MEDIA_EJECT)) { |
| rebake(true, false); |
| } |
| } |
| |
| private void launchFolderGallery(int position) { |
| mAdapter.mItems.get(position).launch(this); |
| } |
| |
| private void onCreateGalleryPickerContextMenu(ContextMenu menu, |
| final ContextMenuInfo menuInfo) { |
| int position = ((AdapterContextMenuInfo) menuInfo).position; |
| menu.setHeaderTitle(mAdapter.baseTitleForPosition(position)); |
| // "Slide Show" |
| if ((mAdapter.getIncludeMediaTypes(position) |
| & ImageManager.INCLUDE_IMAGES) != 0) { |
| menu.add(R.string.slide_show) |
| .setOnMenuItemClickListener(new OnMenuItemClickListener() { |
| public boolean onMenuItemClick(MenuItem item) { |
| return onSlideShowClicked(menuInfo); |
| } |
| }); |
| } |
| // "View" |
| menu.add(R.string.view) |
| .setOnMenuItemClickListener(new OnMenuItemClickListener() { |
| public boolean onMenuItemClick(MenuItem item) { |
| return onViewClicked(menuInfo); |
| } |
| }); |
| } |
| |
| // This is called when the user clicks "Slideshow" from the context menu. |
| private boolean onSlideShowClicked(ContextMenuInfo menuInfo) { |
| AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; |
| int position = info.position; |
| |
| if (position < 0 || position >= mAdapter.mItems.size()) { |
| return true; |
| } |
| // Slide show starts from the first image on the list. |
| Item item = mAdapter.mItems.get(position); |
| Uri targetUri = item.mFirstImageUri; |
| |
| if (targetUri != null && item.mBucketId != null) { |
| targetUri = targetUri.buildUpon() |
| .appendQueryParameter("bucketId", item.mBucketId) |
| .build(); |
| } |
| Intent intent = new Intent(Intent.ACTION_VIEW, targetUri); |
| intent.putExtra("slideshow", true); |
| startActivity(intent); |
| return true; |
| } |
| |
| // This is called when the user clicks "View" from the context menu. |
| private boolean onViewClicked(ContextMenuInfo menuInfo) { |
| AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; |
| launchFolderGallery(info.position); |
| return true; |
| } |
| |
| @Override |
| public void onStop() { |
| super.onStop(); |
| |
| abortWorker(); |
| |
| unregisterReceiver(mReceiver); |
| getContentResolver().unregisterContentObserver(mDbObserver); |
| |
| // free up some ram |
| mAdapter = null; |
| mGridView.setAdapter(null); |
| unloadDrawable(); |
| } |
| |
| @Override |
| public void onStart() { |
| super.onStart(); |
| |
| mAdapter = new GalleryPickerAdapter(getLayoutInflater()); |
| mGridView.setAdapter(mAdapter); |
| |
| // install an intent filter to receive SD card related events. |
| IntentFilter intentFilter = new IntentFilter(); |
| intentFilter.addAction(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); |
| |
| getContentResolver().registerContentObserver( |
| MediaStore.Images.Media.EXTERNAL_CONTENT_URI, |
| true, mDbObserver); |
| |
| // Assume the storage is mounted and not scanning. |
| mUnmounted = false; |
| mScanning = false; |
| startWorker(); |
| } |
| |
| // This is used to stop the worker thread. |
| volatile boolean mAbort = false; |
| |
| // Create the worker thread. |
| private void startWorker() { |
| mAbort = false; |
| mWorkerThread = new Thread("GalleryPicker Worker") { |
| @Override |
| public void run() { |
| workerRun(); |
| } |
| }; |
| BitmapManager.instance().allowThreadDecoding(mWorkerThread); |
| mWorkerThread.start(); |
| } |
| |
| private void abortWorker() { |
| if (mWorkerThread != null) { |
| BitmapManager.instance().cancelThreadDecoding(mWorkerThread, getContentResolver()); |
| mAbort = true; |
| try { |
| mWorkerThread.join(); |
| } catch (InterruptedException ex) { |
| Log.e(TAG, "join interrupted"); |
| } |
| mWorkerThread = null; |
| // Remove all runnables in mHandler. |
| // (We assume that the "what" field in the messages are 0 |
| // for runnables). |
| mHandler.removeMessages(0); |
| mAdapter.clear(); |
| mAdapter.updateDisplay(); |
| clearImageLists(); |
| } |
| } |
| |
| // This is run in the worker thread. |
| private void workerRun() { |
| // We collect items from checkImageList() and checkBucketIds() and |
| // put them in allItems. Later we give allItems to checkThumbBitmap() |
| // and generated thumbnail bitmaps for each item. We do this instead of |
| // generating thumbnail bitmaps in checkImageList() and checkBucketIds() |
| // because we want to show all the folders first, then update them with |
| // the thumb bitmaps. (Generating thumbnail bitmaps takes some time.) |
| ArrayList<Item> allItems = new ArrayList<Item>(); |
| |
| checkScanning(); |
| if (mAbort) return; |
| |
| checkImageList(allItems); |
| if (mAbort) return; |
| |
| checkBucketIds(allItems); |
| if (mAbort) return; |
| |
| checkThumbBitmap(allItems); |
| if (mAbort) return; |
| |
| checkLowStorage(); |
| } |
| |
| // This is run in the worker thread. |
| private void checkScanning() { |
| ContentResolver cr = getContentResolver(); |
| final boolean scanning = |
| ImageManager.isMediaScannerScanning(cr); |
| mHandler.post(new Runnable() { |
| public void run() { |
| checkScanningFinished(scanning); |
| } |
| }); |
| } |
| |
| // This is run in the main thread. |
| private void checkScanningFinished(boolean scanning) { |
| updateScanningDialog(scanning); |
| } |
| |
| // This is run in the worker thread. |
| private void checkImageList(ArrayList<Item> allItems) { |
| int length = IMAGE_LIST_DATA.length; |
| IImageList[] lists = new IImageList[length]; |
| for (int i = 0; i < length; i++) { |
| ImageListData data = IMAGE_LIST_DATA[i]; |
| lists[i] = createImageList(data.mInclude, data.mBucketId, |
| getContentResolver()); |
| if (mAbort) return; |
| Item item = null; |
| |
| if (lists[i].isEmpty()) continue; |
| |
| // i >= 3 means we are looking at All Images/All Videos. |
| // lists[i-3] is the corresponding Camera Images/Camera Videos. |
| // We want to add the "All" list only if it's different from |
| // the "Camera" list. |
| if (i >= 3 && lists[i].getCount() == lists[i - 3].getCount()) { |
| continue; |
| } |
| |
| item = new Item(data.mType, |
| data.mBucketId, |
| getResources().getString(data.mStringId), |
| lists[i]); |
| |
| allItems.add(item); |
| |
| final Item finalItem = item; |
| mHandler.post(new Runnable() { |
| public void run() { |
| updateItem(finalItem); |
| } |
| }); |
| } |
| } |
| |
| // This is run in the main thread. |
| private void updateItem(Item item) { |
| // Hide NoImageView if we are going to add the first item |
| if (mAdapter.getCount() == 0) { |
| hideNoImagesView(); |
| } |
| mAdapter.addItem(item); |
| mAdapter.updateDisplay(); |
| } |
| |
| private static final String CAMERA_BUCKET = |
| ImageManager.CAMERA_IMAGE_BUCKET_ID; |
| |
| // This is run in the worker thread. |
| private void checkBucketIds(ArrayList<Item> allItems) { |
| final IImageList allImages; |
| if (!mScanning && !mUnmounted) { |
| allImages = ImageManager.makeImageList( |
| getContentResolver(), |
| ImageManager.DataLocation.ALL, |
| ImageManager.INCLUDE_IMAGES | ImageManager.INCLUDE_VIDEOS, |
| ImageManager.SORT_DESCENDING, |
| null); |
| } else { |
| allImages = ImageManager.makeEmptyImageList(); |
| } |
| |
| if (mAbort) { |
| allImages.close(); |
| return; |
| } |
| |
| HashMap<String, String> hashMap = allImages.getBucketIds(); |
| allImages.close(); |
| if (mAbort) return; |
| |
| for (Map.Entry<String, String> entry : hashMap.entrySet()) { |
| String key = entry.getKey(); |
| if (key == null) { |
| continue; |
| } |
| if (!key.equals(CAMERA_BUCKET)) { |
| IImageList list = createImageList( |
| ImageManager.INCLUDE_IMAGES |
| | ImageManager.INCLUDE_VIDEOS, key, |
| getContentResolver()); |
| if (mAbort) return; |
| |
| Item item = new Item(Item.TYPE_NORMAL_FOLDERS, key, |
| entry.getValue(), list); |
| |
| allItems.add(item); |
| |
| final Item finalItem = item; |
| mHandler.post(new Runnable() { |
| public void run() { |
| updateItem(finalItem); |
| } |
| }); |
| } |
| } |
| |
| mHandler.post(new Runnable() { |
| public void run() { |
| checkBucketIdsFinished(); |
| } |
| }); |
| } |
| |
| // This is run in the main thread. |
| private void checkBucketIdsFinished() { |
| |
| // If we just have one folder, open it. |
| // If we have zero folder, show the "no images" icon. |
| if (!mScanning) { |
| int numItems = mAdapter.mItems.size(); |
| if (numItems == 0) { |
| showNoImagesView(); |
| } else if (numItems == 1) { |
| mAdapter.mItems.get(0).launch(this); |
| finish(); |
| return; |
| } |
| } |
| } |
| |
| private static final int THUMB_SIZE = 142; |
| // This is run in the worker thread. |
| private void checkThumbBitmap(ArrayList<Item> allItems) { |
| for (Item item : allItems) { |
| final Bitmap b = makeMiniThumbBitmap(THUMB_SIZE, THUMB_SIZE, |
| item.mImageList); |
| if (mAbort) { |
| if (b != null) b.recycle(); |
| return; |
| } |
| |
| final Item finalItem = item; |
| mHandler.post(new Runnable() { |
| public void run() { |
| updateThumbBitmap(finalItem, b); |
| } |
| }); |
| } |
| } |
| |
| // This is run in the main thread. |
| private void updateThumbBitmap(Item item, Bitmap b) { |
| item.setThumbBitmap(b); |
| mAdapter.updateDisplay(); |
| } |
| |
| private static final long LOW_STORAGE_THRESHOLD = 1024 * 1024 * 2; |
| |
| // This is run in the worker thread. |
| private void checkLowStorage() { |
| // 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() { |
| checkLowStorageFinished(); |
| } |
| }); |
| } |
| } |
| } |
| |
| // This is run in the main thread. |
| // This is called only if the storage is low. |
| private void checkLowStorageFinished() { |
| Toast.makeText(GalleryPicker.this, R.string.not_enough_space, 5000) |
| .show(); |
| } |
| |
| // IMAGE_LIST_DATA stores the parameters for the four image lists |
| // we are interested in. The order of the IMAGE_LIST_DATA array is |
| // significant (See the implementation of GalleryPickerAdapter.init). |
| private static final class ImageListData { |
| ImageListData(int type, int include, String bucketId, int stringId) { |
| mType = type; |
| mInclude = include; |
| mBucketId = bucketId; |
| mStringId = stringId; |
| } |
| int mType; |
| int mInclude; |
| String mBucketId; |
| int mStringId; |
| } |
| |
| private static final ImageListData[] IMAGE_LIST_DATA = { |
| // Camera Images |
| new ImageListData(Item.TYPE_CAMERA_IMAGES, |
| ImageManager.INCLUDE_IMAGES, |
| ImageManager.CAMERA_IMAGE_BUCKET_ID, |
| R.string.gallery_camera_bucket_name), |
| // Camera Videos |
| new ImageListData(Item.TYPE_CAMERA_VIDEOS, |
| ImageManager.INCLUDE_VIDEOS, |
| ImageManager.CAMERA_IMAGE_BUCKET_ID, |
| R.string.gallery_camera_videos_bucket_name), |
| |
| // Camera Medias |
| new ImageListData(Item.TYPE_CAMERA_MEDIAS, |
| ImageManager.INCLUDE_VIDEOS | ImageManager.INCLUDE_IMAGES, |
| ImageManager.CAMERA_IMAGE_BUCKET_ID, |
| R.string.gallery_camera_media_bucket_name), |
| |
| // All Images |
| new ImageListData(Item.TYPE_ALL_IMAGES, |
| ImageManager.INCLUDE_IMAGES, |
| null, |
| R.string.all_images), |
| |
| // All Videos |
| new ImageListData(Item.TYPE_ALL_VIDEOS, |
| ImageManager.INCLUDE_VIDEOS, |
| null, |
| R.string.all_videos), |
| }; |
| |
| |
| // These drawables are loaded on-demand. |
| Drawable mFrameGalleryMask; |
| Drawable mCellOutline; |
| Drawable mVideoOverlay; |
| |
| private void loadDrawableIfNeeded() { |
| if (mFrameGalleryMask != null) return; // already loaded |
| Resources r = getResources(); |
| 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); |
| } |
| |
| private void unloadDrawable() { |
| mFrameGalleryMask = null; |
| mCellOutline = null; |
| mVideoOverlay = null; |
| } |
| |
| private static 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); |
| } |
| |
| // This is run in worker thread. |
| private Bitmap makeMiniThumbBitmap(int width, int height, |
| 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); |
| |
| // load the drawables |
| loadDrawableIfNeeded(); |
| |
| // draw the mask normally |
| p.setColor(0xFFFFFFFF); |
| mFrameGalleryMask.setBounds(0, 0, width, height); |
| mFrameGalleryMask.draw(c); |
| |
| Paint pdpaint = new Paint(); |
| pdpaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); |
| |
| pdpaint.setStyle(Paint.Style.FILL); |
| c.drawRect(0, 0, width, height, pdpaint); |
| |
| for (int i = 0; i < 4; i++) { |
| if (mAbort) { |
| return null; |
| } |
| |
| Bitmap temp = null; |
| 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; |
| } |
| |
| temp = Util.transform(m, temp, imageWidth, |
| imageHeight, true, Util.RECYCLE_INPUT); |
| } |
| |
| 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(Menu.NONE, Menu.NONE, MenuHelper.POSITION_GALLERY_SETTING, |
| 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; |
| } |
| |
| // image lists created by createImageList() are collected in mAllLists. |
| // They will be closed in clearImageList, so they don't hold open files |
| // on SD card. We will be killed if we don't close files when the SD card |
| // is unmounted. |
| ArrayList<IImageList> mAllLists = new ArrayList<IImageList>(); |
| |
| private IImageList createImageList(int mediaTypes, String bucketId, |
| ContentResolver cr) { |
| IImageList list = ImageManager.makeImageList( |
| cr, |
| ImageManager.DataLocation.ALL, |
| mediaTypes, |
| ImageManager.SORT_DESCENDING, |
| bucketId); |
| mAllLists.add(list); |
| return list; |
| } |
| |
| private void clearImageLists() { |
| for (IImageList list : mAllLists) { |
| list.close(); |
| } |
| mAllLists.clear(); |
| } |
| } |
| |
| // Item is the underlying data for GalleryPickerAdapter. |
| // It is passed from the activity to the adapter. |
| class Item { |
| public static final int TYPE_NONE = -1; |
| public static final int TYPE_ALL_IMAGES = 0; |
| public static final int TYPE_ALL_VIDEOS = 1; |
| public static final int TYPE_CAMERA_IMAGES = 2; |
| public static final int TYPE_CAMERA_VIDEOS = 3; |
| public static final int TYPE_CAMERA_MEDIAS = 4; |
| public static final int TYPE_NORMAL_FOLDERS = 5; |
| |
| public final int mType; |
| public final String mBucketId; |
| public final String mName; |
| public final IImageList mImageList; |
| public final int mCount; |
| public final Uri mFirstImageUri; // could be null if the list is empty |
| |
| // The thumbnail bitmap is set by setThumbBitmap() later because we want |
| // to let the user sees the folder icon as soon as possible (and possibly |
| // select them), then present more detailed information when we have it. |
| public Bitmap mThumbBitmap; // the thumbnail bitmap for the image list |
| |
| public Item(int type, String bucketId, String name, IImageList list) { |
| mType = type; |
| mBucketId = bucketId; |
| mName = name; |
| mImageList = list; |
| mCount = list.getCount(); |
| if (mCount > 0) { |
| mFirstImageUri = list.getImageAt(0).fullSizeImageUri(); |
| } else { |
| mFirstImageUri = null; |
| } |
| } |
| |
| public void setThumbBitmap(Bitmap thumbBitmap) { |
| mThumbBitmap = thumbBitmap; |
| } |
| |
| public boolean needsBucketId() { |
| return mType >= TYPE_CAMERA_IMAGES; |
| } |
| |
| public void launch(Activity activity) { |
| Uri uri = Images.Media.INTERNAL_CONTENT_URI; |
| if (needsBucketId()) { |
| uri = uri.buildUpon() |
| .appendQueryParameter("bucketId", mBucketId).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: |
| case TYPE_CAMERA_MEDIAS: |
| 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: |
| case TYPE_CAMERA_MEDIAS: |
| return R.drawable.frame_overlay_gallery_video; |
| case TYPE_NORMAL_FOLDERS: |
| default: |
| return R.drawable.frame_overlay_gallery_folder; |
| } |
| } |
| } |
| |
| class GalleryPickerAdapter extends BaseAdapter { |
| ArrayList<Item> mItems = new ArrayList<Item>(); |
| LayoutInflater mInflater; |
| |
| GalleryPickerAdapter(LayoutInflater inflater) { |
| mInflater = inflater; |
| } |
| |
| public void addItem(Item item) { |
| mItems.add(item); |
| } |
| |
| public void updateDisplay() { |
| notifyDataSetChanged(); |
| } |
| |
| public void clear() { |
| mItems.clear(); |
| } |
| |
| public int getCount() { |
| return mItems.size(); |
| } |
| |
| public Object getItem(int position) { |
| return null; |
| } |
| |
| public long getItemId(int position) { |
| return position; |
| } |
| |
| public String baseTitleForPosition(int position) { |
| return mItems.get(position).mName; |
| } |
| |
| public int getIncludeMediaTypes(int position) { |
| return mItems.get(position).getIncludeMediaTypes(); |
| } |
| |
| public View getView(final int position, View convertView, |
| ViewGroup parent) { |
| View v; |
| |
| if (convertView == null) { |
| v = mInflater.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); |
| Item item = mItems.get(position); |
| iv.setOverlay(item.getOverlay()); |
| if (item.mThumbBitmap != null) { |
| iv.setImageBitmap(item.mThumbBitmap); |
| String title = item.mName + " (" + item.mCount + ")"; |
| titleView.setText(title); |
| } else { |
| iv.setImageResource(android.R.color.transparent); |
| titleView.setText(item.mName); |
| } |
| |
| // An workaround due to a bug in TextView. If the length of text is |
| // different from the previous in convertView, the layout would be |
| // wrong. |
| titleView.requestLayout(); |
| |
| return v; |
| } |
| } |