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