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 17 package com.android.camera; 18 19 import com.android.gallery.R; 20 21 import android.app.Activity; 22 import android.app.AlertDialog; 23 import android.content.ActivityNotFoundException; 24 import android.content.Context; 25 import android.content.DialogInterface; 26 import android.content.Intent; 27 import android.content.DialogInterface.OnClickListener; 28 import android.location.Geocoder; 29 import android.media.ExifInterface; 30 import android.media.MediaMetadataRetriever; 31 import android.net.Uri; 32 import android.os.Environment; 33 import android.os.Handler; 34 import android.os.StatFs; 35 import android.preference.PreferenceManager; 36 import android.provider.MediaStore; 37 import android.provider.MediaStore.Images; 38 import android.text.format.Formatter; 39 import android.util.Log; 40 import android.view.Menu; 41 import android.view.MenuItem; 42 import android.view.SubMenu; 43 import android.view.View; 44 import android.widget.ImageView; 45 import android.widget.TextView; 46 import android.widget.Toast; 47 48 import com.android.camera.gallery.IImage; 49 50 import java.io.Closeable; 51 import java.io.IOException; 52 import java.lang.ref.WeakReference; 53 import java.text.SimpleDateFormat; 54 import java.util.ArrayList; 55 import java.util.Date; 56 import java.util.List; 57 58 /** 59 * A utility class to handle various kinds of menu operations. 60 */ 61 public 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.getTitle()); 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 d.findViewById(R.id.details_frame_rate_row) 398 .setVisibility(View.GONE); 399 d.findViewById(R.id.details_bit_rate_row) 400 .setVisibility(View.GONE); 401 d.findViewById(R.id.details_format_row) 402 .setVisibility(View.GONE); 403 d.findViewById(R.id.details_codec_row) 404 .setVisibility(View.GONE); 405 406 int dimensionWidth = 0; 407 int dimensionHeight = 0; 408 if (ImageManager.isImage(image)) { 409 // getWidth is much slower than reading from EXIF 410 dimensionWidth = image.getWidth(); 411 dimensionHeight = image.getHeight(); 412 d.findViewById(R.id.details_duration_row) 413 .setVisibility(View.GONE); 414 } 415 416 String value = null; 417 if (dimensionWidth > 0 && dimensionHeight > 0) { 418 value = String.format( 419 activity.getString(R.string.details_dimension_x), 420 dimensionWidth, dimensionHeight); 421 } 422 423 if (value != null) { 424 setDetailsValue(d, value, R.id.details_resolution_value); 425 } else { 426 hideDetailsRow(d, R.id.details_resolution_row); 427 } 428 429 value = EMPTY_STRING; 430 long dateTaken = image.getDateTaken(); 431 if (dateTaken != 0) { 432 Date date = new Date(image.getDateTaken()); 433 SimpleDateFormat dateFormat = new SimpleDateFormat(); 434 value = dateFormat.format(date); 435 } 436 if (value != EMPTY_STRING) { 437 setDetailsValue(d, value, R.id.details_date_taken_value); 438 } else { 439 hideDetailsRow(d, R.id.details_date_taken_row); 440 } 441 442 // Show more EXIF header details for JPEG images. 443 if (JPEG_MIME_TYPE.equals(image.getMimeType())) { 444 showExifInformation(image, d, activity); 445 } else { 446 hideExifInformation(d); 447 } 448 449 builder.setNeutralButton(R.string.details_ok, 450 new DialogInterface.OnClickListener() { 451 public void onClick(DialogInterface dialog, 452 int which) { 453 dialog.dismiss(); 454 } 455 }); 456 457 handler.post( 458 new Runnable() { 459 public void run() { 460 builder.setIcon( 461 android.R.drawable.ic_dialog_info) 462 .setTitle(R.string.details_panel_title) 463 .setView(d) 464 .show(); 465 } 466 }); 467 } 468 }); 469 return true; 470 } 471 472 // Called when "Rotate left" or "Rotate right" is clicked. 473 private static boolean onRotateClicked(MenuInvoker onInvoke, 474 final int degree) { 475 onInvoke.run(new MenuCallback() { 476 public void run(Uri u, IImage image) { 477 if (image == null || image.isReadonly()) { 478 return; 479 } 480 image.rotateImageBy(degree); 481 } 482 }); 483 return true; 484 } 485 486 // Called when "Crop" is clicked. 487 private static boolean onCropClicked(MenuInvoker onInvoke, 488 final Activity activity) { 489 onInvoke.run(new MenuCallback() { 490 public void run(Uri u, IImage image) { 491 if (u == null) { 492 return; 493 } 494 495 Intent cropIntent = new Intent( 496 "com.android.camera.action.CROP"); 497 cropIntent.setData(u); 498 activity.startActivityForResult( 499 cropIntent, RESULT_COMMON_MENU_CROP); 500 } 501 }); 502 return true; 503 } 504 505 // Called when "Set as" is clicked. 506 private static boolean onSetAsClicked(MenuInvoker onInvoke, 507 final Activity activity) { 508 onInvoke.run(new MenuCallback() { 509 public void run(Uri u, IImage image) { 510 if (u == null || image == null) { 511 return; 512 } 513 514 Intent intent = Util.createSetAsIntent(image); 515 activity.startActivity(Intent.createChooser(intent, 516 activity.getText(R.string.setImage))); 517 } 518 }); 519 return true; 520 } 521 522 // Called when "Share" is clicked. 523 private static boolean onImageShareClicked(MenuInvoker onInvoke, 524 final Activity activity) { 525 onInvoke.run(new MenuCallback() { 526 public void run(Uri u, IImage image) { 527 if (image == null) return; 528 529 Intent intent = new Intent(); 530 intent.setAction(Intent.ACTION_SEND); 531 String mimeType = image.getMimeType(); 532 intent.setType(mimeType); 533 intent.putExtra(Intent.EXTRA_STREAM, u); 534 boolean isImage = ImageManager.isImage(image); 535 try { 536 activity.startActivity(Intent.createChooser(intent, 537 activity.getText(isImage 538 ? R.string.sendImage 539 : R.string.sendVideo))); 540 } catch (android.content.ActivityNotFoundException ex) { 541 Toast.makeText(activity, isImage 542 ? R.string.no_way_to_share_image 543 : R.string.no_way_to_share_video, 544 Toast.LENGTH_SHORT).show(); 545 } 546 } 547 }); 548 return true; 549 } 550 551 // Called when "Play" is clicked. 552 private static boolean onViewPlayClicked(MenuInvoker onInvoke, 553 final Activity activity) { 554 onInvoke.run(new MenuCallback() { 555 public void run(Uri uri, IImage image) { 556 if (image != null) { 557 Intent intent = new Intent(Intent.ACTION_VIEW, 558 image.fullSizeImageUri()); 559 activity.startActivity(intent); 560 } 561 }}); 562 return true; 563 } 564 565 // Called when "Delete" is clicked. 566 private static boolean onDeleteClicked(MenuInvoker onInvoke, 567 final Activity activity, final Runnable onDelete) { 568 onInvoke.run(new MenuCallback() { 569 public void run(Uri uri, IImage image) { 570 if (image != null) { 571 deleteImage(activity, onDelete, image); 572 } 573 }}); 574 return true; 575 } 576 577 static MenuItemsResult addImageMenuItems( 578 Menu menu, 579 int inclusions, 580 final Activity activity, 581 final Handler handler, 582 final Runnable onDelete, 583 final MenuInvoker onInvoke) { 584 final ArrayList<MenuItem> requiresWriteAccessItems = 585 new ArrayList<MenuItem>(); 586 final ArrayList<MenuItem> requiresNoDrmAccessItems = 587 new ArrayList<MenuItem>(); 588 final ArrayList<MenuItem> requiresImageItems = 589 new ArrayList<MenuItem>(); 590 final ArrayList<MenuItem> requiresVideoItems = 591 new ArrayList<MenuItem>(); 592 593 if ((inclusions & INCLUDE_ROTATE_MENU) != 0) { 594 SubMenu rotateSubmenu = menu.addSubMenu(Menu.NONE, Menu.NONE, 595 POSITION_IMAGE_ROTATE, R.string.rotate) 596 .setIcon(android.R.drawable.ic_menu_rotate); 597 // Don't show the rotate submenu if the item at hand is read only 598 // since the items within the submenu won't be shown anyway. This 599 // is really a framework bug in that it shouldn't show the submenu 600 // if the submenu has no visible items. 601 MenuItem rotateLeft = rotateSubmenu.add(R.string.rotate_left) 602 .setOnMenuItemClickListener( 603 new MenuItem.OnMenuItemClickListener() { 604 public boolean onMenuItemClick(MenuItem item) { 605 return onRotateClicked(onInvoke, -90); 606 } 607 }).setAlphabeticShortcut('l'); 608 609 MenuItem rotateRight = rotateSubmenu.add(R.string.rotate_right) 610 .setOnMenuItemClickListener( 611 new MenuItem.OnMenuItemClickListener() { 612 public boolean onMenuItemClick(MenuItem item) { 613 return onRotateClicked(onInvoke, 90); 614 } 615 }).setAlphabeticShortcut('r'); 616 617 requiresWriteAccessItems.add(rotateSubmenu.getItem()); 618 requiresWriteAccessItems.add(rotateLeft); 619 requiresWriteAccessItems.add(rotateRight); 620 621 requiresImageItems.add(rotateSubmenu.getItem()); 622 requiresImageItems.add(rotateLeft); 623 requiresImageItems.add(rotateRight); 624 } 625 626 if ((inclusions & INCLUDE_CROP_MENU) != 0) { 627 MenuItem autoCrop = menu.add(Menu.NONE, Menu.NONE, 628 POSITION_IMAGE_CROP, R.string.camera_crop); 629 autoCrop.setIcon(android.R.drawable.ic_menu_crop); 630 autoCrop.setOnMenuItemClickListener( 631 new MenuItem.OnMenuItemClickListener() { 632 public boolean onMenuItemClick(MenuItem item) { 633 return onCropClicked(onInvoke, activity); 634 } 635 }); 636 requiresWriteAccessItems.add(autoCrop); 637 requiresImageItems.add(autoCrop); 638 } 639 640 if ((inclusions & INCLUDE_SET_MENU) != 0) { 641 MenuItem setMenu = menu.add(Menu.NONE, Menu.NONE, 642 POSITION_IMAGE_SET, R.string.camera_set); 643 setMenu.setIcon(android.R.drawable.ic_menu_set_as); 644 setMenu.setOnMenuItemClickListener( 645 new MenuItem.OnMenuItemClickListener() { 646 public boolean onMenuItemClick(MenuItem item) { 647 return onSetAsClicked(onInvoke, activity); 648 } 649 }); 650 requiresImageItems.add(setMenu); 651 } 652 653 if ((inclusions & INCLUDE_SHARE_MENU) != 0) { 654 MenuItem item1 = menu.add(Menu.NONE, MENU_IMAGE_SHARE, 655 POSITION_IMAGE_SHARE, R.string.camera_share) 656 .setOnMenuItemClickListener( 657 new MenuItem.OnMenuItemClickListener() { 658 public boolean onMenuItemClick(MenuItem item) { 659 return onImageShareClicked(onInvoke, activity); 660 } 661 }); 662 item1.setIcon(android.R.drawable.ic_menu_share); 663 MenuItem item = item1; 664 requiresNoDrmAccessItems.add(item); 665 } 666 667 if ((inclusions & INCLUDE_DELETE_MENU) != 0) { 668 MenuItem deleteItem = menu.add(Menu.NONE, Menu.NONE, 669 POSITION_IMAGE_TOSS, R.string.camera_toss); 670 requiresWriteAccessItems.add(deleteItem); 671 deleteItem.setOnMenuItemClickListener( 672 new MenuItem.OnMenuItemClickListener() { 673 public boolean onMenuItemClick(MenuItem item) { 674 return onDeleteClicked(onInvoke, activity, 675 onDelete); 676 } 677 }) 678 .setAlphabeticShortcut('d') 679 .setIcon(android.R.drawable.ic_menu_delete); 680 } 681 682 if ((inclusions & INCLUDE_DETAILS_MENU) != 0) { 683 MenuItem detailsMenu = menu.add(Menu.NONE, Menu.NONE, 684 POSITION_DETAILS, R.string.details) 685 .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { 686 public boolean onMenuItemClick(MenuItem item) { 687 return onDetailsClicked(onInvoke, handler, activity); 688 } 689 }); 690 detailsMenu.setIcon(R.drawable.ic_menu_view_details); 691 } 692 693 if ((inclusions & INCLUDE_SHOWMAP_MENU) != 0) { 694 MenuItem showOnMapItem = menu.add(Menu.NONE, MENU_IMAGE_SHOWMAP, 695 POSITION_SHOWMAP, R.string.show_on_map); 696 showOnMapItem.setOnMenuItemClickListener( 697 new MenuItem.OnMenuItemClickListener() { 698 public boolean onMenuItemClick(MenuItem item) { 699 return onShowMapClicked(onInvoke, 700 handler, activity); 701 } 702 }).setIcon(R.drawable.ic_menu_3d_globe); 703 requiresImageItems.add(showOnMapItem); 704 } 705 706 if ((inclusions & INCLUDE_VIEWPLAY_MENU) != 0) { 707 MenuItem videoPlayItem = menu.add(Menu.NONE, Menu.NONE, 708 POSITION_VIEWPLAY, R.string.video_play) 709 .setOnMenuItemClickListener( 710 new MenuItem.OnMenuItemClickListener() { 711 public boolean onMenuItemClick(MenuItem item) { 712 return onViewPlayClicked(onInvoke, activity); 713 } 714 }); 715 videoPlayItem.setIcon( 716 com.android.internal.R.drawable.ic_menu_play_clip); 717 requiresVideoItems.add(videoPlayItem); 718 } 719 720 return new MenuItemsResult() { 721 public void gettingReadyToOpen(Menu menu, IImage image) { 722 // protect against null here. this isn't strictly speaking 723 // required but if a client app isn't handling sdcard removal 724 // properly it could happen 725 if (image == null) { 726 return; 727 } 728 729 ArrayList<MenuItem> enableList = new ArrayList<MenuItem>(); 730 ArrayList<MenuItem> disableList = new ArrayList<MenuItem>(); 731 ArrayList<MenuItem> list; 732 733 list = image.isReadonly() ? disableList : enableList; 734 list.addAll(requiresWriteAccessItems); 735 736 list = image.isDrm() ? disableList : enableList; 737 list.addAll(requiresNoDrmAccessItems); 738 739 list = ImageManager.isImage(image) ? enableList : disableList; 740 list.addAll(requiresImageItems); 741 742 list = ImageManager.isVideo(image) ? enableList : disableList; 743 list.addAll(requiresVideoItems); 744 745 for (MenuItem item : enableList) { 746 item.setVisible(true); 747 item.setEnabled(true); 748 } 749 750 for (MenuItem item : disableList) { 751 item.setVisible(false); 752 item.setEnabled(false); 753 } 754 } 755 756 // must override abstract method 757 public void aboutToCall(MenuItem menu, IImage image) { 758 } 759 }; 760 } 761 762 static void deletePhoto(Activity activity, Runnable onDelete) { 763 deleteImpl(activity, onDelete, true); 764 } 765 766 static void deleteImage( 767 Activity activity, Runnable onDelete, IImage image) { 768 deleteImpl(activity, onDelete, ImageManager.isImage(image)); 769 } 770 771 static void deleteImpl( 772 Activity activity, Runnable onDelete, boolean isImage) { 773 boolean needConfirm = PreferenceManager 774 .getDefaultSharedPreferences(activity) 775 .getBoolean("pref_gallery_confirm_delete_key", true); 776 if (!needConfirm) { 777 if (onDelete != null) onDelete.run(); 778 } else { 779 String title = activity.getString(R.string.confirm_delete_title); 780 String message = activity.getString(isImage 781 ? R.string.confirm_delete_message 782 : R.string.confirm_delete_video_message); 783 confirmAction(activity, title, message, onDelete); 784 } 785 } 786 787 public static void deleteMultiple(Context context, Runnable action) { 788 boolean needConfirm = PreferenceManager 789 .getDefaultSharedPreferences(context) 790 .getBoolean("pref_gallery_confirm_delete_key", true); 791 if (!needConfirm) { 792 if (action != null) action.run(); 793 } else { 794 String title = context.getString(R.string.confirm_delete_title); 795 String message = context.getString( 796 R.string.confirm_delete_multiple_message); 797 confirmAction(context, title, message, action); 798 } 799 } 800 801 public static void confirmAction(Context context, String title, 802 String message, final Runnable action) { 803 OnClickListener listener = new OnClickListener() { 804 public void onClick(DialogInterface dialog, int which) { 805 switch (which) { 806 case DialogInterface.BUTTON_POSITIVE: 807 if (action != null) action.run(); 808 } 809 } 810 }; 811 new AlertDialog.Builder(context) 812 .setIcon(android.R.drawable.ic_dialog_alert) 813 .setTitle(title) 814 .setMessage(message) 815 .setPositiveButton(android.R.string.ok, listener) 816 .setNegativeButton(android.R.string.cancel, listener) 817 .create() 818 .show(); 819 } 820 821 static void addCapturePictureMenuItems(Menu menu, final Activity activity) { 822 menu.add(Menu.NONE, Menu.NONE, POSITION_CAPTURE_PICTURE, 823 R.string.capture_picture) 824 .setOnMenuItemClickListener( 825 new MenuItem.OnMenuItemClickListener() { 826 public boolean onMenuItemClick(MenuItem item) { 827 return onCapturePictureClicked(activity); 828 } 829 }).setIcon(android.R.drawable.ic_menu_camera); 830 } 831 832 private static boolean onCapturePictureClicked(Activity activity) { 833 Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA); 834 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 835 try { 836 activity.startActivity(intent); 837 } catch (android.content.ActivityNotFoundException e) { 838 // Ignore exception 839 } 840 return true; 841 } 842 843 static void addCaptureVideoMenuItems(Menu menu, final Activity activity) { 844 menu.add(Menu.NONE, Menu.NONE, POSITION_CAPTURE_VIDEO, 845 R.string.capture_video) 846 .setOnMenuItemClickListener( 847 new MenuItem.OnMenuItemClickListener() { 848 public boolean onMenuItemClick(MenuItem item) { 849 return onCaptureVideoClicked(activity); 850 } 851 }).setIcon(R.drawable.ic_menu_camera_video_view); 852 } 853 854 private static boolean onCaptureVideoClicked(Activity activity) { 855 Intent intent = new Intent(MediaStore.INTENT_ACTION_VIDEO_CAMERA); 856 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 857 try { 858 activity.startActivity(intent); 859 } catch (android.content.ActivityNotFoundException e) { 860 // Ignore exception 861 } 862 return true; 863 } 864 865 public static void addCaptureMenuItems(Menu menu, final Activity activity) { 866 addCapturePictureMenuItems(menu, activity); 867 addCaptureVideoMenuItems(menu, activity); 868 } 869 870 public static String formatDuration(final Context context, 871 int durationMs) { 872 int duration = durationMs / 1000; 873 int h = duration / 3600; 874 int m = (duration - h * 3600) / 60; 875 int s = duration - (h * 3600 + m * 60); 876 String durationValue; 877 if (h == 0) { 878 durationValue = String.format( 879 context.getString(R.string.details_ms), m, s); 880 } else { 881 durationValue = String.format( 882 context.getString(R.string.details_hms), h, m, s); 883 } 884 return durationValue; 885 } 886 887 public static void showStorageToast(Activity activity) { 888 showStorageToast(activity, calculatePicturesRemaining()); 889 } 890 891 public static void showStorageToast(Activity activity, int remaining) { 892 String noStorageText = null; 893 894 if (remaining == MenuHelper.NO_STORAGE_ERROR) { 895 String state = Environment.getExternalStorageState(); 896 if (state == Environment.MEDIA_CHECKING) { 897 noStorageText = activity.getString(R.string.preparing_sd); 898 } else { 899 noStorageText = activity.getString(R.string.no_storage); 900 } 901 } else if (remaining < 1) { 902 noStorageText = activity.getString(R.string.not_enough_space); 903 } 904 905 if (noStorageText != null) { 906 Toast.makeText(activity, noStorageText, 5000).show(); 907 } 908 } 909 910 public static int calculatePicturesRemaining() { 911 try { 912 if (!ImageManager.hasStorage()) { 913 return NO_STORAGE_ERROR; 914 } else { 915 String storageDirectory = 916 Environment.getExternalStorageDirectory().toString(); 917 StatFs stat = new StatFs(storageDirectory); 918 float remaining = ((float) stat.getAvailableBlocks() 919 * (float) stat.getBlockSize()) / 400000F; 920 return (int) remaining; 921 } 922 } catch (Exception ex) { 923 // if we can't stat the filesystem then we don't know how many 924 // pictures are remaining. it might be zero but just leave it 925 // blank since we really don't know. 926 return CANNOT_STAT_ERROR; 927 } 928 } 929 } 930