Home | History | Annotate | Download | only in camera
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.camera;
     18 
     19 import com.android.gallery.R;
     20 
     21 import com.android.camera.gallery.IImage;
     22 import com.android.camera.gallery.IImageList;
     23 
     24 import android.app.Activity;
     25 import android.app.Dialog;
     26 import android.app.ProgressDialog;
     27 import android.content.BroadcastReceiver;
     28 import android.content.ContentResolver;
     29 import android.content.Context;
     30 import android.content.Intent;
     31 import android.content.IntentFilter;
     32 import android.content.res.Resources;
     33 import android.database.ContentObserver;
     34 import android.graphics.Bitmap;
     35 import android.graphics.Canvas;
     36 import android.graphics.Matrix;
     37 import android.graphics.Paint;
     38 import android.graphics.PorterDuff;
     39 import android.graphics.PorterDuffXfermode;
     40 import android.graphics.Rect;
     41 import android.graphics.drawable.Drawable;
     42 import android.net.Uri;
     43 import android.os.Bundle;
     44 import android.os.Environment;
     45 import android.os.Handler;
     46 import android.os.StatFs;
     47 import android.provider.MediaStore;
     48 import android.provider.MediaStore.Images;
     49 import android.util.Log;
     50 import android.view.ContextMenu;
     51 import android.view.LayoutInflater;
     52 import android.view.Menu;
     53 import android.view.MenuItem;
     54 import android.view.View;
     55 import android.view.ViewGroup;
     56 import android.view.ContextMenu.ContextMenuInfo;
     57 import android.view.MenuItem.OnMenuItemClickListener;
     58 import android.widget.AdapterView;
     59 import android.widget.BaseAdapter;
     60 import android.widget.GridView;
     61 import android.widget.TextView;
     62 import android.widget.Toast;
     63 import android.widget.AdapterView.AdapterContextMenuInfo;
     64 
     65 import java.util.ArrayList;
     66 import java.util.HashMap;
     67 import java.util.Map;
     68 
     69 /**
     70  * The GalleryPicker activity.
     71  */
     72 public class GalleryPicker extends NoSearchActivity {
     73     private static final String TAG = "GalleryPicker";
     74 
     75     Handler mHandler = new Handler();  // handler for the main thread
     76     Thread mWorkerThread;
     77     BroadcastReceiver mReceiver;
     78     ContentObserver mDbObserver;
     79     GridView mGridView;
     80     GalleryPickerAdapter mAdapter;  // mAdapter is only accessed in main thread.
     81     boolean mScanning;
     82     boolean mUnmounted;
     83 
     84     @Override
     85     public void onCreate(Bundle icicle) {
     86         super.onCreate(icicle);
     87 
     88         setContentView(R.layout.gallerypicker);
     89 
     90         mGridView = (GridView) findViewById(R.id.albums);
     91 
     92         mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
     93             public void onItemClick(AdapterView<?> parent, View view,
     94                                     int position, long id) {
     95                 launchFolderGallery(position);
     96             }
     97         });
     98 
     99         mGridView.setOnCreateContextMenuListener(
    100                 new View.OnCreateContextMenuListener() {
    101                     public void onCreateContextMenu(ContextMenu menu, View v,
    102                         final ContextMenuInfo menuInfo) {
    103                             onCreateGalleryPickerContextMenu(menu, menuInfo);
    104                     }
    105                 });
    106 
    107         mReceiver = new BroadcastReceiver() {
    108             @Override
    109             public void onReceive(Context context, Intent intent) {
    110                 onReceiveMediaBroadcast(intent);
    111             }
    112         };
    113 
    114         mDbObserver = new ContentObserver(mHandler) {
    115             @Override
    116             public void onChange(boolean selfChange) {
    117                 rebake(false, ImageManager.isMediaScannerScanning(
    118                         getContentResolver()));
    119             }
    120         };
    121 
    122         ImageManager.ensureOSXCompatibleFolder();
    123     }
    124 
    125     Dialog mMediaScanningDialog;
    126 
    127     // Display a dialog if the storage is being scanned now.
    128     public void updateScanningDialog(boolean scanning) {
    129         boolean prevScanning = (mMediaScanningDialog != null);
    130         if (prevScanning == scanning && mAdapter.mItems.size() == 0) return;
    131         // Now we are certain the state is changed.
    132         if (prevScanning) {
    133             mMediaScanningDialog.cancel();
    134             mMediaScanningDialog = null;
    135         } else if (scanning && mAdapter.mItems.size() == 0) {
    136             mMediaScanningDialog = ProgressDialog.show(
    137                     this,
    138                     null,
    139                     getResources().getString(R.string.wait),
    140                     true,
    141                     true);
    142         }
    143     }
    144 
    145     private View mNoImagesView;
    146 
    147     // Show/Hide the "no images" icon and text. Load resources on demand.
    148     private void showNoImagesView() {
    149         if (mNoImagesView == null) {
    150             ViewGroup root  = (ViewGroup) findViewById(R.id.root);
    151             getLayoutInflater().inflate(R.layout.gallerypicker_no_images, root);
    152             mNoImagesView = findViewById(R.id.no_images);
    153         }
    154         mNoImagesView.setVisibility(View.VISIBLE);
    155     }
    156 
    157     private void hideNoImagesView() {
    158         if (mNoImagesView != null) {
    159             mNoImagesView.setVisibility(View.GONE);
    160         }
    161     }
    162 
    163     // The storage status is changed, restart the worker or show "no images".
    164     private void rebake(boolean unmounted, boolean scanning) {
    165         if (unmounted == mUnmounted && scanning == mScanning) return;
    166         abortWorker();
    167         mUnmounted = unmounted;
    168         mScanning = scanning;
    169         updateScanningDialog(mScanning);
    170         if (mUnmounted) {
    171             showNoImagesView();
    172         } else {
    173             hideNoImagesView();
    174             startWorker();
    175         }
    176     }
    177 
    178     // This is called when we receive media-related broadcast.
    179     private void onReceiveMediaBroadcast(Intent intent) {
    180         String action = intent.getAction();
    181         if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
    182             // SD card available
    183             // TODO put up a "please wait" message
    184         } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
    185             // SD card unavailable
    186             rebake(true, false);
    187         } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
    188             rebake(false, true);
    189         } else if (action.equals(
    190                 Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
    191             rebake(false, false);
    192         } else if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
    193             rebake(true, false);
    194         }
    195     }
    196 
    197     private void launchFolderGallery(int position) {
    198         mAdapter.mItems.get(position).launch(this);
    199     }
    200 
    201     private void onCreateGalleryPickerContextMenu(ContextMenu menu,
    202             final ContextMenuInfo menuInfo) {
    203         int position = ((AdapterContextMenuInfo) menuInfo).position;
    204         menu.setHeaderTitle(mAdapter.baseTitleForPosition(position));
    205         // "Slide Show"
    206         if ((mAdapter.getIncludeMediaTypes(position)
    207                 & ImageManager.INCLUDE_IMAGES) != 0) {
    208             menu.add(R.string.slide_show)
    209                     .setOnMenuItemClickListener(new OnMenuItemClickListener() {
    210                         public boolean onMenuItemClick(MenuItem item) {
    211                             return onSlideShowClicked(menuInfo);
    212                         }
    213                     });
    214         }
    215         // "View"
    216         menu.add(R.string.view)
    217                 .setOnMenuItemClickListener(new OnMenuItemClickListener() {
    218                     public boolean onMenuItemClick(MenuItem item) {
    219                             return onViewClicked(menuInfo);
    220                     }
    221                 });
    222     }
    223 
    224     // This is called when the user clicks "Slideshow" from the context menu.
    225     private boolean onSlideShowClicked(ContextMenuInfo menuInfo) {
    226         AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
    227         int position = info.position;
    228 
    229         if (position < 0 || position >= mAdapter.mItems.size()) {
    230             return true;
    231         }
    232         // Slide show starts from the first image on the list.
    233         Item item = mAdapter.mItems.get(position);
    234         Uri targetUri = item.mFirstImageUri;
    235 
    236         if (targetUri != null && item.mBucketId != null) {
    237             targetUri = targetUri.buildUpon()
    238                     .appendQueryParameter("bucketId", item.mBucketId)
    239                     .build();
    240         }
    241         Intent intent = new Intent(Intent.ACTION_VIEW, targetUri);
    242         intent.putExtra("slideshow", true);
    243         startActivity(intent);
    244         return true;
    245     }
    246 
    247     // This is called when the user clicks "View" from the context menu.
    248     private boolean onViewClicked(ContextMenuInfo menuInfo) {
    249         AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
    250         launchFolderGallery(info.position);
    251         return true;
    252     }
    253 
    254     @Override
    255     public void onStop() {
    256         super.onStop();
    257 
    258         abortWorker();
    259 
    260         unregisterReceiver(mReceiver);
    261         getContentResolver().unregisterContentObserver(mDbObserver);
    262 
    263         // free up some ram
    264         mAdapter = null;
    265         mGridView.setAdapter(null);
    266         unloadDrawable();
    267     }
    268 
    269     @Override
    270     public void onStart() {
    271         super.onStart();
    272 
    273         mAdapter = new GalleryPickerAdapter(getLayoutInflater());
    274         mGridView.setAdapter(mAdapter);
    275 
    276         // install an intent filter to receive SD card related events.
    277         IntentFilter intentFilter = new IntentFilter();
    278         intentFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
    279         intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
    280         intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
    281         intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
    282         intentFilter.addAction(Intent.ACTION_MEDIA_EJECT);
    283         intentFilter.addDataScheme("file");
    284 
    285         registerReceiver(mReceiver, intentFilter);
    286 
    287         getContentResolver().registerContentObserver(
    288                 MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
    289                 true, mDbObserver);
    290 
    291         // Assume the storage is mounted and not scanning.
    292         mUnmounted = false;
    293         mScanning = false;
    294         startWorker();
    295     }
    296 
    297     // This is used to stop the worker thread.
    298     volatile boolean mAbort = false;
    299 
    300     // Create the worker thread.
    301     private void startWorker() {
    302         mAbort = false;
    303         mWorkerThread = new Thread("GalleryPicker Worker") {
    304             @Override
    305             public void run() {
    306                 workerRun();
    307             }
    308         };
    309         BitmapManager.instance().allowThreadDecoding(mWorkerThread);
    310         mWorkerThread.start();
    311     }
    312 
    313     private void abortWorker() {
    314         if (mWorkerThread != null) {
    315             BitmapManager.instance().cancelThreadDecoding(mWorkerThread, getContentResolver());
    316             mAbort = true;
    317             try {
    318                 mWorkerThread.join();
    319             } catch (InterruptedException ex) {
    320                 Log.e(TAG, "join interrupted");
    321             }
    322             mWorkerThread = null;
    323             // Remove all runnables in mHandler.
    324             // (We assume that the "what" field in the messages are 0
    325             // for runnables).
    326             mHandler.removeMessages(0);
    327             mAdapter.clear();
    328             mAdapter.updateDisplay();
    329             clearImageLists();
    330         }
    331     }
    332 
    333     // This is run in the worker thread.
    334     private void workerRun() {
    335         // We collect items from checkImageList() and checkBucketIds() and
    336         // put them in allItems. Later we give allItems to checkThumbBitmap()
    337         // and generated thumbnail bitmaps for each item. We do this instead of
    338         // generating thumbnail bitmaps in checkImageList() and checkBucketIds()
    339         // because we want to show all the folders first, then update them with
    340         // the thumb bitmaps. (Generating thumbnail bitmaps takes some time.)
    341         ArrayList<Item> allItems = new ArrayList<Item>();
    342 
    343         checkScanning();
    344         if (mAbort) return;
    345 
    346         checkImageList(allItems);
    347         if (mAbort) return;
    348 
    349         checkBucketIds(allItems);
    350         if (mAbort) return;
    351 
    352         checkThumbBitmap(allItems);
    353         if (mAbort) return;
    354 
    355         checkLowStorage();
    356     }
    357 
    358     // This is run in the worker thread.
    359     private void checkScanning() {
    360         ContentResolver cr = getContentResolver();
    361         final boolean scanning =
    362                 ImageManager.isMediaScannerScanning(cr);
    363         mHandler.post(new Runnable() {
    364                     public void run() {
    365                         checkScanningFinished(scanning);
    366                     }
    367                 });
    368     }
    369 
    370     // This is run in the main thread.
    371     private void checkScanningFinished(boolean scanning) {
    372         updateScanningDialog(scanning);
    373     }
    374 
    375     // This is run in the worker thread.
    376     private void checkImageList(ArrayList<Item> allItems) {
    377         int length = IMAGE_LIST_DATA.length;
    378         IImageList[] lists = new IImageList[length];
    379         for (int i = 0; i < length; i++) {
    380             ImageListData data = IMAGE_LIST_DATA[i];
    381             lists[i] = createImageList(data.mInclude, data.mBucketId,
    382                     getContentResolver());
    383             if (mAbort) return;
    384             Item item = null;
    385 
    386             if (lists[i].isEmpty()) continue;
    387 
    388             // i >= 3 means we are looking at All Images/All Videos.
    389             // lists[i-3] is the corresponding Camera Images/Camera Videos.
    390             // We want to add the "All" list only if it's different from
    391             // the "Camera" list.
    392             if (i >= 3 && lists[i].getCount() == lists[i - 3].getCount()) {
    393                 continue;
    394             }
    395 
    396             item = new Item(data.mType,
    397                             data.mBucketId,
    398                             getResources().getString(data.mStringId),
    399                             lists[i]);
    400 
    401             allItems.add(item);
    402 
    403             final Item finalItem = item;
    404             mHandler.post(new Runnable() {
    405                         public void run() {
    406                             updateItem(finalItem);
    407                         }
    408                     });
    409         }
    410     }
    411 
    412     // This is run in the main thread.
    413     private void updateItem(Item item) {
    414         // Hide NoImageView if we are going to add the first item
    415         if (mAdapter.getCount() == 0) {
    416             hideNoImagesView();
    417         }
    418         mAdapter.addItem(item);
    419         mAdapter.updateDisplay();
    420     }
    421 
    422     private static final String CAMERA_BUCKET =
    423             ImageManager.CAMERA_IMAGE_BUCKET_ID;
    424 
    425     // This is run in the worker thread.
    426     private void checkBucketIds(ArrayList<Item> allItems) {
    427         final IImageList allImages;
    428         if (!mScanning && !mUnmounted) {
    429             allImages = ImageManager.makeImageList(
    430                     getContentResolver(),
    431                     ImageManager.DataLocation.ALL,
    432                     ImageManager.INCLUDE_IMAGES | ImageManager.INCLUDE_VIDEOS,
    433                     ImageManager.SORT_DESCENDING,
    434                     null);
    435         } else {
    436             allImages = ImageManager.makeEmptyImageList();
    437         }
    438 
    439         if (mAbort) {
    440             allImages.close();
    441             return;
    442         }
    443 
    444         HashMap<String, String> hashMap = allImages.getBucketIds();
    445         allImages.close();
    446         if (mAbort) return;
    447 
    448         for (Map.Entry<String, String> entry : hashMap.entrySet()) {
    449             String key = entry.getKey();
    450             if (key == null) {
    451                 continue;
    452             }
    453             if (!key.equals(CAMERA_BUCKET)) {
    454                 IImageList list = createImageList(
    455                         ImageManager.INCLUDE_IMAGES
    456                         | ImageManager.INCLUDE_VIDEOS, key,
    457                         getContentResolver());
    458                 if (mAbort) return;
    459 
    460                 Item item = new Item(Item.TYPE_NORMAL_FOLDERS, key,
    461                         entry.getValue(), list);
    462 
    463                 allItems.add(item);
    464 
    465                 final Item finalItem = item;
    466                 mHandler.post(new Runnable() {
    467                             public void run() {
    468                                 updateItem(finalItem);
    469                             }
    470                         });
    471             }
    472         }
    473 
    474         mHandler.post(new Runnable() {
    475                     public void run() {
    476                         checkBucketIdsFinished();
    477                     }
    478                 });
    479     }
    480 
    481     // This is run in the main thread.
    482     private void checkBucketIdsFinished() {
    483 
    484         // If we just have one folder, open it.
    485         // If we have zero folder, show the "no images" icon.
    486         if (!mScanning) {
    487             int numItems = mAdapter.mItems.size();
    488             if (numItems == 0) {
    489                 showNoImagesView();
    490             } else if (numItems == 1) {
    491                 mAdapter.mItems.get(0).launch(this);
    492                 finish();
    493                 return;
    494             }
    495         }
    496     }
    497 
    498     private static final int THUMB_SIZE = 142;
    499     // This is run in the worker thread.
    500     private void checkThumbBitmap(ArrayList<Item> allItems) {
    501         for (Item item : allItems) {
    502             final Bitmap b = makeMiniThumbBitmap(THUMB_SIZE, THUMB_SIZE,
    503                     item.mImageList);
    504             if (mAbort) {
    505                 if (b != null) b.recycle();
    506                 return;
    507             }
    508 
    509             final Item finalItem = item;
    510             mHandler.post(new Runnable() {
    511                         public void run() {
    512                             updateThumbBitmap(finalItem, b);
    513                         }
    514                     });
    515         }
    516     }
    517 
    518     // This is run in the main thread.
    519     private void updateThumbBitmap(Item item, Bitmap b) {
    520         item.setThumbBitmap(b);
    521         mAdapter.updateDisplay();
    522     }
    523 
    524     private static final long LOW_STORAGE_THRESHOLD = 1024 * 1024 * 2;
    525 
    526     // This is run in the worker thread.
    527     private void checkLowStorage() {
    528         // Check available space only if we are writable
    529         if (ImageManager.hasStorage()) {
    530             String storageDirectory = Environment
    531                     .getExternalStorageDirectory().toString();
    532             StatFs stat = new StatFs(storageDirectory);
    533             long remaining = (long) stat.getAvailableBlocks()
    534                     * (long) stat.getBlockSize();
    535             if (remaining < LOW_STORAGE_THRESHOLD) {
    536                 mHandler.post(new Runnable() {
    537                     public void run() {
    538                         checkLowStorageFinished();
    539                     }
    540                 });
    541             }
    542         }
    543     }
    544 
    545     // This is run in the main thread.
    546     // This is called only if the storage is low.
    547     private void checkLowStorageFinished() {
    548         Toast.makeText(GalleryPicker.this, R.string.not_enough_space, 5000)
    549                 .show();
    550     }
    551 
    552     // IMAGE_LIST_DATA stores the parameters for the four image lists
    553     // we are interested in. The order of the IMAGE_LIST_DATA array is
    554     // significant (See the implementation of GalleryPickerAdapter.init).
    555     private static final class ImageListData {
    556         ImageListData(int type, int include, String bucketId, int stringId) {
    557             mType = type;
    558             mInclude = include;
    559             mBucketId = bucketId;
    560             mStringId = stringId;
    561         }
    562         int mType;
    563         int mInclude;
    564         String mBucketId;
    565         int mStringId;
    566     }
    567 
    568     private static final ImageListData[] IMAGE_LIST_DATA = {
    569         // Camera Images
    570         new ImageListData(Item.TYPE_CAMERA_IMAGES,
    571                           ImageManager.INCLUDE_IMAGES,
    572                           ImageManager.CAMERA_IMAGE_BUCKET_ID,
    573                           R.string.gallery_camera_bucket_name),
    574         // Camera Videos
    575         new ImageListData(Item.TYPE_CAMERA_VIDEOS,
    576                           ImageManager.INCLUDE_VIDEOS,
    577                           ImageManager.CAMERA_IMAGE_BUCKET_ID,
    578                           R.string.gallery_camera_videos_bucket_name),
    579 
    580         // Camera Medias
    581         new ImageListData(Item.TYPE_CAMERA_MEDIAS,
    582                 ImageManager.INCLUDE_VIDEOS | ImageManager.INCLUDE_IMAGES,
    583                 ImageManager.CAMERA_IMAGE_BUCKET_ID,
    584                 R.string.gallery_camera_media_bucket_name),
    585 
    586         // All Images
    587         new ImageListData(Item.TYPE_ALL_IMAGES,
    588                           ImageManager.INCLUDE_IMAGES,
    589                           null,
    590                           R.string.all_images),
    591 
    592         // All Videos
    593         new ImageListData(Item.TYPE_ALL_VIDEOS,
    594                           ImageManager.INCLUDE_VIDEOS,
    595                           null,
    596                           R.string.all_videos),
    597     };
    598 
    599 
    600     // These drawables are loaded on-demand.
    601     Drawable mFrameGalleryMask;
    602     Drawable mCellOutline;
    603     Drawable mVideoOverlay;
    604 
    605     private void loadDrawableIfNeeded() {
    606         if (mFrameGalleryMask != null) return;  // already loaded
    607         Resources r = getResources();
    608         mFrameGalleryMask = r.getDrawable(
    609                 R.drawable.frame_gallery_preview_album_mask);
    610         mCellOutline = r.getDrawable(android.R.drawable.gallery_thumb);
    611         mVideoOverlay = r.getDrawable(R.drawable.ic_gallery_video_overlay);
    612     }
    613 
    614     private void unloadDrawable() {
    615         mFrameGalleryMask = null;
    616         mCellOutline = null;
    617         mVideoOverlay = null;
    618     }
    619 
    620     private static void placeImage(Bitmap image, Canvas c, Paint paint,
    621             int imageWidth, int widthPadding, int imageHeight,
    622             int heightPadding, int offsetX, int offsetY,
    623             int pos) {
    624         int row = pos / 2;
    625         int col = pos - (row * 2);
    626 
    627         int xPos = (col * (imageWidth + widthPadding)) - offsetX;
    628         int yPos = (row * (imageHeight + heightPadding)) - offsetY;
    629 
    630         c.drawBitmap(image, xPos, yPos, paint);
    631     }
    632 
    633     // This is run in worker thread.
    634     private Bitmap makeMiniThumbBitmap(int width, int height,
    635             IImageList images) {
    636         int count = images.getCount();
    637         // We draw three different version of the folder image depending on the
    638         // number of images in the folder.
    639         //    For a single image, that image draws over the whole folder.
    640         //    For two or three images, we draw the two most recent photos.
    641         //    For four or more images, we draw four photos.
    642         final int padding = 4;
    643         int imageWidth = width;
    644         int imageHeight = height;
    645         int offsetWidth = 0;
    646         int offsetHeight = 0;
    647 
    648         imageWidth = (imageWidth - padding) / 2;  // 2 here because we show two
    649                                                   // images
    650         imageHeight = (imageHeight - padding) / 2;  // per row and column
    651 
    652         final Paint p = new Paint();
    653         final Bitmap b = Bitmap.createBitmap(width, height,
    654                 Bitmap.Config.ARGB_8888);
    655         final Canvas c = new Canvas(b);
    656         final Matrix m = new Matrix();
    657 
    658         // draw the whole canvas as transparent
    659         p.setColor(0x00000000);
    660         c.drawPaint(p);
    661 
    662         // load the drawables
    663         loadDrawableIfNeeded();
    664 
    665         // draw the mask normally
    666         p.setColor(0xFFFFFFFF);
    667         mFrameGalleryMask.setBounds(0, 0, width, height);
    668         mFrameGalleryMask.draw(c);
    669 
    670         Paint pdpaint = new Paint();
    671         pdpaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
    672 
    673         pdpaint.setStyle(Paint.Style.FILL);
    674         c.drawRect(0, 0, width, height, pdpaint);
    675 
    676         for (int i = 0; i < 4; i++) {
    677             if (mAbort) {
    678                 return null;
    679             }
    680 
    681             Bitmap temp = null;
    682             IImage image = i < count ? images.getImageAt(i) : null;
    683 
    684             if (image != null) {
    685                 temp = image.miniThumbBitmap();
    686             }
    687 
    688             if (temp != null) {
    689                 if (ImageManager.isVideo(image)) {
    690                     Bitmap newMap = temp.copy(temp.getConfig(), true);
    691                     Canvas overlayCanvas = new Canvas(newMap);
    692                     int overlayWidth = mVideoOverlay.getIntrinsicWidth();
    693                     int overlayHeight = mVideoOverlay.getIntrinsicHeight();
    694                     int left = (newMap.getWidth() - overlayWidth) / 2;
    695                     int top = (newMap.getHeight() - overlayHeight) / 2;
    696                     Rect newBounds = new Rect(left, top, left + overlayWidth,
    697                             top + overlayHeight);
    698                     mVideoOverlay.setBounds(newBounds);
    699                     mVideoOverlay.draw(overlayCanvas);
    700                     temp.recycle();
    701                     temp = newMap;
    702                 }
    703 
    704                 temp = Util.transform(m, temp, imageWidth,
    705                         imageHeight, true, Util.RECYCLE_INPUT);
    706             }
    707 
    708             Bitmap thumb = Bitmap.createBitmap(imageWidth, imageHeight,
    709                                                Bitmap.Config.ARGB_8888);
    710             Canvas tempCanvas = new Canvas(thumb);
    711             if (temp != null) {
    712                 tempCanvas.drawBitmap(temp, new Matrix(), new Paint());
    713             }
    714             mCellOutline.setBounds(0, 0, imageWidth, imageHeight);
    715             mCellOutline.draw(tempCanvas);
    716 
    717             placeImage(thumb, c, pdpaint, imageWidth, padding, imageHeight,
    718                        padding, offsetWidth, offsetHeight, i);
    719 
    720             thumb.recycle();
    721 
    722             if (temp != null) {
    723                 temp.recycle();
    724             }
    725         }
    726 
    727         return b;
    728     }
    729 
    730     @Override
    731     public boolean onCreateOptionsMenu(Menu menu) {
    732         super.onCreateOptionsMenu(menu);
    733 
    734         MenuHelper.addCaptureMenuItems(menu, this);
    735 
    736         menu.add(Menu.NONE, Menu.NONE, MenuHelper.POSITION_GALLERY_SETTING,
    737                 R.string.camerasettings)
    738                 .setOnMenuItemClickListener(new OnMenuItemClickListener() {
    739                     public boolean onMenuItemClick(MenuItem item) {
    740                         Intent preferences = new Intent();
    741                         preferences.setClass(GalleryPicker.this,
    742                                              GallerySettings.class);
    743                         startActivity(preferences);
    744                         return true;
    745                     }
    746                 })
    747                 .setAlphabeticShortcut('p')
    748                 .setIcon(android.R.drawable.ic_menu_preferences);
    749 
    750         return true;
    751     }
    752 
    753     // image lists created by createImageList() are collected in mAllLists.
    754     // They will be closed in clearImageList, so they don't hold open files
    755     // on SD card. We will be killed if we don't close files when the SD card
    756     // is unmounted.
    757     ArrayList<IImageList> mAllLists = new ArrayList<IImageList>();
    758 
    759     private IImageList createImageList(int mediaTypes, String bucketId,
    760             ContentResolver cr) {
    761         IImageList list = ImageManager.makeImageList(
    762                 cr,
    763                 ImageManager.DataLocation.ALL,
    764                 mediaTypes,
    765                 ImageManager.SORT_DESCENDING,
    766                 bucketId);
    767         mAllLists.add(list);
    768         return list;
    769     }
    770 
    771     private void clearImageLists() {
    772         for (IImageList list : mAllLists) {
    773             list.close();
    774         }
    775         mAllLists.clear();
    776     }
    777 }
    778 
    779 // Item is the underlying data for GalleryPickerAdapter.
    780 // It is passed from the activity to the adapter.
    781 class Item {
    782     public static final int TYPE_NONE = -1;
    783     public static final int TYPE_ALL_IMAGES = 0;
    784     public static final int TYPE_ALL_VIDEOS = 1;
    785     public static final int TYPE_CAMERA_IMAGES = 2;
    786     public static final int TYPE_CAMERA_VIDEOS = 3;
    787     public static final int TYPE_CAMERA_MEDIAS = 4;
    788     public static final int TYPE_NORMAL_FOLDERS = 5;
    789 
    790     public final int mType;
    791     public final String mBucketId;
    792     public final String mName;
    793     public final IImageList mImageList;
    794     public final int mCount;
    795     public final Uri mFirstImageUri;  // could be null if the list is empty
    796 
    797     // The thumbnail bitmap is set by setThumbBitmap() later because we want
    798     // to let the user sees the folder icon as soon as possible (and possibly
    799     // select them), then present more detailed information when we have it.
    800     public Bitmap mThumbBitmap;  // the thumbnail bitmap for the image list
    801 
    802     public Item(int type, String bucketId, String name, IImageList list) {
    803         mType = type;
    804         mBucketId = bucketId;
    805         mName = name;
    806         mImageList = list;
    807         mCount = list.getCount();
    808         if (mCount > 0) {
    809             mFirstImageUri = list.getImageAt(0).fullSizeImageUri();
    810         } else {
    811             mFirstImageUri = null;
    812         }
    813     }
    814 
    815     public void setThumbBitmap(Bitmap thumbBitmap) {
    816         mThumbBitmap = thumbBitmap;
    817     }
    818 
    819     public boolean needsBucketId() {
    820         return mType >= TYPE_CAMERA_IMAGES;
    821     }
    822 
    823     public void launch(Activity activity) {
    824         Uri uri = Images.Media.INTERNAL_CONTENT_URI;
    825         if (needsBucketId()) {
    826             uri = uri.buildUpon()
    827                     .appendQueryParameter("bucketId", mBucketId).build();
    828         }
    829         Intent intent = new Intent(Intent.ACTION_VIEW, uri);
    830         intent.putExtra("windowTitle", mName);
    831         intent.putExtra("mediaTypes", getIncludeMediaTypes());
    832         activity.startActivity(intent);
    833     }
    834 
    835     public int getIncludeMediaTypes() {
    836         return convertItemTypeToIncludedMediaType(mType);
    837     }
    838 
    839     public static int convertItemTypeToIncludedMediaType(int itemType) {
    840         switch (itemType) {
    841         case TYPE_ALL_IMAGES:
    842         case TYPE_CAMERA_IMAGES:
    843             return ImageManager.INCLUDE_IMAGES;
    844         case TYPE_ALL_VIDEOS:
    845         case TYPE_CAMERA_VIDEOS:
    846             return ImageManager.INCLUDE_VIDEOS;
    847         case TYPE_NORMAL_FOLDERS:
    848         case TYPE_CAMERA_MEDIAS:
    849         default:
    850             return ImageManager.INCLUDE_IMAGES
    851                     | ImageManager.INCLUDE_VIDEOS;
    852         }
    853     }
    854 
    855     public int getOverlay() {
    856         switch (mType) {
    857             case TYPE_ALL_IMAGES:
    858             case TYPE_CAMERA_IMAGES:
    859                 return R.drawable.frame_overlay_gallery_camera;
    860             case TYPE_ALL_VIDEOS:
    861             case TYPE_CAMERA_VIDEOS:
    862             case TYPE_CAMERA_MEDIAS:
    863                 return R.drawable.frame_overlay_gallery_video;
    864             case TYPE_NORMAL_FOLDERS:
    865             default:
    866                 return R.drawable.frame_overlay_gallery_folder;
    867         }
    868     }
    869 }
    870 
    871 class GalleryPickerAdapter extends BaseAdapter {
    872     ArrayList<Item> mItems = new ArrayList<Item>();
    873     LayoutInflater mInflater;
    874 
    875     GalleryPickerAdapter(LayoutInflater inflater) {
    876         mInflater = inflater;
    877     }
    878 
    879     public void addItem(Item item) {
    880         mItems.add(item);
    881     }
    882 
    883     public void updateDisplay() {
    884         notifyDataSetChanged();
    885     }
    886 
    887     public void clear() {
    888         mItems.clear();
    889     }
    890 
    891     public int getCount() {
    892         return mItems.size();
    893     }
    894 
    895     public Object getItem(int position) {
    896         return null;
    897     }
    898 
    899     public long getItemId(int position) {
    900         return position;
    901     }
    902 
    903     public String baseTitleForPosition(int position) {
    904         return mItems.get(position).mName;
    905     }
    906 
    907     public int getIncludeMediaTypes(int position) {
    908         return mItems.get(position).getIncludeMediaTypes();
    909     }
    910 
    911     public View getView(final int position, View convertView,
    912                         ViewGroup parent) {
    913         View v;
    914 
    915         if (convertView == null) {
    916             v = mInflater.inflate(R.layout.gallery_picker_item, null);
    917         } else {
    918             v = convertView;
    919         }
    920 
    921         TextView titleView = (TextView) v.findViewById(R.id.title);
    922 
    923         GalleryPickerItem iv =
    924                 (GalleryPickerItem) v.findViewById(R.id.thumbnail);
    925         Item item = mItems.get(position);
    926         iv.setOverlay(item.getOverlay());
    927         if (item.mThumbBitmap != null) {
    928             iv.setImageBitmap(item.mThumbBitmap);
    929             String title = item.mName + " (" + item.mCount + ")";
    930             titleView.setText(title);
    931         } else {
    932             iv.setImageResource(android.R.color.transparent);
    933             titleView.setText(item.mName);
    934         }
    935 
    936         // An workaround due to a bug in TextView. If the length of text is
    937         // different from the previous in convertView, the layout would be
    938         // wrong.
    939         titleView.requestLayout();
    940 
    941         return v;
    942     }
    943 }
    944