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