ImageGallery.java revision 666ea1b28a76aeba74744148b15099254d918671
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 android.app.Activity; 20import android.app.AlertDialog; 21import android.app.Dialog; 22import android.app.ProgressDialog; 23import android.content.ActivityNotFoundException; 24import android.content.BroadcastReceiver; 25import android.content.Context; 26import android.content.DialogInterface; 27import android.content.Intent; 28import android.content.IntentFilter; 29import android.content.SharedPreferences; 30import android.content.pm.ActivityInfo; 31import android.content.res.Configuration; 32import android.graphics.Bitmap; 33import android.graphics.BitmapFactory; 34import android.graphics.Canvas; 35import android.graphics.Paint; 36import android.graphics.Rect; 37import android.graphics.drawable.Drawable; 38import android.net.Uri; 39import android.os.Bundle; 40import android.os.Handler; 41import android.os.Parcelable; 42import android.preference.PreferenceManager; 43import android.provider.MediaStore; 44import android.util.Log; 45import android.view.ContextMenu; 46import android.view.KeyEvent; 47import android.view.Menu; 48import android.view.MenuItem; 49import android.view.View; 50import android.view.Window; 51import android.view.View.OnClickListener; 52import android.view.animation.Animation; 53import android.view.animation.AnimationUtils; 54import android.widget.Button; 55import android.widget.TextView; 56import android.widget.Toast; 57 58import com.android.camera.gallery.IImage; 59import com.android.camera.gallery.IImageList; 60import com.android.camera.gallery.VideoObject; 61 62import java.util.ArrayList; 63import java.util.HashSet; 64 65public class ImageGallery extends Activity implements 66 GridViewSpecial.Listener, GridViewSpecial.DrawAdapter { 67 private static final String STATE_SCROLL_POSITION = "scroll_position"; 68 private static final String STATE_SELECTED_INDEX = "first_index"; 69 70 private static final String TAG = "ImageGallery"; 71 private static final float INVALID_POSITION = -1f; 72 private ImageManager.ImageListParam mParam; 73 private IImageList mAllImages; 74 private int mInclusion; 75 boolean mSortAscending = false; 76 private View mNoImagesView; 77 public static final int CROP_MSG = 2; 78 79 private Dialog mMediaScanningDialog; 80 private MenuItem mSlideShowItem; 81 private SharedPreferences mPrefs; 82 private long mVideoSizeLimit = Long.MAX_VALUE; 83 private View mFooterOrganizeView; 84 85 private BroadcastReceiver mReceiver = null; 86 87 private final Handler mHandler = new Handler(); 88 private boolean mLayoutComplete; 89 private boolean mPausing = true; 90 private ImageLoader mLoader; 91 private GridViewSpecial mGvs; 92 93 private Uri mCropResultUri; 94 95 // The index of the first picture in GridViewSpecial. 96 private int mSelectedIndex = GridViewSpecial.INDEX_NONE; 97 private float mScrollPosition = INVALID_POSITION; 98 private boolean mConfigurationChanged = false; 99 100 private HashSet<IImage> mMultiSelected = null; 101 102 @Override 103 public void onCreate(Bundle icicle) { 104 super.onCreate(icicle); 105 106 mPrefs = PreferenceManager.getDefaultSharedPreferences(this); 107 108 // Must be called before setContentView(). 109 requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); 110 111 setContentView(R.layout.image_gallery); 112 113 getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, 114 R.layout.custom_gallery_title); 115 116 mNoImagesView = findViewById(R.id.no_images); 117 118 mGvs = (GridViewSpecial) findViewById(R.id.grid); 119 mGvs.setListener(this); 120 121 mFooterOrganizeView = findViewById(R.id.footer_organize); 122 123 // consume all click events on the footer view 124 mFooterOrganizeView.setOnClickListener(Util.getNullOnClickListener()); 125 initializeFooterButtons(); 126 127 if (isPickIntent()) { 128 mVideoSizeLimit = getIntent().getLongExtra( 129 MediaStore.EXTRA_SIZE_LIMIT, Long.MAX_VALUE); 130 } else { 131 mVideoSizeLimit = Long.MAX_VALUE; 132 mGvs.setOnCreateContextMenuListener( 133 new CreateContextMenuListener()); 134 } 135 136 setupInclusion(); 137 138 mLoader = new ImageLoader(getContentResolver(), mHandler); 139 } 140 141 private void initializeFooterButtons() { 142 Button deleteButton = (Button) findViewById(R.id.button_delete); 143 deleteButton.setOnClickListener(new OnClickListener() { 144 public void onClick(View v) { 145 onDeleteMultipleClicked(); 146 } 147 }); 148 149 Button shareButton = (Button) findViewById(R.id.button_share); 150 shareButton.setOnClickListener(new OnClickListener() { 151 public void onClick(View v) { 152 onShareMultipleClicked(); 153 } 154 }); 155 156 Button closeButton = (Button) findViewById(R.id.button_close); 157 closeButton.setOnClickListener(new OnClickListener() { 158 public void onClick(View v) { 159 closeMultiSelectMode(); 160 } 161 }); 162 } 163 164 private MenuItem addSlideShowMenu(Menu menu) { 165 return menu.add(Menu.NONE, Menu.NONE, MenuHelper.POSITION_SLIDESHOW, 166 R.string.slide_show) 167 .setOnMenuItemClickListener( 168 new MenuItem.OnMenuItemClickListener() { 169 public boolean onMenuItemClick(MenuItem item) { 170 return onSlideShowClicked(); 171 } 172 }).setIcon(android.R.drawable.ic_menu_slideshow); 173 } 174 175 public boolean onSlideShowClicked() { 176 if (!canHandleEvent()) { 177 return false; 178 } 179 IImage img = getCurrentImage(); 180 if (img == null) { 181 img = mAllImages.getImageAt(0); 182 if (img == null) { 183 return true; 184 } 185 } 186 Uri targetUri = img.fullSizeImageUri(); 187 Uri thisUri = getIntent().getData(); 188 if (thisUri != null) { 189 String bucket = thisUri.getQueryParameter("bucketId"); 190 if (bucket != null) { 191 targetUri = targetUri.buildUpon() 192 .appendQueryParameter("bucketId", bucket) 193 .build(); 194 } 195 } 196 Intent intent = new Intent(Intent.ACTION_VIEW, targetUri); 197 intent.putExtra("slideshow", true); 198 startActivity(intent); 199 return true; 200 } 201 202 private final Runnable mDeletePhotoRunnable = new Runnable() { 203 public void run() { 204 if (!canHandleEvent()) return; 205 206 IImage currentImage = getCurrentImage(); 207 208 // The selection will be cleared when mGvs.stop() is called, so 209 // we need to call getCurrentImage() before mGvs.stop(). 210 mGvs.stop(); 211 212 if (currentImage != null) { 213 mAllImages.removeImage(currentImage); 214 } 215 mGvs.setImageList(mAllImages); 216 mGvs.start(); 217 218 mNoImagesView.setVisibility(mAllImages.isEmpty() 219 ? View.VISIBLE 220 : View.GONE); 221 } 222 }; 223 224 private Uri getCurrentImageUri() { 225 IImage image = getCurrentImage(); 226 if (image != null) { 227 return image.fullSizeImageUri(); 228 } else { 229 return null; 230 } 231 } 232 233 private IImage getCurrentImage() { 234 int currentSelection = mGvs.getCurrentSelection(); 235 if (currentSelection < 0 236 || currentSelection >= mAllImages.getCount()) { 237 return null; 238 } else { 239 return mAllImages.getImageAt(currentSelection); 240 } 241 } 242 243 @Override 244 public void onConfigurationChanged(Configuration newConfig) { 245 super.onConfigurationChanged(newConfig); 246 mConfigurationChanged = true; 247 } 248 249 boolean canHandleEvent() { 250 // Don't process event in pause state. 251 return (!mPausing) && (mLayoutComplete); 252 } 253 254 @Override 255 public boolean onKeyDown(int keyCode, KeyEvent event) { 256 if (!canHandleEvent()) return false; 257 switch (keyCode) { 258 case KeyEvent.KEYCODE_DEL: 259 IImage image = getCurrentImage(); 260 if (image != null) { 261 MenuHelper.deleteImage( 262 this, mDeletePhotoRunnable, getCurrentImage()); 263 } 264 return true; 265 } 266 return super.onKeyDown(keyCode, event); 267 } 268 269 private boolean isPickIntent() { 270 String action = getIntent().getAction(); 271 return (Intent.ACTION_PICK.equals(action) 272 || Intent.ACTION_GET_CONTENT.equals(action)); 273 } 274 275 private void launchCropperOrFinish(IImage img) { 276 Bundle myExtras = getIntent().getExtras(); 277 278 long size = MenuHelper.getImageFileSize(img); 279 if (size < 0) { 280 // Return if the image file is not available. 281 return; 282 } 283 284 if (size > mVideoSizeLimit) { 285 DialogInterface.OnClickListener buttonListener = 286 new DialogInterface.OnClickListener() { 287 public void onClick(DialogInterface dialog, int which) { 288 dialog.dismiss(); 289 } 290 }; 291 new AlertDialog.Builder(this) 292 .setIcon(android.R.drawable.ic_dialog_info) 293 .setTitle(R.string.file_info_title) 294 .setMessage(R.string.video_exceed_mms_limit) 295 .setNeutralButton(R.string.details_ok, buttonListener) 296 .show(); 297 return; 298 } 299 300 String cropValue = myExtras != null ? myExtras.getString("crop") : null; 301 if (cropValue != null) { 302 Bundle newExtras = new Bundle(); 303 if (cropValue.equals("circle")) { 304 newExtras.putString("circleCrop", "true"); 305 } 306 307 Intent cropIntent = new Intent(); 308 cropIntent.setData(img.fullSizeImageUri()); 309 cropIntent.setClass(this, CropImage.class); 310 cropIntent.putExtras(newExtras); 311 312 /* pass through any extras that were passed in */ 313 cropIntent.putExtras(myExtras); 314 startActivityForResult(cropIntent, CROP_MSG); 315 } else { 316 Intent result = new Intent(null, img.fullSizeImageUri()); 317 if (myExtras != null && myExtras.getBoolean("return-data")) { 318 // The size of a transaction should be below 100K. 319 Bitmap bitmap = img.fullSizeBitmap( 320 IImage.UNCONSTRAINED, 100 * 1024); 321 if (bitmap != null) { 322 result.putExtra("data", bitmap); 323 } 324 } 325 setResult(RESULT_OK, result); 326 finish(); 327 } 328 } 329 330 @Override 331 protected void onActivityResult(int requestCode, int resultCode, 332 Intent data) { 333 switch (requestCode) { 334 case MenuHelper.RESULT_COMMON_MENU_CROP: { 335 if (resultCode == RESULT_OK) { 336 337 // The CropImage activity passes back the Uri of the cropped 338 // image as the Action rather than the Data. 339 // We store this URI so we can move the selection box to it 340 // later. 341 mCropResultUri = Uri.parse(data.getAction()); 342 } 343 break; 344 } 345 case CROP_MSG: { 346 if (resultCode == RESULT_OK) { 347 setResult(resultCode, data); 348 finish(); 349 } 350 break; 351 } 352 } 353 } 354 355 @Override 356 public void onPause() { 357 super.onPause(); 358 mPausing = true; 359 360 mLoader.stop(); 361 362 mGvs.stop(); 363 364 if (mReceiver != null) { 365 unregisterReceiver(mReceiver); 366 mReceiver = null; 367 } 368 369 // Now that we've paused the threads that are using the cursor it is 370 // safe to close it. 371 mAllImages.close(); 372 mAllImages = null; 373 } 374 375 private void rebake(boolean unmounted, boolean scanning) { 376 mGvs.stop(); 377 if (mAllImages != null) { 378 mAllImages.close(); 379 mAllImages = null; 380 } 381 382 if (mMediaScanningDialog != null) { 383 mMediaScanningDialog.cancel(); 384 mMediaScanningDialog = null; 385 } 386 387 if (scanning) { 388 mMediaScanningDialog = ProgressDialog.show( 389 this, 390 null, 391 getResources().getString(R.string.wait), 392 true, 393 true); 394 } 395 396 mParam = allImages(!unmounted && !scanning); 397 mAllImages = ImageManager.makeImageList(getContentResolver(), mParam); 398 399 mGvs.setImageList(mAllImages); 400 mGvs.setDrawAdapter(this); 401 mGvs.setLoader(mLoader); 402 mGvs.start(); 403 mNoImagesView.setVisibility(mAllImages.getCount() > 0 404 ? View.GONE 405 : View.VISIBLE); 406 } 407 408 @Override 409 protected void onSaveInstanceState(Bundle state) { 410 super.onSaveInstanceState(state); 411 state.putFloat(STATE_SCROLL_POSITION, mScrollPosition); 412 state.putInt(STATE_SELECTED_INDEX, mSelectedIndex); 413 } 414 415 @Override 416 protected void onRestoreInstanceState(Bundle state) { 417 super.onRestoreInstanceState(state); 418 mScrollPosition = state.getFloat( 419 STATE_SCROLL_POSITION, INVALID_POSITION); 420 mSelectedIndex = state.getInt(STATE_SELECTED_INDEX, 0); 421 } 422 423 @Override 424 public void onResume() { 425 super.onResume(); 426 427 mGvs.setSizeChoice(Integer.parseInt( 428 mPrefs.getString("pref_gallery_size_key", "1"))); 429 mGvs.requestFocus(); 430 431 String sortOrder = mPrefs.getString("pref_gallery_sort_key", null); 432 if (sortOrder != null) { 433 mSortAscending = sortOrder.equals("ascending"); 434 } 435 436 mPausing = false; 437 438 // install an intent filter to receive SD card related events. 439 IntentFilter intentFilter = 440 new IntentFilter(Intent.ACTION_MEDIA_MOUNTED); 441 intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); 442 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED); 443 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); 444 intentFilter.addAction(Intent.ACTION_MEDIA_EJECT); 445 intentFilter.addDataScheme("file"); 446 447 mReceiver = new BroadcastReceiver() { 448 @Override 449 public void onReceive(Context context, Intent intent) { 450 String action = intent.getAction(); 451 if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) { 452 // SD card available 453 // TODO put up a "please wait" message 454 // TODO also listen for the media scanner finished message 455 } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) { 456 // SD card unavailable 457 rebake(true, false); 458 } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) { 459 rebake(false, true); 460 } else if (action.equals( 461 Intent.ACTION_MEDIA_SCANNER_FINISHED)) { 462 rebake(false, false); 463 } else if (action.equals(Intent.ACTION_MEDIA_EJECT)) { 464 rebake(true, false); 465 } 466 } 467 }; 468 registerReceiver(mReceiver, intentFilter); 469 rebake(false, ImageManager.isMediaScannerScanning( 470 getContentResolver())); 471 } 472 473 @Override 474 public boolean onCreateOptionsMenu(Menu menu) { 475 if (isPickIntent()) { 476 String type = getIntent().resolveType(this); 477 if (type != null) { 478 if (isImageType(type)) { 479 MenuHelper.addCapturePictureMenuItems(menu, this); 480 } else if (isVideoType(type)) { 481 MenuHelper.addCaptureVideoMenuItems(menu, this); 482 } 483 } 484 } else { 485 MenuHelper.addCaptureMenuItems(menu, this); 486 if ((mInclusion & ImageManager.INCLUDE_IMAGES) != 0) { 487 mSlideShowItem = addSlideShowMenu(menu); 488 } 489 490 MenuItem item = menu.add(Menu.NONE, Menu.NONE, 491 MenuHelper.POSITION_GALLERY_SETTING, 492 R.string.camerasettings); 493 item.setOnMenuItemClickListener( 494 new MenuItem.OnMenuItemClickListener() { 495 public boolean onMenuItemClick(MenuItem item) { 496 Intent preferences = new Intent(); 497 preferences.setClass(ImageGallery.this, 498 GallerySettings.class); 499 startActivity(preferences); 500 return true; 501 } 502 }); 503 item.setAlphabeticShortcut('p'); 504 item.setIcon(android.R.drawable.ic_menu_preferences); 505 506 item = menu.add(Menu.NONE, Menu.NONE, 507 MenuHelper.POSITION_MULTISELECT, 508 R.string.multiselect); 509 item.setOnMenuItemClickListener( 510 new MenuItem.OnMenuItemClickListener() { 511 public boolean onMenuItemClick(MenuItem item) { 512 if (isInMultiSelectMode()) { 513 closeMultiSelectMode(); 514 } else { 515 openMultiSelectMode(); 516 } 517 return true; 518 } 519 }); 520 item.setIcon(R.drawable.ic_menu_multiselect_gallery); 521 } 522 return true; 523 } 524 525 @Override 526 public boolean onPrepareOptionsMenu(Menu menu) { 527 if (!canHandleEvent()) return false; 528 if ((mInclusion & ImageManager.INCLUDE_IMAGES) != 0) { 529 boolean videoSelected = isVideoSelected(); 530 // TODO: Only enable slide show if there is at least one image in 531 // the folder. 532 if (mSlideShowItem != null) { 533 mSlideShowItem.setEnabled(!videoSelected); 534 } 535 } 536 537 return true; 538 } 539 540 private boolean isVideoSelected() { 541 IImage image = getCurrentImage(); 542 return (image != null) && ImageManager.isVideo(image); 543 } 544 545 private boolean isImageType(String type) { 546 return type.equals("vnd.android.cursor.dir/image") 547 || type.equals("image/*"); 548 } 549 550 private boolean isVideoType(String type) { 551 return type.equals("vnd.android.cursor.dir/video") 552 || type.equals("video/*"); 553 } 554 555 // According to the intent, setup what we include (image/video) in the 556 // gallery and the title of the gallery. 557 private void setupInclusion() { 558 mInclusion = ImageManager.INCLUDE_IMAGES | ImageManager.INCLUDE_VIDEOS; 559 560 Intent intent = getIntent(); 561 if (intent != null) { 562 String type = intent.resolveType(this); 563 TextView leftText = (TextView) findViewById(R.id.left_text); 564 if (type != null) { 565 if (isImageType(type)) { 566 mInclusion = ImageManager.INCLUDE_IMAGES; 567 if (isPickIntent()) { 568 leftText.setText(R.string.pick_photos_gallery_title); 569 } else { 570 leftText.setText(R.string.photos_gallery_title); 571 } 572 } 573 if (isVideoType(type)) { 574 mInclusion = ImageManager.INCLUDE_VIDEOS; 575 if (isPickIntent()) { 576 leftText.setText(R.string.pick_videos_gallery_title); 577 } else { 578 leftText.setText(R.string.videos_gallery_title); 579 } 580 } 581 } 582 Bundle extras = intent.getExtras(); 583 String title = (extras != null) 584 ? extras.getString("windowTitle") 585 : null; 586 if (title != null && title.length() > 0) { 587 leftText.setText(title); 588 } 589 590 if (extras != null) { 591 mInclusion = (ImageManager.INCLUDE_IMAGES 592 | ImageManager.INCLUDE_VIDEOS) 593 & extras.getInt("mediaTypes", mInclusion); 594 } 595 596 if (extras != null && extras.getBoolean("pick-drm")) { 597 Log.d(TAG, "pick-drm is true"); 598 mInclusion = ImageManager.INCLUDE_DRM_IMAGES; 599 } 600 } 601 } 602 603 // Returns the image list parameter which contains the subset of image/video 604 // we want. 605 private ImageManager.ImageListParam allImages(boolean storageAvailable) { 606 if (!storageAvailable) { 607 return ImageManager.getEmptyImageListParam(); 608 } else { 609 Uri uri = getIntent().getData(); 610 return ImageManager.getImageListParam( 611 ImageManager.DataLocation.EXTERNAL, 612 mInclusion, 613 mSortAscending 614 ? ImageManager.SORT_ASCENDING 615 : ImageManager.SORT_DESCENDING, 616 (uri != null) 617 ? uri.getQueryParameter("bucketId") 618 : null); 619 } 620 } 621 622 private void toggleMultiSelected(IImage image) { 623 int original = mMultiSelected.size(); 624 if (!mMultiSelected.add(image)) { 625 mMultiSelected.remove(image); 626 } 627 mGvs.invalidate(); 628 if (original == 0) showFooter(); 629 if (mMultiSelected.size() == 0) hideFooter(); 630 } 631 632 public void onImageClicked(int index) { 633 if (index < 0 || index >= mAllImages.getCount()) { 634 return; 635 } 636 mSelectedIndex = index; 637 mGvs.setSelectedIndex(index); 638 639 IImage image = mAllImages.getImageAt(index); 640 641 if (isInMultiSelectMode()) { 642 toggleMultiSelected(image); 643 return; 644 } 645 646 if (isPickIntent()) { 647 launchCropperOrFinish(image); 648 } else { 649 Intent intent; 650 if (image instanceof VideoObject) { 651 intent = new Intent( 652 Intent.ACTION_VIEW, image.fullSizeImageUri()); 653 intent.putExtra(MediaStore.EXTRA_SCREEN_ORIENTATION, 654 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 655 } else { 656 intent = new Intent(this, ViewImage.class); 657 intent.putExtra(ViewImage.KEY_IMAGE_LIST, mParam); 658 intent.setData(image.fullSizeImageUri()); 659 } 660 startActivity(intent); 661 } 662 } 663 664 public void onImageTapped(int index) { 665 // In the multiselect mode, once the finger finishes tapping, we hide 666 // the selection box by setting the selected index to none. However, if 667 // we use the dpad center key, we will keep the selected index in order 668 // to show the the selection box. We do this because we have the 669 // multiselect marker on the images to indicate which of them are 670 // selected, so we don't need the selection box, but in the dpad case 671 // we still need the selection box to show as a "cursor". 672 673 if (isInMultiSelectMode()) { 674 mGvs.setSelectedIndex(GridViewSpecial.INDEX_NONE); 675 toggleMultiSelected(mAllImages.getImageAt(index)); 676 } else { 677 onImageClicked(index); 678 } 679 } 680 681 private class CreateContextMenuListener implements 682 View.OnCreateContextMenuListener { 683 public void onCreateContextMenu(ContextMenu menu, View v, 684 ContextMenu.ContextMenuInfo menuInfo) { 685 if (!canHandleEvent()) return; 686 687 IImage image = getCurrentImage(); 688 689 if (image == null) { 690 return; 691 } 692 693 boolean isImage = ImageManager.isImage(image); 694 if (isImage) { 695 menu.add(R.string.view) 696 .setOnMenuItemClickListener( 697 new MenuItem.OnMenuItemClickListener() { 698 public boolean onMenuItemClick(MenuItem item) { 699 if (!canHandleEvent()) return false; 700 onImageClicked(mGvs.getCurrentSelection()); 701 return true; 702 } 703 }); 704 } 705 706 menu.setHeaderTitle(isImage 707 ? R.string.context_menu_header 708 : R.string.video_context_menu_header); 709 if ((mInclusion & (ImageManager.INCLUDE_IMAGES 710 | ImageManager.INCLUDE_VIDEOS)) != 0) { 711 MenuHelper.MenuItemsResult r = MenuHelper.addImageMenuItems( 712 menu, 713 MenuHelper.INCLUDE_ALL, 714 ImageGallery.this, 715 mHandler, 716 mDeletePhotoRunnable, 717 new MenuHelper.MenuInvoker() { 718 public void run(MenuHelper.MenuCallback cb) { 719 if (!canHandleEvent()) { 720 return; 721 } 722 cb.run(getCurrentImageUri(), getCurrentImage()); 723 mGvs.invalidateImage(mGvs.getCurrentSelection()); 724 } 725 }); 726 727 if (r != null) { 728 r.gettingReadyToOpen(menu, image); 729 } 730 731 if (isImage) { 732 MenuHelper.enableShowOnMapMenuItem( 733 menu, MenuHelper.hasLatLngData(image)); 734 addSlideShowMenu(menu); 735 } 736 } 737 } 738 } 739 740 public void onLayoutComplete(boolean changed) { 741 mLayoutComplete = true; 742 if (mCropResultUri != null) { 743 IImage image = mAllImages.getImageForUri(mCropResultUri); 744 mCropResultUri = null; 745 if (image != null) { 746 mSelectedIndex = mAllImages.getImageIndex(image); 747 } 748 } 749 mGvs.setSelectedIndex(mSelectedIndex); 750 if (mScrollPosition == INVALID_POSITION) { 751 if (mSortAscending) { 752 mGvs.scrollTo(0, mGvs.getHeight()); 753 } else { 754 mGvs.scrollToImage(0); 755 } 756 } else if (mConfigurationChanged) { 757 mConfigurationChanged = false; 758 mGvs.scrollTo(mScrollPosition); 759 if (mGvs.getCurrentSelection() != GridViewSpecial.INDEX_NONE) { 760 mGvs.scrollToVisible(mSelectedIndex); 761 } 762 } else { 763 mGvs.scrollTo(mScrollPosition); 764 } 765 } 766 767 public void onScroll(float scrollPosition) { 768 mScrollPosition = scrollPosition; 769 } 770 771 private Drawable mVideoOverlay; 772 private Drawable mVideoMmsErrorOverlay; 773 private Drawable mMultiSelectTrue; 774 private Drawable mMultiSelectFalse; 775 776 // mSrcRect and mDstRect are only used in drawImage, but we put them as 777 // instance variables to reduce the memory allocation overhead because 778 // drawImage() is called a lot. 779 private final Rect mSrcRect = new Rect(); 780 private final Rect mDstRect = new Rect(); 781 782 private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); 783 784 public void drawImage(Canvas canvas, IImage image, 785 Bitmap b, int xPos, int yPos, int w, int h) { 786 if (b != null) { 787 // if the image is close to the target size then crop, 788 // otherwise scale both the bitmap and the view should be 789 // square but I suppose that could change in the future. 790 791 int bw = b.getWidth(); 792 int bh = b.getHeight(); 793 794 int deltaW = bw - w; 795 int deltaH = bh - h; 796 797 if (deltaW >= 0 && deltaW < 10 && 798 deltaH >= 0 && deltaH < 10) { 799 int halfDeltaW = deltaW / 2; 800 int halfDeltaH = deltaH / 2; 801 mSrcRect.set(0 + halfDeltaW, 0 + halfDeltaH, 802 bw - halfDeltaW, bh - halfDeltaH); 803 mDstRect.set(xPos, yPos, xPos + w, yPos + h); 804 canvas.drawBitmap(b, mSrcRect, mDstRect, null); 805 } else { 806 mSrcRect.set(0, 0, bw, bh); 807 mDstRect.set(xPos, yPos, xPos + w, yPos + h); 808 canvas.drawBitmap(b, mSrcRect, mDstRect, mPaint); 809 } 810 } else { 811 // If the thumbnail cannot be drawn, put up an error icon 812 // instead 813 Bitmap error = getErrorBitmap(image); 814 int width = error.getWidth(); 815 int height = error.getHeight(); 816 mSrcRect.set(0, 0, width, height); 817 int left = (w - width) / 2 + xPos; 818 int top = (w - height) / 2 + yPos; 819 mDstRect.set(left, top, left + width, top + height); 820 canvas.drawBitmap(error, mSrcRect, mDstRect, null); 821 } 822 823 if (ImageManager.isVideo(image)) { 824 Drawable overlay = null; 825 long size = MenuHelper.getImageFileSize(image); 826 if (size >= 0 && size <= mVideoSizeLimit) { 827 if (mVideoOverlay == null) { 828 mVideoOverlay = getResources().getDrawable( 829 R.drawable.ic_gallery_video_overlay); 830 } 831 overlay = mVideoOverlay; 832 } else { 833 if (mVideoMmsErrorOverlay == null) { 834 mVideoMmsErrorOverlay = getResources().getDrawable( 835 R.drawable.ic_error_mms_video_overlay); 836 } 837 overlay = mVideoMmsErrorOverlay; 838 Paint paint = new Paint(); 839 paint.setARGB(0x80, 0x00, 0x00, 0x00); 840 canvas.drawRect(xPos, yPos, xPos + w, yPos + h, paint); 841 } 842 int width = overlay.getIntrinsicWidth(); 843 int height = overlay.getIntrinsicHeight(); 844 int left = (w - width) / 2 + xPos; 845 int top = (h - height) / 2 + yPos; 846 mSrcRect.set(left, top, left + width, top + height); 847 overlay.setBounds(mSrcRect); 848 overlay.draw(canvas); 849 } 850 } 851 852 public boolean needsDecoration() { 853 return (mMultiSelected != null); 854 } 855 856 public void drawDecoration(Canvas canvas, IImage image, 857 int xPos, int yPos, int w, int h) { 858 if (mMultiSelected != null) { 859 initializeMultiSelectDrawables(); 860 861 Drawable checkBox = mMultiSelected.contains(image) 862 ? mMultiSelectTrue 863 : mMultiSelectFalse; 864 int width = checkBox.getIntrinsicWidth(); 865 int height = checkBox.getIntrinsicHeight(); 866 int left = 5 + xPos; 867 int top = h - height - 5 + yPos; 868 mSrcRect.set(left, top, left + width, top + height); 869 checkBox.setBounds(mSrcRect); 870 checkBox.draw(canvas); 871 } 872 } 873 874 private void initializeMultiSelectDrawables() { 875 if (mMultiSelectTrue == null) { 876 mMultiSelectTrue = getResources() 877 .getDrawable(R.drawable.btn_check_buttonless_on); 878 } 879 if (mMultiSelectFalse == null) { 880 mMultiSelectFalse = getResources() 881 .getDrawable(R.drawable.btn_check_buttonless_off); 882 } 883 } 884 885 private Bitmap mMissingImageThumbnailBitmap; 886 private Bitmap mMissingVideoThumbnailBitmap; 887 888 // Create this bitmap lazily, and only once for all the ImageBlocks to 889 // use 890 public Bitmap getErrorBitmap(IImage image) { 891 if (ImageManager.isImage(image)) { 892 if (mMissingImageThumbnailBitmap == null) { 893 mMissingImageThumbnailBitmap = BitmapFactory.decodeResource( 894 getResources(), 895 R.drawable.ic_missing_thumbnail_picture); 896 } 897 return mMissingImageThumbnailBitmap; 898 } else { 899 if (mMissingVideoThumbnailBitmap == null) { 900 mMissingVideoThumbnailBitmap = BitmapFactory.decodeResource( 901 getResources(), R.drawable.ic_missing_thumbnail_video); 902 } 903 return mMissingVideoThumbnailBitmap; 904 } 905 } 906 907 private Animation mFooterAppear; 908 private Animation mFooterDisappear; 909 910 private void showFooter() { 911 mFooterOrganizeView.setVisibility(View.VISIBLE); 912 if (mFooterAppear == null) { 913 mFooterAppear = AnimationUtils.loadAnimation( 914 this, R.anim.footer_appear); 915 } 916 mFooterOrganizeView.startAnimation(mFooterAppear); 917 } 918 919 private void hideFooter() { 920 if (mFooterOrganizeView.getVisibility() != View.GONE) { 921 mFooterOrganizeView.setVisibility(View.GONE); 922 if (mFooterDisappear == null) { 923 mFooterDisappear = AnimationUtils.loadAnimation( 924 this, R.anim.footer_disappear); 925 } 926 mFooterOrganizeView.startAnimation(mFooterDisappear); 927 } 928 } 929 930 private String getShareMultipleMimeType() { 931 final int FLAG_IMAGE = 1, FLAG_VIDEO = 2; 932 int flag = 0; 933 for (IImage image : mMultiSelected) { 934 flag |= ImageManager.isImage(image) ? FLAG_IMAGE : FLAG_VIDEO; 935 } 936 return flag == FLAG_IMAGE 937 ? "image/*" 938 : flag == FLAG_VIDEO ? "video/*" : "*/*"; 939 } 940 941 private void onShareMultipleClicked() { 942 if (mMultiSelected.size() > 1) { 943 Intent intent = new Intent(); 944 intent.setAction(Intent.ACTION_SEND_MULTIPLE); 945 946 String mimeType = getShareMultipleMimeType(); 947 intent.setType(mimeType); 948 ArrayList<Parcelable> list = new ArrayList<Parcelable>(); 949 for (IImage image : mMultiSelected) { 950 list.add(image.fullSizeImageUri()); 951 } 952 intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, list); 953 try { 954 startActivity(Intent.createChooser( 955 intent, getText(R.string.send_media_files))); 956 } catch (android.content.ActivityNotFoundException ex) { 957 Toast.makeText(this, R.string.no_way_to_share, 958 Toast.LENGTH_SHORT).show(); 959 } 960 } else if (mMultiSelected.size() == 1) { 961 IImage image = mMultiSelected.iterator().next(); 962 Intent intent = new Intent(); 963 intent.setAction(Intent.ACTION_SEND); 964 String mimeType = image.getMimeType(); 965 intent.setType(mimeType); 966 intent.putExtra(Intent.EXTRA_STREAM, image.fullSizeImageUri()); 967 boolean isImage = ImageManager.isImage(image); 968 try { 969 startActivity(Intent.createChooser(intent, getText( 970 isImage ? R.string.sendImage : R.string.sendVideo))); 971 } catch (android.content.ActivityNotFoundException ex) { 972 Toast.makeText(this, isImage 973 ? R.string.no_way_to_share_image 974 : R.string.no_way_to_share_video, 975 Toast.LENGTH_SHORT).show(); 976 } 977 } 978 } 979 980 private void onDeleteMultipleClicked() { 981 Runnable action = new Runnable() { 982 public void run() { 983 ArrayList<Uri> uriList = new ArrayList<Uri>(); 984 for (IImage image : mMultiSelected) { 985 uriList.add(image.fullSizeImageUri()); 986 } 987 closeMultiSelectMode(); 988 Intent intent = new Intent(ImageGallery.this, 989 DeleteImage.class); 990 intent.putExtra("delete-uris", uriList); 991 try { 992 startActivity(intent); 993 } catch (ActivityNotFoundException ex) { 994 Log.e(TAG, "Delete images fail", ex); 995 } 996 } 997 }; 998 MenuHelper.deleteMultiple(this, action); 999 } 1000 1001 private boolean isInMultiSelectMode() { 1002 return mMultiSelected != null; 1003 } 1004 1005 private void closeMultiSelectMode() { 1006 if (mMultiSelected == null) return; 1007 mMultiSelected = null; 1008 mGvs.invalidate(); 1009 hideFooter(); 1010 } 1011 1012 private void openMultiSelectMode() { 1013 if (mMultiSelected != null) return; 1014 mMultiSelected = new HashSet<IImage>(); 1015 mGvs.invalidate(); 1016 } 1017 1018} 1019