GalleryPicker.java revision d6c2fb7a38fcdb58742fcfffd67a4594487ec71c
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 17package com.android.camera; 18 19import com.android.gallery.R; 20 21import com.android.camera.gallery.IImage; 22import com.android.camera.gallery.IImageList; 23 24import android.app.Activity; 25import android.app.Dialog; 26import android.app.ProgressDialog; 27import android.content.BroadcastReceiver; 28import android.content.ContentResolver; 29import android.content.Context; 30import android.content.Intent; 31import android.content.IntentFilter; 32import android.content.res.Resources; 33import android.database.ContentObserver; 34import android.graphics.Bitmap; 35import android.graphics.Canvas; 36import android.graphics.Matrix; 37import android.graphics.Paint; 38import android.graphics.PorterDuff; 39import android.graphics.PorterDuffXfermode; 40import android.graphics.Rect; 41import android.graphics.drawable.Drawable; 42import android.net.Uri; 43import android.os.Bundle; 44import android.os.Environment; 45import android.os.Handler; 46import android.os.StatFs; 47import android.provider.MediaStore; 48import android.provider.MediaStore.Images; 49import android.util.Log; 50import android.view.ContextMenu; 51import android.view.LayoutInflater; 52import android.view.Menu; 53import android.view.MenuItem; 54import android.view.View; 55import android.view.ViewGroup; 56import android.view.ContextMenu.ContextMenuInfo; 57import android.view.MenuItem.OnMenuItemClickListener; 58import android.widget.AdapterView; 59import android.widget.BaseAdapter; 60import android.widget.GridView; 61import android.widget.TextView; 62import android.widget.Toast; 63import android.widget.AdapterView.AdapterContextMenuInfo; 64 65import java.util.ArrayList; 66import java.util.HashMap; 67import java.util.Map; 68 69/** 70 * The GalleryPicker activity. 71 */ 72public class GalleryPicker extends Activity { 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); 316 MediaStore.Images.Thumbnails.cancelThumbnailRequest(getContentResolver(), -1); 317 mAbort = true; 318 try { 319 mWorkerThread.join(); 320 } catch (InterruptedException ex) { 321 Log.e(TAG, "join interrupted"); 322 } 323 mWorkerThread = null; 324 // Remove all runnables in mHandler. 325 // (We assume that the "what" field in the messages are 0 326 // for runnables). 327 mHandler.removeMessages(0); 328 mAdapter.clear(); 329 mAdapter.updateDisplay(); 330 clearImageLists(); 331 } 332 } 333 334 // This is run in the worker thread. 335 private void workerRun() { 336 337 // We collect items from checkImageList() and checkBucketIds() and 338 // put them in allItems. Later we give allItems to checkThumbBitmap() 339 // and generated thumbnail bitmaps for each item. We do this instead of 340 // generating thumbnail bitmaps in checkImageList() and checkBucketIds() 341 // because we want to show all the folders first, then update them with 342 // the thumb bitmaps. (Generating thumbnail bitmaps takes some time.) 343 ArrayList<Item> allItems = new ArrayList<Item>(); 344 345 checkScanning(); 346 if (mAbort) return; 347 348 checkImageList(allItems); 349 if (mAbort) return; 350 351 checkBucketIds(allItems); 352 if (mAbort) return; 353 354 checkThumbBitmap(allItems); 355 if (mAbort) return; 356 357 checkLowStorage(); 358 } 359 360 // This is run in the worker thread. 361 private void checkScanning() { 362 ContentResolver cr = getContentResolver(); 363 final boolean scanning = 364 ImageManager.isMediaScannerScanning(cr); 365 mHandler.post(new Runnable() { 366 public void run() { 367 checkScanningFinished(scanning); 368 } 369 }); 370 } 371 372 // This is run in the main thread. 373 private void checkScanningFinished(boolean scanning) { 374 updateScanningDialog(scanning); 375 } 376 377 // This is run in the worker thread. 378 private void checkImageList(ArrayList<Item> allItems) { 379 int length = IMAGE_LIST_DATA.length; 380 IImageList[] lists = new IImageList[length]; 381 for (int i = 0; i < length; i++) { 382 ImageListData data = IMAGE_LIST_DATA[i]; 383 lists[i] = createImageList(data.mInclude, data.mBucketId, 384 getContentResolver()); 385 if (mAbort) return; 386 Item item = null; 387 388 if (lists[i].isEmpty()) continue; 389 390 // i >= 3 means we are looking at All Images/All Videos. 391 // lists[i-3] is the corresponding Camera Images/Camera Videos. 392 // We want to add the "All" list only if it's different from 393 // the "Camera" list. 394 if (i >= 3 && lists[i].getCount() == lists[i - 3].getCount()) { 395 continue; 396 } 397 398 item = new Item(data.mType, 399 data.mBucketId, 400 getResources().getString(data.mStringId), 401 lists[i]); 402 403 allItems.add(item); 404 405 final Item finalItem = item; 406 mHandler.post(new Runnable() { 407 public void run() { 408 updateItem(finalItem); 409 } 410 }); 411 } 412 } 413 414 // This is run in the main thread. 415 private void updateItem(Item item) { 416 // Hide NoImageView if we are going to add the first item 417 if (mAdapter.getCount() == 0) { 418 hideNoImagesView(); 419 } 420 mAdapter.addItem(item); 421 mAdapter.updateDisplay(); 422 } 423 424 private static final String CAMERA_BUCKET = 425 ImageManager.CAMERA_IMAGE_BUCKET_ID; 426 427 // This is run in the worker thread. 428 private void checkBucketIds(ArrayList<Item> allItems) { 429 final IImageList allImages; 430 if (!mScanning && !mUnmounted) { 431 allImages = ImageManager.makeImageList( 432 getContentResolver(), 433 ImageManager.DataLocation.ALL, 434 ImageManager.INCLUDE_IMAGES | ImageManager.INCLUDE_VIDEOS, 435 ImageManager.SORT_DESCENDING, 436 null); 437 } else { 438 allImages = ImageManager.makeEmptyImageList(); 439 } 440 441 if (mAbort) { 442 allImages.close(); 443 return; 444 } 445 446 HashMap<String, String> hashMap = allImages.getBucketIds(); 447 allImages.close(); 448 if (mAbort) return; 449 450 for (Map.Entry<String, String> entry : hashMap.entrySet()) { 451 String key = entry.getKey(); 452 if (key == null) { 453 continue; 454 } 455 if (!key.equals(CAMERA_BUCKET)) { 456 IImageList list = createImageList( 457 ImageManager.INCLUDE_IMAGES 458 | ImageManager.INCLUDE_VIDEOS, key, 459 getContentResolver()); 460 if (mAbort) return; 461 462 Item item = new Item(Item.TYPE_NORMAL_FOLDERS, key, 463 entry.getValue(), list); 464 465 allItems.add(item); 466 467 final Item finalItem = item; 468 mHandler.post(new Runnable() { 469 public void run() { 470 updateItem(finalItem); 471 } 472 }); 473 } 474 } 475 476 mHandler.post(new Runnable() { 477 public void run() { 478 checkBucketIdsFinished(); 479 } 480 }); 481 } 482 483 // This is run in the main thread. 484 private void checkBucketIdsFinished() { 485 486 // If we just have one folder, open it. 487 // If we have zero folder, show the "no images" icon. 488 if (!mScanning) { 489 int numItems = mAdapter.mItems.size(); 490 if (numItems == 0) { 491 showNoImagesView(); 492 } else if (numItems == 1) { 493 mAdapter.mItems.get(0).launch(this); 494 finish(); 495 return; 496 } 497 } 498 } 499 500 private static final int THUMB_SIZE = 142; 501 // This is run in the worker thread. 502 private void checkThumbBitmap(ArrayList<Item> allItems) { 503 for (Item item : allItems) { 504 final Bitmap b = makeMiniThumbBitmap(THUMB_SIZE, THUMB_SIZE, 505 item.mImageList); 506 if (mAbort) { 507 if (b != null) b.recycle(); 508 return; 509 } 510 511 final Item finalItem = item; 512 mHandler.post(new Runnable() { 513 public void run() { 514 updateThumbBitmap(finalItem, b); 515 } 516 }); 517 } 518 } 519 520 // This is run in the main thread. 521 private void updateThumbBitmap(Item item, Bitmap b) { 522 item.setThumbBitmap(b); 523 mAdapter.updateDisplay(); 524 } 525 526 private static final long LOW_STORAGE_THRESHOLD = 1024 * 1024 * 2; 527 528 // This is run in the worker thread. 529 private void checkLowStorage() { 530 // Check available space only if we are writable 531 if (ImageManager.hasStorage()) { 532 String storageDirectory = Environment 533 .getExternalStorageDirectory().toString(); 534 StatFs stat = new StatFs(storageDirectory); 535 long remaining = (long) stat.getAvailableBlocks() 536 * (long) stat.getBlockSize(); 537 if (remaining < LOW_STORAGE_THRESHOLD) { 538 mHandler.post(new Runnable() { 539 public void run() { 540 checkLowStorageFinished(); 541 } 542 }); 543 } 544 } 545 } 546 547 // This is run in the main thread. 548 // This is called only if the storage is low. 549 private void checkLowStorageFinished() { 550 Toast.makeText(GalleryPicker.this, R.string.not_enough_space, 5000) 551 .show(); 552 } 553 554 // IMAGE_LIST_DATA stores the parameters for the four image lists 555 // we are interested in. The order of the IMAGE_LIST_DATA array is 556 // significant (See the implementation of GalleryPickerAdapter.init). 557 private static final class ImageListData { 558 ImageListData(int type, int include, String bucketId, int stringId) { 559 mType = type; 560 mInclude = include; 561 mBucketId = bucketId; 562 mStringId = stringId; 563 } 564 int mType; 565 int mInclude; 566 String mBucketId; 567 int mStringId; 568 } 569 570 private static final ImageListData[] IMAGE_LIST_DATA = { 571 // Camera Images 572 new ImageListData(Item.TYPE_CAMERA_IMAGES, 573 ImageManager.INCLUDE_IMAGES, 574 ImageManager.CAMERA_IMAGE_BUCKET_ID, 575 R.string.gallery_camera_bucket_name), 576 // Camera Videos 577 new ImageListData(Item.TYPE_CAMERA_VIDEOS, 578 ImageManager.INCLUDE_VIDEOS, 579 ImageManager.CAMERA_IMAGE_BUCKET_ID, 580 R.string.gallery_camera_videos_bucket_name), 581 582 // Camera Medias 583 new ImageListData(Item.TYPE_CAMERA_MEDIAS, 584 ImageManager.INCLUDE_VIDEOS | ImageManager.INCLUDE_IMAGES, 585 ImageManager.CAMERA_IMAGE_BUCKET_ID, 586 R.string.gallery_camera_media_bucket_name), 587 588 // All Images 589 new ImageListData(Item.TYPE_ALL_IMAGES, 590 ImageManager.INCLUDE_IMAGES, 591 null, 592 R.string.all_images), 593 594 // All Videos 595 new ImageListData(Item.TYPE_ALL_VIDEOS, 596 ImageManager.INCLUDE_VIDEOS, 597 null, 598 R.string.all_videos), 599 }; 600 601 602 // These drawables are loaded on-demand. 603 Drawable mFrameGalleryMask; 604 Drawable mCellOutline; 605 Drawable mVideoOverlay; 606 607 private void loadDrawableIfNeeded() { 608 if (mFrameGalleryMask != null) return; // already loaded 609 Resources r = getResources(); 610 mFrameGalleryMask = r.getDrawable( 611 R.drawable.frame_gallery_preview_album_mask); 612 mCellOutline = r.getDrawable(android.R.drawable.gallery_thumb); 613 mVideoOverlay = r.getDrawable(R.drawable.ic_gallery_video_overlay); 614 } 615 616 private void unloadDrawable() { 617 mFrameGalleryMask = null; 618 mCellOutline = null; 619 mVideoOverlay = null; 620 } 621 622 private static void placeImage(Bitmap image, Canvas c, Paint paint, 623 int imageWidth, int widthPadding, int imageHeight, 624 int heightPadding, int offsetX, int offsetY, 625 int pos) { 626 int row = pos / 2; 627 int col = pos - (row * 2); 628 629 int xPos = (col * (imageWidth + widthPadding)) - offsetX; 630 int yPos = (row * (imageHeight + heightPadding)) - offsetY; 631 632 c.drawBitmap(image, xPos, yPos, paint); 633 } 634 635 // This is run in worker thread. 636 private Bitmap makeMiniThumbBitmap(int width, int height, 637 IImageList images) { 638 int count = images.getCount(); 639 // We draw three different version of the folder image depending on the 640 // number of images in the folder. 641 // For a single image, that image draws over the whole folder. 642 // For two or three images, we draw the two most recent photos. 643 // For four or more images, we draw four photos. 644 final int padding = 4; 645 int imageWidth = width; 646 int imageHeight = height; 647 int offsetWidth = 0; 648 int offsetHeight = 0; 649 650 imageWidth = (imageWidth - padding) / 2; // 2 here because we show two 651 // images 652 imageHeight = (imageHeight - padding) / 2; // per row and column 653 654 final Paint p = new Paint(); 655 final Bitmap b = Bitmap.createBitmap(width, height, 656 Bitmap.Config.ARGB_8888); 657 final Canvas c = new Canvas(b); 658 final Matrix m = new Matrix(); 659 660 // draw the whole canvas as transparent 661 p.setColor(0x00000000); 662 c.drawPaint(p); 663 664 // load the drawables 665 loadDrawableIfNeeded(); 666 667 // draw the mask normally 668 p.setColor(0xFFFFFFFF); 669 mFrameGalleryMask.setBounds(0, 0, width, height); 670 mFrameGalleryMask.draw(c); 671 672 Paint pdpaint = new Paint(); 673 pdpaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); 674 675 pdpaint.setStyle(Paint.Style.FILL); 676 c.drawRect(0, 0, width, height, pdpaint); 677 678 for (int i = 0; i < 4; i++) { 679 if (mAbort) { 680 return null; 681 } 682 683 Bitmap temp = null; 684 IImage image = i < count ? images.getImageAt(i) : null; 685 686 if (image != null) { 687 temp = image.miniThumbBitmap(); 688 } 689 690 if (temp != null) { 691 if (ImageManager.isVideo(image)) { 692 Bitmap newMap = temp.copy(temp.getConfig(), true); 693 Canvas overlayCanvas = new Canvas(newMap); 694 int overlayWidth = mVideoOverlay.getIntrinsicWidth(); 695 int overlayHeight = mVideoOverlay.getIntrinsicHeight(); 696 int left = (newMap.getWidth() - overlayWidth) / 2; 697 int top = (newMap.getHeight() - overlayHeight) / 2; 698 Rect newBounds = new Rect(left, top, left + overlayWidth, 699 top + overlayHeight); 700 mVideoOverlay.setBounds(newBounds); 701 mVideoOverlay.draw(overlayCanvas); 702 temp.recycle(); 703 temp = newMap; 704 } 705 706 temp = Util.transform(m, temp, imageWidth, 707 imageHeight, true, Util.RECYCLE_INPUT); 708 } 709 710 Bitmap thumb = Bitmap.createBitmap(imageWidth, imageHeight, 711 Bitmap.Config.ARGB_8888); 712 Canvas tempCanvas = new Canvas(thumb); 713 if (temp != null) { 714 tempCanvas.drawBitmap(temp, new Matrix(), new Paint()); 715 } 716 mCellOutline.setBounds(0, 0, imageWidth, imageHeight); 717 mCellOutline.draw(tempCanvas); 718 719 placeImage(thumb, c, pdpaint, imageWidth, padding, imageHeight, 720 padding, offsetWidth, offsetHeight, i); 721 722 thumb.recycle(); 723 724 if (temp != null) { 725 temp.recycle(); 726 } 727 } 728 729 return b; 730 } 731 732 @Override 733 public boolean onCreateOptionsMenu(Menu menu) { 734 super.onCreateOptionsMenu(menu); 735 736 MenuHelper.addCaptureMenuItems(menu, this); 737 738 menu.add(Menu.NONE, Menu.NONE, MenuHelper.POSITION_GALLERY_SETTING, 739 R.string.camerasettings) 740 .setOnMenuItemClickListener(new OnMenuItemClickListener() { 741 public boolean onMenuItemClick(MenuItem item) { 742 Intent preferences = new Intent(); 743 preferences.setClass(GalleryPicker.this, 744 GallerySettings.class); 745 startActivity(preferences); 746 return true; 747 } 748 }) 749 .setAlphabeticShortcut('p') 750 .setIcon(android.R.drawable.ic_menu_preferences); 751 752 return true; 753 } 754 755 // image lists created by createImageList() are collected in mAllLists. 756 // They will be closed in clearImageList, so they don't hold open files 757 // on SD card. We will be killed if we don't close files when the SD card 758 // is unmounted. 759 ArrayList<IImageList> mAllLists = new ArrayList<IImageList>(); 760 761 private IImageList createImageList(int mediaTypes, String bucketId, 762 ContentResolver cr) { 763 IImageList list = ImageManager.makeImageList( 764 cr, 765 ImageManager.DataLocation.ALL, 766 mediaTypes, 767 ImageManager.SORT_DESCENDING, 768 bucketId); 769 mAllLists.add(list); 770 return list; 771 } 772 773 private void clearImageLists() { 774 for (IImageList list : mAllLists) { 775 list.close(); 776 } 777 mAllLists.clear(); 778 } 779} 780 781// Item is the underlying data for GalleryPickerAdapter. 782// It is passed from the activity to the adapter. 783class Item { 784 public static final int TYPE_NONE = -1; 785 public static final int TYPE_ALL_IMAGES = 0; 786 public static final int TYPE_ALL_VIDEOS = 1; 787 public static final int TYPE_CAMERA_IMAGES = 2; 788 public static final int TYPE_CAMERA_VIDEOS = 3; 789 public static final int TYPE_CAMERA_MEDIAS = 4; 790 public static final int TYPE_NORMAL_FOLDERS = 5; 791 792 public final int mType; 793 public final String mBucketId; 794 public final String mName; 795 public final IImageList mImageList; 796 public final int mCount; 797 public final Uri mFirstImageUri; // could be null if the list is empty 798 799 // The thumbnail bitmap is set by setThumbBitmap() later because we want 800 // to let the user sees the folder icon as soon as possible (and possibly 801 // select them), then present more detailed information when we have it. 802 public Bitmap mThumbBitmap; // the thumbnail bitmap for the image list 803 804 public Item(int type, String bucketId, String name, IImageList list) { 805 mType = type; 806 mBucketId = bucketId; 807 mName = name; 808 mImageList = list; 809 mCount = list.getCount(); 810 if (mCount > 0) { 811 mFirstImageUri = list.getImageAt(0).fullSizeImageUri(); 812 } else { 813 mFirstImageUri = null; 814 } 815 } 816 817 public void setThumbBitmap(Bitmap thumbBitmap) { 818 mThumbBitmap = thumbBitmap; 819 } 820 821 public boolean needsBucketId() { 822 return mType >= TYPE_CAMERA_IMAGES; 823 } 824 825 public void launch(Activity activity) { 826 Uri uri = Images.Media.INTERNAL_CONTENT_URI; 827 if (needsBucketId()) { 828 uri = uri.buildUpon() 829 .appendQueryParameter("bucketId", mBucketId).build(); 830 } 831 Intent intent = new Intent(Intent.ACTION_VIEW, uri); 832 intent.putExtra("windowTitle", mName); 833 intent.putExtra("mediaTypes", getIncludeMediaTypes()); 834 activity.startActivity(intent); 835 } 836 837 public int getIncludeMediaTypes() { 838 return convertItemTypeToIncludedMediaType(mType); 839 } 840 841 public static int convertItemTypeToIncludedMediaType(int itemType) { 842 switch (itemType) { 843 case TYPE_ALL_IMAGES: 844 case TYPE_CAMERA_IMAGES: 845 return ImageManager.INCLUDE_IMAGES; 846 case TYPE_ALL_VIDEOS: 847 case TYPE_CAMERA_VIDEOS: 848 return ImageManager.INCLUDE_VIDEOS; 849 case TYPE_NORMAL_FOLDERS: 850 case TYPE_CAMERA_MEDIAS: 851 default: 852 return ImageManager.INCLUDE_IMAGES 853 | ImageManager.INCLUDE_VIDEOS; 854 } 855 } 856 857 public int getOverlay() { 858 switch (mType) { 859 case TYPE_ALL_IMAGES: 860 case TYPE_CAMERA_IMAGES: 861 return R.drawable.frame_overlay_gallery_camera; 862 case TYPE_ALL_VIDEOS: 863 case TYPE_CAMERA_VIDEOS: 864 case TYPE_CAMERA_MEDIAS: 865 return R.drawable.frame_overlay_gallery_video; 866 case TYPE_NORMAL_FOLDERS: 867 default: 868 return R.drawable.frame_overlay_gallery_folder; 869 } 870 } 871} 872 873class GalleryPickerAdapter extends BaseAdapter { 874 ArrayList<Item> mItems = new ArrayList<Item>(); 875 LayoutInflater mInflater; 876 877 GalleryPickerAdapter(LayoutInflater inflater) { 878 mInflater = inflater; 879 } 880 881 public void addItem(Item item) { 882 mItems.add(item); 883 } 884 885 public void updateDisplay() { 886 notifyDataSetChanged(); 887 } 888 889 public void clear() { 890 mItems.clear(); 891 } 892 893 public int getCount() { 894 return mItems.size(); 895 } 896 897 public Object getItem(int position) { 898 return null; 899 } 900 901 public long getItemId(int position) { 902 return position; 903 } 904 905 public String baseTitleForPosition(int position) { 906 return mItems.get(position).mName; 907 } 908 909 public int getIncludeMediaTypes(int position) { 910 return mItems.get(position).getIncludeMediaTypes(); 911 } 912 913 public View getView(final int position, View convertView, 914 ViewGroup parent) { 915 View v; 916 917 if (convertView == null) { 918 v = mInflater.inflate(R.layout.gallery_picker_item, null); 919 } else { 920 v = convertView; 921 } 922 923 TextView titleView = (TextView) v.findViewById(R.id.title); 924 925 GalleryPickerItem iv = 926 (GalleryPickerItem) v.findViewById(R.id.thumbnail); 927 Item item = mItems.get(position); 928 iv.setOverlay(item.getOverlay()); 929 if (item.mThumbBitmap != null) { 930 iv.setImageBitmap(item.mThumbBitmap); 931 String title = item.mName + " (" + item.mCount + ")"; 932 titleView.setText(title); 933 } else { 934 iv.setImageResource(android.R.color.transparent); 935 titleView.setText(item.mName); 936 } 937 938 // An workaround due to a bug in TextView. If the length of text is 939 // different from the previous in convertView, the layout would be 940 // wrong. 941 titleView.requestLayout(); 942 943 return v; 944 } 945} 946