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 { gettingReadyToOpen(Menu menu, IImage image)106 public void gettingReadyToOpen(Menu menu, IImage image); aboutToCall(MenuItem item, IImage image)107 public void aboutToCall(MenuItem item, IImage image); 108 } 109 110 public interface MenuInvoker { run(MenuCallback r)111 public void run(MenuCallback r); 112 } 113 114 public interface MenuCallback { run(Uri uri, IImage image)115 public void run(Uri uri, IImage image); 116 } 117 closeSilently(Closeable c)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 getImageFileSize(IImage image)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) isWhiteListUri(Uri uri)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 enableShareMenuItem(Menu menu, boolean enabled)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 hasLatLngData(IImage image)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 enableShowOnMapMenuItem(Menu menu, boolean enabled)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 setDetailsValue(View d, String text, int valueId)188 private static void setDetailsValue(View d, String text, int valueId) { 189 ((TextView) d.findViewById(valueId)).setText(text); 190 } 191 hideDetailsRow(View d, int rowId)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 UpdateLocationCallback(WeakReference<View> view)200 public UpdateLocationCallback(WeakReference<View> view) { 201 mView = view; 202 } 203 onComplete(String location)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 setLatLngDetails(final View d, Activity context, ExifInterface exif)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 getExif(IImage image)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. onShowMapClicked(MenuInvoker onInvoke, final Handler handler, final Activity activity)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 hideExifInformation(View d)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 showExifInformation(IImage image, View d, Activity activity)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 */ getWhiteBalanceString(ExifInterface exif)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. onDetailsClicked(MenuInvoker onInvoke, final Handler handler, final Activity activity)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. onRotateClicked(MenuInvoker onInvoke, final int degree)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. onCropClicked(MenuInvoker onInvoke, final Activity activity)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. onSetAsClicked(MenuInvoker onInvoke, final Activity activity)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. onImageShareClicked(MenuInvoker onInvoke, final Activity activity)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. onViewPlayClicked(MenuInvoker onInvoke, final Activity activity)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. onDeleteClicked(MenuInvoker onInvoke, final Activity activity, final Runnable onDelete)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 addImageMenuItems( Menu menu, int inclusions, final Activity activity, final Handler handler, final Runnable onDelete, final MenuInvoker onInvoke)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