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 } 599 600 // Returns the image list parameter which contains the subset of image/video 601 // we want. 602 private ImageManager.ImageListParam allImages(boolean storageAvailable) { 603 if (!storageAvailable) { 604 return ImageManager.getEmptyImageListParam(); 605 } else { 606 Uri uri = getIntent().getData(); 607 return ImageManager.getImageListParam( 608 ImageManager.DataLocation.EXTERNAL, 609 mInclusion, 610 mSortAscending 611 ? ImageManager.SORT_ASCENDING 612 : ImageManager.SORT_DESCENDING, 613 (uri != null) 614 ? uri.getQueryParameter("bucketId") 615 : null); 616 } 617 } 618 619 private void toggleMultiSelected(IImage image) { 620 int original = mMultiSelected.size(); 621 if (!mMultiSelected.add(image)) { 622 mMultiSelected.remove(image); 623 } 624 mGvs.invalidate(); 625 if (original == 0) showFooter(); 626 if (mMultiSelected.size() == 0) hideFooter(); 627 } 628 629 public void onImageClicked(int index) { 630 if (index < 0 || index >= mAllImages.getCount()) { 631 return; 632 } 633 mSelectedIndex = index; 634 mGvs.setSelectedIndex(index); 635 636 IImage image = mAllImages.getImageAt(index); 637 638 if (isInMultiSelectMode()) { 639 toggleMultiSelected(image); 640 return; 641 } 642 643 if (isPickIntent()) { 644 launchCropperOrFinish(image); 645 } else { 646 Intent intent; 647 if (image instanceof VideoObject) { 648 intent = new Intent( 649 Intent.ACTION_VIEW, image.fullSizeImageUri()); 650 intent.putExtra(MediaStore.EXTRA_SCREEN_ORIENTATION, 651 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 652 } else { 653 intent = new Intent(this, ViewImage.class); 654 intent.putExtra(ViewImage.KEY_IMAGE_LIST, mParam); 655 intent.setData(image.fullSizeImageUri()); 656 } 657 startActivity(intent); 658 } 659 } 660 661 public void onImageTapped(int index) { 662 // In the multiselect mode, once the finger finishes tapping, we hide 663 // the selection box by setting the selected index to none. However, if 664 // we use the dpad center key, we will keep the selected index in order 665 // to show the the selection box. We do this because we have the 666 // multiselect marker on the images to indicate which of them are 667 // selected, so we don't need the selection box, but in the dpad case 668 // we still need the selection box to show as a "cursor". 669 670 if (isInMultiSelectMode()) { 671 mGvs.setSelectedIndex(GridViewSpecial.INDEX_NONE); 672 toggleMultiSelected(mAllImages.getImageAt(index)); 673 } else { 674 onImageClicked(index); 675 } 676 } 677 678 private class CreateContextMenuListener implements 679 View.OnCreateContextMenuListener { 680 public void onCreateContextMenu(ContextMenu menu, View v, 681 ContextMenu.ContextMenuInfo menuInfo) { 682 if (!canHandleEvent()) return; 683 684 IImage image = getCurrentImage(); 685 686 if (image == null) { 687 return; 688 } 689 690 boolean isImage = ImageManager.isImage(image); 691 if (isImage) { 692 menu.add(R.string.view) 693 .setOnMenuItemClickListener( 694 new MenuItem.OnMenuItemClickListener() { 695 public boolean onMenuItemClick(MenuItem item) { 696 if (!canHandleEvent()) return false; 697 onImageClicked(mGvs.getCurrentSelection()); 698 return true; 699 } 700 }); 701 } 702 703 menu.setHeaderTitle(isImage 704 ? R.string.context_menu_header 705 : R.string.video_context_menu_header); 706 if ((mInclusion & (ImageManager.INCLUDE_IMAGES 707 | ImageManager.INCLUDE_VIDEOS)) != 0) { 708 MenuHelper.MenuItemsResult r = MenuHelper.addImageMenuItems( 709 menu, 710 MenuHelper.INCLUDE_ALL, 711 ImageGallery.this, 712 mHandler, 713 mDeletePhotoRunnable, 714 new MenuHelper.MenuInvoker() { 715 public void run(MenuHelper.MenuCallback cb) { 716 if (!canHandleEvent()) { 717 return; 718 } 719 cb.run(getCurrentImageUri(), getCurrentImage()); 720 mGvs.invalidateImage(mGvs.getCurrentSelection()); 721 } 722 }); 723 724 if (r != null) { 725 r.gettingReadyToOpen(menu, image); 726 } 727 728 if (isImage) { 729 MenuHelper.enableShowOnMapMenuItem( 730 menu, MenuHelper.hasLatLngData(image)); 731 addSlideShowMenu(menu); 732 } 733 } 734 } 735 } 736 737 public void onLayoutComplete(boolean changed) { 738 mLayoutComplete = true; 739 if (mCropResultUri != null) { 740 IImage image = mAllImages.getImageForUri(mCropResultUri); 741 mCropResultUri = null; 742 if (image != null) { 743 mSelectedIndex = mAllImages.getImageIndex(image); 744 } 745 } 746 mGvs.setSelectedIndex(mSelectedIndex); 747 if (mScrollPosition == INVALID_POSITION) { 748 if (mSortAscending) { 749 mGvs.scrollTo(0, mGvs.getHeight()); 750 } else { 751 mGvs.scrollToImage(0); 752 } 753 } else if (mConfigurationChanged) { 754 mConfigurationChanged = false; 755 mGvs.scrollTo(mScrollPosition); 756 if (mGvs.getCurrentSelection() != GridViewSpecial.INDEX_NONE) { 757 mGvs.scrollToVisible(mSelectedIndex); 758 } 759 } else { 760 mGvs.scrollTo(mScrollPosition); 761 } 762 } 763 764 public void onScroll(float scrollPosition) { 765 mScrollPosition = scrollPosition; 766 } 767 768 private Drawable mVideoOverlay; 769 private Drawable mVideoMmsErrorOverlay; 770 private Drawable mMultiSelectTrue; 771 private Drawable mMultiSelectFalse; 772 773 // mSrcRect and mDstRect are only used in drawImage, but we put them as 774 // instance variables to reduce the memory allocation overhead because 775 // drawImage() is called a lot. 776 private final Rect mSrcRect = new Rect(); 777 private final Rect mDstRect = new Rect(); 778 779 private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); 780 781 public void drawImage(Canvas canvas, IImage image, 782 Bitmap b, int xPos, int yPos, int w, int h) { 783 if (b != null) { 784 // if the image is close to the target size then crop, 785 // otherwise scale both the bitmap and the view should be 786 // square but I suppose that could change in the future. 787 788 int bw = b.getWidth(); 789 int bh = b.getHeight(); 790 791 int deltaW = bw - w; 792 int deltaH = bh - h; 793 794 if (deltaW >= 0 && deltaW < 10 && 795 deltaH >= 0 && deltaH < 10) { 796 int halfDeltaW = deltaW / 2; 797 int halfDeltaH = deltaH / 2; 798 mSrcRect.set(0 + halfDeltaW, 0 + halfDeltaH, 799 bw - halfDeltaW, bh - halfDeltaH); 800 mDstRect.set(xPos, yPos, xPos + w, yPos + h); 801 canvas.drawBitmap(b, mSrcRect, mDstRect, null); 802 } else { 803 mSrcRect.set(0, 0, bw, bh); 804 mDstRect.set(xPos, yPos, xPos + w, yPos + h); 805 canvas.drawBitmap(b, mSrcRect, mDstRect, mPaint); 806 } 807 } else { 808 // If the thumbnail cannot be drawn, put up an error icon 809 // instead 810 Bitmap error = getErrorBitmap(image); 811 int width = error.getWidth(); 812 int height = error.getHeight(); 813 mSrcRect.set(0, 0, width, height); 814 int left = (w - width) / 2 + xPos; 815 int top = (w - height) / 2 + yPos; 816 mDstRect.set(left, top, left + width, top + height); 817 canvas.drawBitmap(error, mSrcRect, mDstRect, null); 818 } 819 820 if (ImageManager.isVideo(image)) { 821 Drawable overlay = null; 822 long size = MenuHelper.getImageFileSize(image); 823 if (size >= 0 && size <= mVideoSizeLimit) { 824 if (mVideoOverlay == null) { 825 mVideoOverlay = getResources().getDrawable( 826 R.drawable.ic_gallery_video_overlay); 827 } 828 overlay = mVideoOverlay; 829 } else { 830 if (mVideoMmsErrorOverlay == null) { 831 mVideoMmsErrorOverlay = getResources().getDrawable( 832 R.drawable.ic_error_mms_video_overlay); 833 } 834 overlay = mVideoMmsErrorOverlay; 835 Paint paint = new Paint(); 836 paint.setARGB(0x80, 0x00, 0x00, 0x00); 837 canvas.drawRect(xPos, yPos, xPos + w, yPos + h, paint); 838 } 839 int width = overlay.getIntrinsicWidth(); 840 int height = overlay.getIntrinsicHeight(); 841 int left = (w - width) / 2 + xPos; 842 int top = (h - height) / 2 + yPos; 843 mSrcRect.set(left, top, left + width, top + height); 844 overlay.setBounds(mSrcRect); 845 overlay.draw(canvas); 846 } 847 } 848 849 public boolean needsDecoration() { 850 return (mMultiSelected != null); 851 } 852 853 public void drawDecoration(Canvas canvas, IImage image, 854 int xPos, int yPos, int w, int h) { 855 if (mMultiSelected != null) { 856 initializeMultiSelectDrawables(); 857 858 Drawable checkBox = mMultiSelected.contains(image) 859 ? mMultiSelectTrue 860 : mMultiSelectFalse; 861 int width = checkBox.getIntrinsicWidth(); 862 int height = checkBox.getIntrinsicHeight(); 863 int left = 5 + xPos; 864 int top = h - height - 5 + yPos; 865 mSrcRect.set(left, top, left + width, top + height); 866 checkBox.setBounds(mSrcRect); 867 checkBox.draw(canvas); 868 } 869 } 870 871 private void initializeMultiSelectDrawables() { 872 if (mMultiSelectTrue == null) { 873 mMultiSelectTrue = getResources() 874 .getDrawable(R.drawable.btn_check_buttonless_on); 875 } 876 if (mMultiSelectFalse == null) { 877 mMultiSelectFalse = getResources() 878 .getDrawable(R.drawable.btn_check_buttonless_off); 879 } 880 } 881 882 private Bitmap mMissingImageThumbnailBitmap; 883 private Bitmap mMissingVideoThumbnailBitmap; 884 885 // Create this bitmap lazily, and only once for all the ImageBlocks to 886 // use 887 public Bitmap getErrorBitmap(IImage image) { 888 if (ImageManager.isImage(image)) { 889 if (mMissingImageThumbnailBitmap == null) { 890 mMissingImageThumbnailBitmap = BitmapFactory.decodeResource( 891 getResources(), 892 R.drawable.ic_missing_thumbnail_picture); 893 } 894 return mMissingImageThumbnailBitmap; 895 } else { 896 if (mMissingVideoThumbnailBitmap == null) { 897 mMissingVideoThumbnailBitmap = BitmapFactory.decodeResource( 898 getResources(), R.drawable.ic_missing_thumbnail_video); 899 } 900 return mMissingVideoThumbnailBitmap; 901 } 902 } 903 904 private Animation mFooterAppear; 905 private Animation mFooterDisappear; 906 907 private void showFooter() { 908 mFooterOrganizeView.setVisibility(View.VISIBLE); 909 if (mFooterAppear == null) { 910 mFooterAppear = AnimationUtils.loadAnimation( 911 this, R.anim.footer_appear); 912 } 913 mFooterOrganizeView.startAnimation(mFooterAppear); 914 } 915 916 private void hideFooter() { 917 if (mFooterOrganizeView.getVisibility() != View.GONE) { 918 mFooterOrganizeView.setVisibility(View.GONE); 919 if (mFooterDisappear == null) { 920 mFooterDisappear = AnimationUtils.loadAnimation( 921 this, R.anim.footer_disappear); 922 } 923 mFooterOrganizeView.startAnimation(mFooterDisappear); 924 } 925 } 926 927 private String getShareMultipleMimeType() { 928 final int FLAG_IMAGE = 1, FLAG_VIDEO = 2; 929 int flag = 0; 930 for (IImage image : mMultiSelected) { 931 flag |= ImageManager.isImage(image) ? FLAG_IMAGE : FLAG_VIDEO; 932 } 933 return flag == FLAG_IMAGE 934 ? "image/*" 935 : flag == FLAG_VIDEO ? "video/*" : "*/*"; 936 } 937 938 private void onShareMultipleClicked() { 939 if (mMultiSelected == null) return; 940 if (mMultiSelected.size() > 1) { 941 Intent intent = new Intent(); 942 intent.setAction(Intent.ACTION_SEND_MULTIPLE); 943 944 String mimeType = getShareMultipleMimeType(); 945 intent.setType(mimeType); 946 ArrayList<Parcelable> list = new ArrayList<Parcelable>(); 947 for (IImage image : mMultiSelected) { 948 list.add(image.fullSizeImageUri()); 949 } 950 intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, list); 951 try { 952 startActivity(Intent.createChooser( 953 intent, getText(R.string.send_media_files))); 954 } catch (android.content.ActivityNotFoundException ex) { 955 Toast.makeText(this, R.string.no_way_to_share, 956 Toast.LENGTH_SHORT).show(); 957 } 958 } else if (mMultiSelected.size() == 1) { 959 IImage image = mMultiSelected.iterator().next(); 960 Intent intent = new Intent(); 961 intent.setAction(Intent.ACTION_SEND); 962 String mimeType = image.getMimeType(); 963 intent.setType(mimeType); 964 intent.putExtra(Intent.EXTRA_STREAM, image.fullSizeImageUri()); 965 boolean isImage = ImageManager.isImage(image); 966 try { 967 startActivity(Intent.createChooser(intent, getText( 968 isImage ? R.string.sendImage : R.string.sendVideo))); 969 } catch (android.content.ActivityNotFoundException ex) { 970 Toast.makeText(this, isImage 971 ? R.string.no_way_to_share_image 972 : R.string.no_way_to_share_video, 973 Toast.LENGTH_SHORT).show(); 974 } 975 } 976 } 977 978 private void onDeleteMultipleClicked() { 979 if (mMultiSelected == null) return; 980 Runnable action = new Runnable() { 981 public void run() { 982 ArrayList<Uri> uriList = new ArrayList<Uri>(); 983 for (IImage image : mMultiSelected) { 984 uriList.add(image.fullSizeImageUri()); 985 } 986 closeMultiSelectMode(); 987 Intent intent = new Intent(ImageGallery.this, 988 DeleteImage.class); 989 intent.putExtra("delete-uris", uriList); 990 try { 991 startActivity(intent); 992 } catch (ActivityNotFoundException ex) { 993 Log.e(TAG, "Delete images fail", ex); 994 } 995 } 996 }; 997 MenuHelper.deleteMultiple(this, action); 998 } 999 1000 private boolean isInMultiSelectMode() { 1001 return mMultiSelected != null; 1002 } 1003 1004 private void closeMultiSelectMode() { 1005 if (mMultiSelected == null) return; 1006 mMultiSelected = null; 1007 mGvs.invalidate(); 1008 hideFooter(); 1009 } 1010 1011 private void openMultiSelectMode() { 1012 if (mMultiSelected != null) return; 1013 mMultiSelected = new HashSet<IImage>(); 1014 mGvs.invalidate(); 1015 } 1016 1017} 1018