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 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. startWorker()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 abortWorker()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. workerRun()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. checkScanning()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. checkScanningFinished(boolean scanning)371 private void checkScanningFinished(boolean scanning) { 372 updateScanningDialog(scanning); 373 } 374 375 // This is run in the worker thread. checkImageList(ArrayList<Item> allItems)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. updateItem(Item item)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. checkBucketIds(ArrayList<Item> allItems)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. checkBucketIdsFinished()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. checkThumbBitmap(ArrayList<Item> allItems)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. updateThumbBitmap(Item item, Bitmap b)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. checkLowStorage()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. checkLowStorageFinished()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 { ImageListData(int type, int include, String bucketId, int stringId)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 loadDrawableIfNeeded()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 unloadDrawable()614 private void unloadDrawable() { 615 mFrameGalleryMask = null; 616 mCellOutline = null; 617 mVideoOverlay = null; 618 } 619 placeImage(Bitmap image, Canvas c, Paint paint, int imageWidth, int widthPadding, int imageHeight, int heightPadding, int offsetX, int offsetY, int pos)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. makeMiniThumbBitmap(int width, int height, IImageList images)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