MenuHelper.java revision 7d2d5ec1e9d6c19278e41c103eacd8090be24406
1/* 2 * Copyright (C) 2008 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.content.ActivityNotFoundException; 24import android.content.Context; 25import android.content.DialogInterface; 26import android.content.Intent; 27import android.content.DialogInterface.OnClickListener; 28import android.location.Geocoder; 29import android.media.ExifInterface; 30import android.media.MediaMetadataRetriever; 31import android.net.Uri; 32import android.os.Environment; 33import android.os.Handler; 34import android.os.StatFs; 35import android.preference.PreferenceManager; 36import android.provider.MediaStore; 37import android.provider.MediaStore.Images; 38import android.text.format.Formatter; 39import android.util.Log; 40import android.view.Menu; 41import android.view.MenuItem; 42import android.view.SubMenu; 43import android.view.View; 44import android.widget.ImageView; 45import android.widget.TextView; 46import android.widget.Toast; 47 48import com.android.camera.gallery.IImage; 49 50import java.io.Closeable; 51import java.io.IOException; 52import java.lang.ref.WeakReference; 53import java.text.SimpleDateFormat; 54import java.util.ArrayList; 55import java.util.Date; 56import java.util.List; 57 58/** 59 * A utility class to handle various kinds of menu operations. 60 */ 61public class MenuHelper { 62 private static final String TAG = "MenuHelper"; 63 64 public static final int INCLUDE_ALL = 0xFFFFFFFF; 65 public static final int INCLUDE_VIEWPLAY_MENU = (1 << 0); 66 public static final int INCLUDE_SHARE_MENU = (1 << 1); 67 public static final int INCLUDE_SET_MENU = (1 << 2); 68 public static final int INCLUDE_CROP_MENU = (1 << 3); 69 public static final int INCLUDE_DELETE_MENU = (1 << 4); 70 public static final int INCLUDE_ROTATE_MENU = (1 << 5); 71 public static final int INCLUDE_DETAILS_MENU = (1 << 6); 72 public static final int INCLUDE_SHOWMAP_MENU = (1 << 7); 73 74 public static final int MENU_IMAGE_SHARE = 1; 75 public static final int MENU_IMAGE_SHOWMAP = 2; 76 77 public static final int POSITION_SWITCH_CAMERA_MODE = 1; 78 public static final int POSITION_GOTO_GALLERY = 2; 79 public static final int POSITION_VIEWPLAY = 3; 80 public static final int POSITION_CAPTURE_PICTURE = 4; 81 public static final int POSITION_CAPTURE_VIDEO = 5; 82 public static final int POSITION_IMAGE_SHARE = 6; 83 public static final int POSITION_IMAGE_ROTATE = 7; 84 public static final int POSITION_IMAGE_TOSS = 8; 85 public static final int POSITION_IMAGE_CROP = 9; 86 public static final int POSITION_IMAGE_SET = 10; 87 public static final int POSITION_DETAILS = 11; 88 public static final int POSITION_SHOWMAP = 12; 89 public static final int POSITION_SLIDESHOW = 13; 90 public static final int POSITION_MULTISELECT = 14; 91 public static final int POSITION_CAMERA_SETTING = 15; 92 public static final int POSITION_GALLERY_SETTING = 16; 93 94 public static final int NO_STORAGE_ERROR = -1; 95 public static final int CANNOT_STAT_ERROR = -2; 96 public static final String EMPTY_STRING = ""; 97 public static final String JPEG_MIME_TYPE = "image/jpeg"; 98 // valid range is -180f to +180f 99 public static final float INVALID_LATLNG = 255f; 100 101 /** Activity result code used to report crop results. 102 */ 103 public static final int RESULT_COMMON_MENU_CROP = 490; 104 105 public interface MenuItemsResult { 106 public void gettingReadyToOpen(Menu menu, IImage image); 107 public void aboutToCall(MenuItem item, IImage image); 108 } 109 110 public interface MenuInvoker { 111 public void run(MenuCallback r); 112 } 113 114 public interface MenuCallback { 115 public void run(Uri uri, IImage image); 116 } 117 118 public static void closeSilently(Closeable c) { 119 if (c != null) { 120 try { 121 c.close(); 122 } catch (Throwable e) { 123 // ignore 124 } 125 } 126 } 127 128 public static long getImageFileSize(IImage image) { 129 java.io.InputStream data = image.fullSizeImageData(); 130 if (data == null) return -1; 131 try { 132 return data.available(); 133 } catch (java.io.IOException ex) { 134 return -1; 135 } finally { 136 closeSilently(data); 137 } 138 } 139 140 // This is a hack before we find a solution to pass a permission to other 141 // applications. See bug #1735149, #1836138. 142 // Checks if the URI is on our whitelist: 143 // content://media/... (MediaProvider) 144 // file:///sdcard/... (Browser download) 145 public static boolean isWhiteListUri(Uri uri) { 146 if (uri == null) return false; 147 148 String scheme = uri.getScheme(); 149 String authority = uri.getAuthority(); 150 151 if (scheme.equals("content") && authority.equals("media")) { 152 return true; 153 } 154 155 if (scheme.equals("file")) { 156 List<String> p = uri.getPathSegments(); 157 158 if (p.size() >= 1 && p.get(0).equals("sdcard")) { 159 return true; 160 } 161 } 162 163 return false; 164 } 165 166 public static void enableShareMenuItem(Menu menu, boolean enabled) { 167 MenuItem item = menu.findItem(MENU_IMAGE_SHARE); 168 if (item != null) { 169 item.setVisible(enabled); 170 item.setEnabled(enabled); 171 } 172 } 173 174 public static boolean hasLatLngData(IImage image) { 175 ExifInterface exif = getExif(image); 176 if (exif == null) return false; 177 float latlng[] = new float[2]; 178 return exif.getLatLong(latlng); 179 } 180 181 public static void enableShowOnMapMenuItem(Menu menu, boolean enabled) { 182 MenuItem item = menu.findItem(MENU_IMAGE_SHOWMAP); 183 if (item != null) { 184 item.setEnabled(enabled); 185 } 186 } 187 188 private static void setDetailsValue(View d, String text, int valueId) { 189 ((TextView) d.findViewById(valueId)).setText(text); 190 } 191 192 private static void hideDetailsRow(View d, int rowId) { 193 d.findViewById(rowId).setVisibility(View.GONE); 194 } 195 196 private static class UpdateLocationCallback implements 197 ReverseGeocoderTask.Callback { 198 WeakReference<View> mView; 199 200 public UpdateLocationCallback(WeakReference<View> view) { 201 mView = view; 202 } 203 204 public void onComplete(String location) { 205 // View d is per-thread data, so when setDetailsValue is 206 // executed by UI thread, it doesn't matter whether the 207 // details dialog is dismissed or not. 208 View view = mView.get(); 209 if (view == null) return; 210 if (!location.equals(MenuHelper.EMPTY_STRING)) { 211 MenuHelper.setDetailsValue(view, location, 212 R.id.details_location_value); 213 } else { 214 MenuHelper.hideDetailsRow(view, R.id.details_location_row); 215 } 216 } 217 } 218 219 private static void setLatLngDetails(final View d, Activity context, 220 ExifInterface exif) { 221 float[] latlng = new float[2]; 222 if (exif.getLatLong(latlng)) { 223 setDetailsValue(d, String.valueOf(latlng[0]), 224 R.id.details_latitude_value); 225 setDetailsValue(d, String.valueOf(latlng[1]), 226 R.id.details_longitude_value); 227 228 if (latlng[0] == INVALID_LATLNG || latlng[1] == INVALID_LATLNG) { 229 hideDetailsRow(d, R.id.details_latitude_row); 230 hideDetailsRow(d, R.id.details_longitude_row); 231 hideDetailsRow(d, R.id.details_location_row); 232 return; 233 } 234 235 UpdateLocationCallback cb = new UpdateLocationCallback( 236 new WeakReference<View>(d)); 237 Geocoder geocoder = new Geocoder(context); 238 new ReverseGeocoderTask(geocoder, latlng, cb).execute(); 239 } else { 240 hideDetailsRow(d, R.id.details_latitude_row); 241 hideDetailsRow(d, R.id.details_longitude_row); 242 hideDetailsRow(d, R.id.details_location_row); 243 } 244 } 245 246 private static ExifInterface getExif(IImage image) { 247 if (!JPEG_MIME_TYPE.equals(image.getMimeType())) { 248 return null; 249 } 250 251 try { 252 return new ExifInterface(image.getDataPath()); 253 } catch (IOException ex) { 254 Log.e(TAG, "cannot read exif", ex); 255 return null; 256 } 257 } 258 // Called when "Show on Maps" is clicked. 259 // Displays image location on Google Maps for further operations. 260 private static boolean onShowMapClicked(MenuInvoker onInvoke, 261 final Handler handler, 262 final Activity activity) { 263 onInvoke.run(new MenuCallback() { 264 public void run(Uri u, IImage image) { 265 if (image == null) { 266 return; 267 } 268 269 boolean ok = false; 270 ExifInterface exif = getExif(image); 271 float latlng[] = null; 272 if (exif != null) { 273 latlng = new float[2]; 274 if (exif.getLatLong(latlng)) { 275 ok = true; 276 } 277 } 278 279 if (!ok) { 280 handler.post(new Runnable() { 281 public void run() { 282 Toast.makeText(activity, 283 R.string.no_location_image, 284 Toast.LENGTH_SHORT).show(); 285 } 286 }); 287 return; 288 } 289 290 // Can't use geo:latitude,longitude because it only centers 291 // the MapView to specified location, but we need a bubble 292 // for further operations (routing to/from). 293 // The q=(lat, lng) syntax is suggested by geo-team. 294 String uri = "http://maps.google.com/maps?f=q&" + 295 "q=(" + latlng[0] + "," + latlng[1] + ")"; 296 activity.startActivity(new Intent( 297 android.content.Intent.ACTION_VIEW, 298 Uri.parse(uri))); 299 } 300 }); 301 return true; 302 } 303 304 private static void hideExifInformation(View d) { 305 hideDetailsRow(d, R.id.details_resolution_row); 306 hideDetailsRow(d, R.id.details_make_row); 307 hideDetailsRow(d, R.id.details_model_row); 308 hideDetailsRow(d, R.id.details_whitebalance_row); 309 hideDetailsRow(d, R.id.details_latitude_row); 310 hideDetailsRow(d, R.id.details_longitude_row); 311 hideDetailsRow(d, R.id.details_location_row); 312 } 313 314 private static void showExifInformation(IImage image, View d, 315 Activity activity) { 316 ExifInterface exif = getExif(image); 317 if (exif == null) { 318 hideExifInformation(d); 319 return; 320 } 321 322 String value = exif.getAttribute(ExifInterface.TAG_MAKE); 323 if (value != null) { 324 setDetailsValue(d, value, R.id.details_make_value); 325 } else { 326 hideDetailsRow(d, R.id.details_make_row); 327 } 328 329 value = exif.getAttribute(ExifInterface.TAG_MODEL); 330 if (value != null) { 331 setDetailsValue(d, value, R.id.details_model_value); 332 } else { 333 hideDetailsRow(d, R.id.details_model_row); 334 } 335 336 value = getWhiteBalanceString(exif); 337 if (value != null && !value.equals(EMPTY_STRING)) { 338 setDetailsValue(d, value, R.id.details_whitebalance_value); 339 } else { 340 hideDetailsRow(d, R.id.details_whitebalance_row); 341 } 342 343 setLatLngDetails(d, activity, exif); 344 } 345 346 /** 347 * Returns a human-readable string describing the white balance value. Returns empty 348 * string if there is no white balance value or it is not recognized. 349 */ 350 private static String getWhiteBalanceString(ExifInterface exif) { 351 int whitebalance = exif.getAttributeInt(ExifInterface.TAG_WHITE_BALANCE, -1); 352 if (whitebalance == -1) return ""; 353 354 switch (whitebalance) { 355 case ExifInterface.WHITEBALANCE_AUTO: 356 return "Auto"; 357 case ExifInterface.WHITEBALANCE_MANUAL: 358 return "Manual"; 359 default: 360 return ""; 361 } 362 } 363 364 // Called when "Details" is clicked. 365 // Displays detailed information about the image/video. 366 private static boolean onDetailsClicked(MenuInvoker onInvoke, 367 final Handler handler, 368 final Activity activity) { 369 onInvoke.run(new MenuCallback() { 370 public void run(Uri u, IImage image) { 371 if (image == null) { 372 return; 373 } 374 375 final AlertDialog.Builder builder = 376 new AlertDialog.Builder(activity); 377 378 final View d = View.inflate(activity, R.layout.detailsview, 379 null); 380 381 ImageView imageView = (ImageView) d.findViewById( 382 R.id.details_thumbnail_image); 383 imageView.setImageBitmap(image.miniThumbBitmap()); 384 385 TextView textView = (TextView) d.findViewById( 386 R.id.details_image_title); 387 textView.setText(image.getDisplayName()); 388 389 long length = getImageFileSize(image); 390 String lengthString = length < 0 391 ? EMPTY_STRING 392 : Formatter.formatFileSize(activity, length); 393 ((TextView) d 394 .findViewById(R.id.details_file_size_value)) 395 .setText(lengthString); 396 397 int dimensionWidth = 0; 398 int dimensionHeight = 0; 399 if (ImageManager.isImage(image)) { 400 // getWidth is much slower than reading from EXIF 401 dimensionWidth = image.getWidth(); 402 dimensionHeight = image.getHeight(); 403 d.findViewById(R.id.details_duration_row) 404 .setVisibility(View.GONE); 405 d.findViewById(R.id.details_frame_rate_row) 406 .setVisibility(View.GONE); 407 d.findViewById(R.id.details_bit_rate_row) 408 .setVisibility(View.GONE); 409 d.findViewById(R.id.details_format_row) 410 .setVisibility(View.GONE); 411 d.findViewById(R.id.details_codec_row) 412 .setVisibility(View.GONE); 413 } else { 414 MediaMetadataRetriever retriever 415 = new MediaMetadataRetriever(); 416 try { 417 retriever.setMode(MediaMetadataRetriever 418 .MODE_GET_METADATA_ONLY); 419 retriever.setDataSource(image.getDataPath()); 420 try { 421 dimensionWidth = Integer.parseInt( 422 retriever.extractMetadata( 423 MediaMetadataRetriever 424 .METADATA_KEY_VIDEO_WIDTH)); 425 dimensionHeight = Integer.parseInt( 426 retriever.extractMetadata( 427 MediaMetadataRetriever 428 .METADATA_KEY_VIDEO_HEIGHT)); 429 } catch (NumberFormatException e) { 430 dimensionWidth = 0; 431 dimensionHeight = 0; 432 } 433 434 try { 435 int durationMs = Integer.parseInt( 436 retriever.extractMetadata( 437 MediaMetadataRetriever 438 .METADATA_KEY_DURATION)); 439 String durationValue = formatDuration( 440 activity, durationMs); 441 ((TextView) d.findViewById( 442 R.id.details_duration_value)) 443 .setText(durationValue); 444 } catch (NumberFormatException e) { 445 d.findViewById( 446 R.id.details_frame_rate_row) 447 .setVisibility(View.GONE); 448 } 449 450 try { 451 String frameRate = String.format( 452 activity.getString(R.string.details_fps), 453 Integer.parseInt( 454 retriever.extractMetadata( 455 MediaMetadataRetriever 456 .METADATA_KEY_FRAME_RATE))); 457 ((TextView) d.findViewById( 458 R.id.details_frame_rate_value)) 459 .setText(frameRate); 460 } catch (NumberFormatException e) { 461 d.findViewById( 462 R.id.details_frame_rate_row) 463 .setVisibility(View.GONE); 464 } 465 466 try { 467 long bitRate = Long.parseLong( 468 retriever.extractMetadata( 469 MediaMetadataRetriever 470 .METADATA_KEY_BIT_RATE)); 471 String bps; 472 if (bitRate < 1000000) { 473 bps = String.format( 474 activity.getString( 475 R.string.details_kbps), 476 bitRate / 1000); 477 } else { 478 bps = String.format( 479 activity.getString( 480 R.string.details_mbps), 481 (bitRate) / 1000000.0); 482 } 483 ((TextView) d.findViewById( 484 R.id.details_bit_rate_value)) 485 .setText(bps); 486 } catch (NumberFormatException e) { 487 d.findViewById(R.id.details_bit_rate_row) 488 .setVisibility(View.GONE); 489 } 490 491 String format = retriever.extractMetadata( 492 MediaMetadataRetriever 493 .METADATA_KEY_VIDEO_FORMAT); 494 ((TextView) d.findViewById( 495 R.id.details_format_value)) 496 .setText(format); 497 498 String codec = retriever.extractMetadata( 499 MediaMetadataRetriever.METADATA_KEY_CODEC); 500 if (codec != null) { 501 setDetailsValue(d, codec, R.id.details_codec_value); 502 } else { 503 hideDetailsRow(d, R.id.details_codec_row); 504 } 505 506 } catch (RuntimeException ex) { 507 // Assume this is a corrupt video file. 508 } finally { 509 try { 510 retriever.release(); 511 } catch (RuntimeException ex) { 512 // Ignore failures while cleaning up. 513 } 514 } 515 } 516 517 String value = null; 518 if (dimensionWidth > 0 && dimensionHeight > 0) { 519 value = String.format( 520 activity.getString(R.string.details_dimension_x), 521 dimensionWidth, dimensionHeight); 522 } 523 524 if (value != null) { 525 setDetailsValue(d, value, R.id.details_resolution_value); 526 } else { 527 hideDetailsRow(d, R.id.details_resolution_row); 528 } 529 530 value = EMPTY_STRING; 531 long dateTaken = image.getDateTaken(); 532 if (dateTaken != 0) { 533 Date date = new Date(image.getDateTaken()); 534 SimpleDateFormat dateFormat = new SimpleDateFormat(); 535 value = dateFormat.format(date); 536 } 537 if (value != EMPTY_STRING) { 538 setDetailsValue(d, value, R.id.details_date_taken_value); 539 } else { 540 hideDetailsRow(d, R.id.details_date_taken_row); 541 } 542 543 // Show more EXIF header details for JPEG images. 544 if (JPEG_MIME_TYPE.equals(image.getMimeType())) { 545 showExifInformation(image, d, activity); 546 } else { 547 hideExifInformation(d); 548 } 549 550 builder.setNeutralButton(R.string.details_ok, 551 new DialogInterface.OnClickListener() { 552 public void onClick(DialogInterface dialog, 553 int which) { 554 dialog.dismiss(); 555 } 556 }); 557 558 handler.post( 559 new Runnable() { 560 public void run() { 561 builder.setIcon( 562 android.R.drawable.ic_dialog_info) 563 .setTitle(R.string.details_panel_title) 564 .setView(d) 565 .show(); 566 } 567 }); 568 } 569 }); 570 return true; 571 } 572 573 // Called when "Rotate left" or "Rotate right" is clicked. 574 private static boolean onRotateClicked(MenuInvoker onInvoke, 575 final int degree) { 576 onInvoke.run(new MenuCallback() { 577 public void run(Uri u, IImage image) { 578 if (image == null || image.isReadonly()) { 579 return; 580 } 581 image.rotateImageBy(degree); 582 } 583 }); 584 return true; 585 } 586 587 // Called when "Crop" is clicked. 588 private static boolean onCropClicked(MenuInvoker onInvoke, 589 final Activity activity) { 590 onInvoke.run(new MenuCallback() { 591 public void run(Uri u, IImage image) { 592 if (u == null) { 593 return; 594 } 595 596 Intent cropIntent = new Intent( 597 "com.android.camera.action.CROP"); 598 cropIntent.setData(u); 599 activity.startActivityForResult( 600 cropIntent, RESULT_COMMON_MENU_CROP); 601 } 602 }); 603 return true; 604 } 605 606 // Called when "Set as" is clicked. 607 private static boolean onSetAsClicked(MenuInvoker onInvoke, 608 final Activity activity) { 609 onInvoke.run(new MenuCallback() { 610 public void run(Uri u, IImage image) { 611 if (u == null || image == null) { 612 return; 613 } 614 615 Intent intent = Util.createSetAsIntent(image); 616 activity.startActivity(Intent.createChooser(intent, 617 activity.getText(R.string.setImage))); 618 } 619 }); 620 return true; 621 } 622 623 // Called when "Share" is clicked. 624 private static boolean onImageShareClicked(MenuInvoker onInvoke, 625 final Activity activity) { 626 onInvoke.run(new MenuCallback() { 627 public void run(Uri u, IImage image) { 628 if (image == null) return; 629 630 Intent intent = new Intent(); 631 intent.setAction(Intent.ACTION_SEND); 632 String mimeType = image.getMimeType(); 633 intent.setType(mimeType); 634 intent.putExtra(Intent.EXTRA_STREAM, u); 635 boolean isImage = ImageManager.isImage(image); 636 try { 637 activity.startActivity(Intent.createChooser(intent, 638 activity.getText(isImage 639 ? R.string.sendImage 640 : R.string.sendVideo))); 641 } catch (android.content.ActivityNotFoundException ex) { 642 Toast.makeText(activity, isImage 643 ? R.string.no_way_to_share_image 644 : R.string.no_way_to_share_video, 645 Toast.LENGTH_SHORT).show(); 646 } 647 } 648 }); 649 return true; 650 } 651 652 // Called when "Play" is clicked. 653 private static boolean onViewPlayClicked(MenuInvoker onInvoke, 654 final Activity activity) { 655 onInvoke.run(new MenuCallback() { 656 public void run(Uri uri, IImage image) { 657 if (image != null) { 658 Intent intent = new Intent(Intent.ACTION_VIEW, 659 image.fullSizeImageUri()); 660 activity.startActivity(intent); 661 } 662 }}); 663 return true; 664 } 665 666 // Called when "Delete" is clicked. 667 private static boolean onDeleteClicked(MenuInvoker onInvoke, 668 final Activity activity, final Runnable onDelete) { 669 onInvoke.run(new MenuCallback() { 670 public void run(Uri uri, IImage image) { 671 if (image != null) { 672 deleteImage(activity, onDelete, image); 673 } 674 }}); 675 return true; 676 } 677 678 static MenuItemsResult addImageMenuItems( 679 Menu menu, 680 int inclusions, 681 final Activity activity, 682 final Handler handler, 683 final Runnable onDelete, 684 final MenuInvoker onInvoke) { 685 final ArrayList<MenuItem> requiresWriteAccessItems = 686 new ArrayList<MenuItem>(); 687 final ArrayList<MenuItem> requiresNoDrmAccessItems = 688 new ArrayList<MenuItem>(); 689 final ArrayList<MenuItem> requiresImageItems = 690 new ArrayList<MenuItem>(); 691 final ArrayList<MenuItem> requiresVideoItems = 692 new ArrayList<MenuItem>(); 693 694 if ((inclusions & INCLUDE_ROTATE_MENU) != 0) { 695 SubMenu rotateSubmenu = menu.addSubMenu(Menu.NONE, Menu.NONE, 696 POSITION_IMAGE_ROTATE, R.string.rotate) 697 .setIcon(android.R.drawable.ic_menu_rotate); 698 // Don't show the rotate submenu if the item at hand is read only 699 // since the items within the submenu won't be shown anyway. This 700 // is really a framework bug in that it shouldn't show the submenu 701 // if the submenu has no visible items. 702 MenuItem rotateLeft = rotateSubmenu.add(R.string.rotate_left) 703 .setOnMenuItemClickListener( 704 new MenuItem.OnMenuItemClickListener() { 705 public boolean onMenuItemClick(MenuItem item) { 706 return onRotateClicked(onInvoke, -90); 707 } 708 }).setAlphabeticShortcut('l'); 709 710 MenuItem rotateRight = rotateSubmenu.add(R.string.rotate_right) 711 .setOnMenuItemClickListener( 712 new MenuItem.OnMenuItemClickListener() { 713 public boolean onMenuItemClick(MenuItem item) { 714 return onRotateClicked(onInvoke, 90); 715 } 716 }).setAlphabeticShortcut('r'); 717 718 requiresWriteAccessItems.add(rotateSubmenu.getItem()); 719 requiresWriteAccessItems.add(rotateLeft); 720 requiresWriteAccessItems.add(rotateRight); 721 722 requiresImageItems.add(rotateSubmenu.getItem()); 723 requiresImageItems.add(rotateLeft); 724 requiresImageItems.add(rotateRight); 725 } 726 727 if ((inclusions & INCLUDE_CROP_MENU) != 0) { 728 MenuItem autoCrop = menu.add(Menu.NONE, Menu.NONE, 729 POSITION_IMAGE_CROP, R.string.camera_crop); 730 autoCrop.setIcon(android.R.drawable.ic_menu_crop); 731 autoCrop.setOnMenuItemClickListener( 732 new MenuItem.OnMenuItemClickListener() { 733 public boolean onMenuItemClick(MenuItem item) { 734 return onCropClicked(onInvoke, activity); 735 } 736 }); 737 requiresWriteAccessItems.add(autoCrop); 738 requiresImageItems.add(autoCrop); 739 } 740 741 if ((inclusions & INCLUDE_SET_MENU) != 0) { 742 MenuItem setMenu = menu.add(Menu.NONE, Menu.NONE, 743 POSITION_IMAGE_SET, R.string.camera_set); 744 setMenu.setIcon(android.R.drawable.ic_menu_set_as); 745 setMenu.setOnMenuItemClickListener( 746 new MenuItem.OnMenuItemClickListener() { 747 public boolean onMenuItemClick(MenuItem item) { 748 return onSetAsClicked(onInvoke, activity); 749 } 750 }); 751 requiresImageItems.add(setMenu); 752 } 753 754 if ((inclusions & INCLUDE_SHARE_MENU) != 0) { 755 MenuItem item1 = menu.add(Menu.NONE, MENU_IMAGE_SHARE, 756 POSITION_IMAGE_SHARE, R.string.camera_share) 757 .setOnMenuItemClickListener( 758 new MenuItem.OnMenuItemClickListener() { 759 public boolean onMenuItemClick(MenuItem item) { 760 return onImageShareClicked(onInvoke, activity); 761 } 762 }); 763 item1.setIcon(android.R.drawable.ic_menu_share); 764 MenuItem item = item1; 765 requiresNoDrmAccessItems.add(item); 766 } 767 768 if ((inclusions & INCLUDE_DELETE_MENU) != 0) { 769 MenuItem deleteItem = menu.add(Menu.NONE, Menu.NONE, 770 POSITION_IMAGE_TOSS, R.string.camera_toss); 771 requiresWriteAccessItems.add(deleteItem); 772 deleteItem.setOnMenuItemClickListener( 773 new MenuItem.OnMenuItemClickListener() { 774 public boolean onMenuItemClick(MenuItem item) { 775 return onDeleteClicked(onInvoke, activity, 776 onDelete); 777 } 778 }) 779 .setAlphabeticShortcut('d') 780 .setIcon(android.R.drawable.ic_menu_delete); 781 } 782 783 if ((inclusions & INCLUDE_DETAILS_MENU) != 0) { 784 MenuItem detailsMenu = menu.add(Menu.NONE, Menu.NONE, 785 POSITION_DETAILS, R.string.details) 786 .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { 787 public boolean onMenuItemClick(MenuItem item) { 788 return onDetailsClicked(onInvoke, handler, activity); 789 } 790 }); 791 detailsMenu.setIcon(R.drawable.ic_menu_view_details); 792 } 793 794 if ((inclusions & INCLUDE_SHOWMAP_MENU) != 0) { 795 MenuItem showOnMapItem = menu.add(Menu.NONE, MENU_IMAGE_SHOWMAP, 796 POSITION_SHOWMAP, R.string.show_on_map); 797 showOnMapItem.setOnMenuItemClickListener( 798 new MenuItem.OnMenuItemClickListener() { 799 public boolean onMenuItemClick(MenuItem item) { 800 return onShowMapClicked(onInvoke, 801 handler, activity); 802 } 803 }).setIcon(R.drawable.ic_menu_3d_globe); 804 requiresImageItems.add(showOnMapItem); 805 } 806 807 if ((inclusions & INCLUDE_VIEWPLAY_MENU) != 0) { 808 MenuItem videoPlayItem = menu.add(Menu.NONE, Menu.NONE, 809 POSITION_VIEWPLAY, R.string.video_play) 810 .setOnMenuItemClickListener( 811 new MenuItem.OnMenuItemClickListener() { 812 public boolean onMenuItemClick(MenuItem item) { 813 return onViewPlayClicked(onInvoke, activity); 814 } 815 }); 816 videoPlayItem.setIcon( 817 com.android.internal.R.drawable.ic_menu_play_clip); 818 requiresVideoItems.add(videoPlayItem); 819 } 820 821 return new MenuItemsResult() { 822 public void gettingReadyToOpen(Menu menu, IImage image) { 823 // protect against null here. this isn't strictly speaking 824 // required but if a client app isn't handling sdcard removal 825 // properly it could happen 826 if (image == null) { 827 return; 828 } 829 830 ArrayList<MenuItem> enableList = new ArrayList<MenuItem>(); 831 ArrayList<MenuItem> disableList = new ArrayList<MenuItem>(); 832 ArrayList<MenuItem> list; 833 834 list = image.isReadonly() ? disableList : enableList; 835 list.addAll(requiresWriteAccessItems); 836 837 list = image.isDrm() ? disableList : enableList; 838 list.addAll(requiresNoDrmAccessItems); 839 840 list = ImageManager.isImage(image) ? enableList : disableList; 841 list.addAll(requiresImageItems); 842 843 list = ImageManager.isVideo(image) ? enableList : disableList; 844 list.addAll(requiresVideoItems); 845 846 for (MenuItem item : enableList) { 847 item.setVisible(true); 848 item.setEnabled(true); 849 } 850 851 for (MenuItem item : disableList) { 852 item.setVisible(false); 853 item.setEnabled(false); 854 } 855 } 856 857 // must override abstract method 858 public void aboutToCall(MenuItem menu, IImage image) { 859 } 860 }; 861 } 862 863 static void deletePhoto(Activity activity, Runnable onDelete) { 864 deleteImpl(activity, onDelete, true); 865 } 866 867 static void deleteImage( 868 Activity activity, Runnable onDelete, IImage image) { 869 deleteImpl(activity, onDelete, ImageManager.isImage(image)); 870 } 871 872 static void deleteImpl( 873 Activity activity, Runnable onDelete, boolean isImage) { 874 boolean needConfirm = PreferenceManager 875 .getDefaultSharedPreferences(activity) 876 .getBoolean("pref_gallery_confirm_delete_key", true); 877 if (!needConfirm) { 878 if (onDelete != null) onDelete.run(); 879 } else { 880 String title = activity.getString(R.string.confirm_delete_title); 881 String message = activity.getString(isImage 882 ? R.string.confirm_delete_message 883 : R.string.confirm_delete_video_message); 884 confirmAction(activity, title, message, onDelete); 885 } 886 } 887 888 public static void deleteMultiple(Context context, Runnable action) { 889 boolean needConfirm = PreferenceManager 890 .getDefaultSharedPreferences(context) 891 .getBoolean("pref_gallery_confirm_delete_key", true); 892 if (!needConfirm) { 893 if (action != null) action.run(); 894 } else { 895 String title = context.getString(R.string.confirm_delete_title); 896 String message = context.getString( 897 R.string.confirm_delete_multiple_message); 898 confirmAction(context, title, message, action); 899 } 900 } 901 902 public static void confirmAction(Context context, String title, 903 String message, final Runnable action) { 904 OnClickListener listener = new OnClickListener() { 905 public void onClick(DialogInterface dialog, int which) { 906 switch (which) { 907 case DialogInterface.BUTTON_POSITIVE: 908 if (action != null) action.run(); 909 } 910 } 911 }; 912 new AlertDialog.Builder(context) 913 .setIcon(android.R.drawable.ic_dialog_alert) 914 .setTitle(title) 915 .setMessage(message) 916 .setPositiveButton(android.R.string.ok, listener) 917 .setNegativeButton(android.R.string.cancel, listener) 918 .create() 919 .show(); 920 } 921 922 static void addCapturePictureMenuItems(Menu menu, final Activity activity) { 923 menu.add(Menu.NONE, Menu.NONE, POSITION_CAPTURE_PICTURE, 924 R.string.capture_picture) 925 .setOnMenuItemClickListener( 926 new MenuItem.OnMenuItemClickListener() { 927 public boolean onMenuItemClick(MenuItem item) { 928 return onCapturePictureClicked(activity); 929 } 930 }).setIcon(android.R.drawable.ic_menu_camera); 931 } 932 933 private static boolean onCapturePictureClicked(Activity activity) { 934 Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA); 935 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 936 try { 937 activity.startActivity(intent); 938 } catch (android.content.ActivityNotFoundException e) { 939 // Ignore exception 940 } 941 return true; 942 } 943 944 static void addCaptureVideoMenuItems(Menu menu, final Activity activity) { 945 menu.add(Menu.NONE, Menu.NONE, POSITION_CAPTURE_VIDEO, 946 R.string.capture_video) 947 .setOnMenuItemClickListener( 948 new MenuItem.OnMenuItemClickListener() { 949 public boolean onMenuItemClick(MenuItem item) { 950 return onCaptureVideoClicked(activity); 951 } 952 }).setIcon(R.drawable.ic_menu_camera_video_view); 953 } 954 955 private static boolean onCaptureVideoClicked(Activity activity) { 956 Intent intent = new Intent(MediaStore.INTENT_ACTION_VIDEO_CAMERA); 957 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 958 try { 959 activity.startActivity(intent); 960 } catch (android.content.ActivityNotFoundException e) { 961 // Ignore exception 962 } 963 return true; 964 } 965 966 public static void addCaptureMenuItems(Menu menu, final Activity activity) { 967 addCapturePictureMenuItems(menu, activity); 968 addCaptureVideoMenuItems(menu, activity); 969 } 970 971 public static String formatDuration(final Context context, 972 int durationMs) { 973 int duration = durationMs / 1000; 974 int h = duration / 3600; 975 int m = (duration - h * 3600) / 60; 976 int s = duration - (h * 3600 + m * 60); 977 String durationValue; 978 if (h == 0) { 979 durationValue = String.format( 980 context.getString(R.string.details_ms), m, s); 981 } else { 982 durationValue = String.format( 983 context.getString(R.string.details_hms), h, m, s); 984 } 985 return durationValue; 986 } 987 988 public static void showStorageToast(Activity activity) { 989 showStorageToast(activity, calculatePicturesRemaining()); 990 } 991 992 public static void showStorageToast(Activity activity, int remaining) { 993 String noStorageText = null; 994 995 if (remaining == MenuHelper.NO_STORAGE_ERROR) { 996 String state = Environment.getExternalStorageState(); 997 if (state == Environment.MEDIA_CHECKING) { 998 noStorageText = activity.getString(R.string.preparing_sd); 999 } else { 1000 noStorageText = activity.getString(R.string.no_storage); 1001 } 1002 } else if (remaining < 1) { 1003 noStorageText = activity.getString(R.string.not_enough_space); 1004 } 1005 1006 if (noStorageText != null) { 1007 Toast.makeText(activity, noStorageText, 5000).show(); 1008 } 1009 } 1010 1011 public static int calculatePicturesRemaining() { 1012 try { 1013 if (!ImageManager.hasStorage()) { 1014 return NO_STORAGE_ERROR; 1015 } else { 1016 String storageDirectory = 1017 Environment.getExternalStorageDirectory().toString(); 1018 StatFs stat = new StatFs(storageDirectory); 1019 float remaining = ((float) stat.getAvailableBlocks() 1020 * (float) stat.getBlockSize()) / 400000F; 1021 return (int) remaining; 1022 } 1023 } catch (Exception ex) { 1024 // if we can't stat the filesystem then we don't know how many 1025 // pictures are remaining. it might be zero but just leave it 1026 // blank since we really don't know. 1027 return CANNOT_STAT_ERROR; 1028 } 1029 } 1030} 1031