You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
930 lines
36 KiB
930 lines
36 KiB
/*
|
|
* Copyright (C) 2008 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.camera;
|
|
|
|
import com.android.gallery.R;
|
|
|
|
import android.app.Activity;
|
|
import android.app.AlertDialog;
|
|
import android.content.ActivityNotFoundException;
|
|
import android.content.Context;
|
|
import android.content.DialogInterface;
|
|
import android.content.Intent;
|
|
import android.content.DialogInterface.OnClickListener;
|
|
import android.location.Geocoder;
|
|
import android.media.ExifInterface;
|
|
import android.media.MediaMetadataRetriever;
|
|
import android.net.Uri;
|
|
import android.os.Environment;
|
|
import android.os.Handler;
|
|
import android.os.StatFs;
|
|
import android.preference.PreferenceManager;
|
|
import android.provider.MediaStore;
|
|
import android.provider.MediaStore.Images;
|
|
import android.text.format.Formatter;
|
|
import android.util.Log;
|
|
import android.view.Menu;
|
|
import android.view.MenuItem;
|
|
import android.view.SubMenu;
|
|
import android.view.View;
|
|
import android.widget.ImageView;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
|
|
import com.android.camera.gallery.IImage;
|
|
|
|
import java.io.Closeable;
|
|
import java.io.IOException;
|
|
import java.lang.ref.WeakReference;
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.ArrayList;
|
|
import java.util.Date;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* A utility class to handle various kinds of menu operations.
|
|
*/
|
|
public class MenuHelper {
|
|
private static final String TAG = "MenuHelper";
|
|
|
|
public static final int INCLUDE_ALL = 0xFFFFFFFF;
|
|
public static final int INCLUDE_VIEWPLAY_MENU = (1 << 0);
|
|
public static final int INCLUDE_SHARE_MENU = (1 << 1);
|
|
public static final int INCLUDE_SET_MENU = (1 << 2);
|
|
public static final int INCLUDE_CROP_MENU = (1 << 3);
|
|
public static final int INCLUDE_DELETE_MENU = (1 << 4);
|
|
public static final int INCLUDE_ROTATE_MENU = (1 << 5);
|
|
public static final int INCLUDE_DETAILS_MENU = (1 << 6);
|
|
public static final int INCLUDE_SHOWMAP_MENU = (1 << 7);
|
|
|
|
public static final int MENU_IMAGE_SHARE = 1;
|
|
public static final int MENU_IMAGE_SHOWMAP = 2;
|
|
|
|
public static final int POSITION_SWITCH_CAMERA_MODE = 1;
|
|
public static final int POSITION_GOTO_GALLERY = 2;
|
|
public static final int POSITION_VIEWPLAY = 3;
|
|
public static final int POSITION_CAPTURE_PICTURE = 4;
|
|
public static final int POSITION_CAPTURE_VIDEO = 5;
|
|
public static final int POSITION_IMAGE_SHARE = 6;
|
|
public static final int POSITION_IMAGE_ROTATE = 7;
|
|
public static final int POSITION_IMAGE_TOSS = 8;
|
|
public static final int POSITION_IMAGE_CROP = 9;
|
|
public static final int POSITION_IMAGE_SET = 10;
|
|
public static final int POSITION_DETAILS = 11;
|
|
public static final int POSITION_SHOWMAP = 12;
|
|
public static final int POSITION_SLIDESHOW = 13;
|
|
public static final int POSITION_MULTISELECT = 14;
|
|
public static final int POSITION_CAMERA_SETTING = 15;
|
|
public static final int POSITION_GALLERY_SETTING = 16;
|
|
|
|
public static final int NO_STORAGE_ERROR = -1;
|
|
public static final int CANNOT_STAT_ERROR = -2;
|
|
public static final String EMPTY_STRING = "";
|
|
public static final String JPEG_MIME_TYPE = "image/jpeg";
|
|
// valid range is -180f to +180f
|
|
public static final float INVALID_LATLNG = 255f;
|
|
|
|
/** Activity result code used to report crop results.
|
|
*/
|
|
public static final int RESULT_COMMON_MENU_CROP = 490;
|
|
|
|
public interface MenuItemsResult {
|
|
public void gettingReadyToOpen(Menu menu, IImage image);
|
|
public void aboutToCall(MenuItem item, IImage image);
|
|
}
|
|
|
|
public interface MenuInvoker {
|
|
public void run(MenuCallback r);
|
|
}
|
|
|
|
public interface MenuCallback {
|
|
public void run(Uri uri, IImage image);
|
|
}
|
|
|
|
public static void closeSilently(Closeable c) {
|
|
if (c != null) {
|
|
try {
|
|
c.close();
|
|
} catch (Throwable e) {
|
|
// ignore
|
|
}
|
|
}
|
|
}
|
|
|
|
public static long getImageFileSize(IImage image) {
|
|
java.io.InputStream data = image.fullSizeImageData();
|
|
if (data == null) return -1;
|
|
try {
|
|
return data.available();
|
|
} catch (java.io.IOException ex) {
|
|
return -1;
|
|
} finally {
|
|
closeSilently(data);
|
|
}
|
|
}
|
|
|
|
// This is a hack before we find a solution to pass a permission to other
|
|
// applications. See bug #1735149, #1836138.
|
|
// Checks if the URI is on our whitelist:
|
|
// content://media/... (MediaProvider)
|
|
// file:///sdcard/... (Browser download)
|
|
public static boolean isWhiteListUri(Uri uri) {
|
|
if (uri == null) return false;
|
|
|
|
String scheme = uri.getScheme();
|
|
String authority = uri.getAuthority();
|
|
|
|
if (scheme.equals("content") && authority.equals("media")) {
|
|
return true;
|
|
}
|
|
|
|
if (scheme.equals("file")) {
|
|
List<String> p = uri.getPathSegments();
|
|
|
|
if (p.size() >= 1 && p.get(0).equals("sdcard")) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static void enableShareMenuItem(Menu menu, boolean enabled) {
|
|
MenuItem item = menu.findItem(MENU_IMAGE_SHARE);
|
|
if (item != null) {
|
|
item.setVisible(enabled);
|
|
item.setEnabled(enabled);
|
|
}
|
|
}
|
|
|
|
public static boolean hasLatLngData(IImage image) {
|
|
ExifInterface exif = getExif(image);
|
|
if (exif == null) return false;
|
|
float latlng[] = new float[2];
|
|
return exif.getLatLong(latlng);
|
|
}
|
|
|
|
public static void enableShowOnMapMenuItem(Menu menu, boolean enabled) {
|
|
MenuItem item = menu.findItem(MENU_IMAGE_SHOWMAP);
|
|
if (item != null) {
|
|
item.setEnabled(enabled);
|
|
}
|
|
}
|
|
|
|
private static void setDetailsValue(View d, String text, int valueId) {
|
|
((TextView) d.findViewById(valueId)).setText(text);
|
|
}
|
|
|
|
private static void hideDetailsRow(View d, int rowId) {
|
|
d.findViewById(rowId).setVisibility(View.GONE);
|
|
}
|
|
|
|
private static class UpdateLocationCallback implements
|
|
ReverseGeocoderTask.Callback {
|
|
WeakReference<View> mView;
|
|
|
|
public UpdateLocationCallback(WeakReference<View> view) {
|
|
mView = view;
|
|
}
|
|
|
|
public void onComplete(String location) {
|
|
// View d is per-thread data, so when setDetailsValue is
|
|
// executed by UI thread, it doesn't matter whether the
|
|
// details dialog is dismissed or not.
|
|
View view = mView.get();
|
|
if (view == null) return;
|
|
if (!location.equals(MenuHelper.EMPTY_STRING)) {
|
|
MenuHelper.setDetailsValue(view, location,
|
|
R.id.details_location_value);
|
|
} else {
|
|
MenuHelper.hideDetailsRow(view, R.id.details_location_row);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void setLatLngDetails(final View d, Activity context,
|
|
ExifInterface exif) {
|
|
float[] latlng = new float[2];
|
|
if (exif.getLatLong(latlng)) {
|
|
setDetailsValue(d, String.valueOf(latlng[0]),
|
|
R.id.details_latitude_value);
|
|
setDetailsValue(d, String.valueOf(latlng[1]),
|
|
R.id.details_longitude_value);
|
|
|
|
if (latlng[0] == INVALID_LATLNG || latlng[1] == INVALID_LATLNG) {
|
|
hideDetailsRow(d, R.id.details_latitude_row);
|
|
hideDetailsRow(d, R.id.details_longitude_row);
|
|
hideDetailsRow(d, R.id.details_location_row);
|
|
return;
|
|
}
|
|
|
|
UpdateLocationCallback cb = new UpdateLocationCallback(
|
|
new WeakReference<View>(d));
|
|
Geocoder geocoder = new Geocoder(context);
|
|
new ReverseGeocoderTask(geocoder, latlng, cb).execute();
|
|
} else {
|
|
hideDetailsRow(d, R.id.details_latitude_row);
|
|
hideDetailsRow(d, R.id.details_longitude_row);
|
|
hideDetailsRow(d, R.id.details_location_row);
|
|
}
|
|
}
|
|
|
|
private static ExifInterface getExif(IImage image) {
|
|
if (!JPEG_MIME_TYPE.equals(image.getMimeType())) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return new ExifInterface(image.getDataPath());
|
|
} catch (IOException ex) {
|
|
Log.e(TAG, "cannot read exif", ex);
|
|
return null;
|
|
}
|
|
}
|
|
// Called when "Show on Maps" is clicked.
|
|
// Displays image location on Google Maps for further operations.
|
|
private static boolean onShowMapClicked(MenuInvoker onInvoke,
|
|
final Handler handler,
|
|
final Activity activity) {
|
|
onInvoke.run(new MenuCallback() {
|
|
public void run(Uri u, IImage image) {
|
|
if (image == null) {
|
|
return;
|
|
}
|
|
|
|
boolean ok = false;
|
|
ExifInterface exif = getExif(image);
|
|
float latlng[] = null;
|
|
if (exif != null) {
|
|
latlng = new float[2];
|
|
if (exif.getLatLong(latlng)) {
|
|
ok = true;
|
|
}
|
|
}
|
|
|
|
if (!ok) {
|
|
handler.post(new Runnable() {
|
|
public void run() {
|
|
Toast.makeText(activity,
|
|
R.string.no_location_image,
|
|
Toast.LENGTH_SHORT).show();
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Can't use geo:latitude,longitude because it only centers
|
|
// the MapView to specified location, but we need a bubble
|
|
// for further operations (routing to/from).
|
|
// The q=(lat, lng) syntax is suggested by geo-team.
|
|
String uri = "http://maps.google.com/maps?f=q&" +
|
|
"q=(" + latlng[0] + "," + latlng[1] + ")";
|
|
activity.startActivity(new Intent(
|
|
android.content.Intent.ACTION_VIEW,
|
|
Uri.parse(uri)));
|
|
}
|
|
});
|
|
return true;
|
|
}
|
|
|
|
private static void hideExifInformation(View d) {
|
|
hideDetailsRow(d, R.id.details_resolution_row);
|
|
hideDetailsRow(d, R.id.details_make_row);
|
|
hideDetailsRow(d, R.id.details_model_row);
|
|
hideDetailsRow(d, R.id.details_whitebalance_row);
|
|
hideDetailsRow(d, R.id.details_latitude_row);
|
|
hideDetailsRow(d, R.id.details_longitude_row);
|
|
hideDetailsRow(d, R.id.details_location_row);
|
|
}
|
|
|
|
private static void showExifInformation(IImage image, View d,
|
|
Activity activity) {
|
|
ExifInterface exif = getExif(image);
|
|
if (exif == null) {
|
|
hideExifInformation(d);
|
|
return;
|
|
}
|
|
|
|
String value = exif.getAttribute(ExifInterface.TAG_MAKE);
|
|
if (value != null) {
|
|
setDetailsValue(d, value, R.id.details_make_value);
|
|
} else {
|
|
hideDetailsRow(d, R.id.details_make_row);
|
|
}
|
|
|
|
value = exif.getAttribute(ExifInterface.TAG_MODEL);
|
|
if (value != null) {
|
|
setDetailsValue(d, value, R.id.details_model_value);
|
|
} else {
|
|
hideDetailsRow(d, R.id.details_model_row);
|
|
}
|
|
|
|
value = getWhiteBalanceString(exif);
|
|
if (value != null && !value.equals(EMPTY_STRING)) {
|
|
setDetailsValue(d, value, R.id.details_whitebalance_value);
|
|
} else {
|
|
hideDetailsRow(d, R.id.details_whitebalance_row);
|
|
}
|
|
|
|
setLatLngDetails(d, activity, exif);
|
|
}
|
|
|
|
/**
|
|
* Returns a human-readable string describing the white balance value. Returns empty
|
|
* string if there is no white balance value or it is not recognized.
|
|
*/
|
|
private static String getWhiteBalanceString(ExifInterface exif) {
|
|
int whitebalance = exif.getAttributeInt(ExifInterface.TAG_WHITE_BALANCE, -1);
|
|
if (whitebalance == -1) return "";
|
|
|
|
switch (whitebalance) {
|
|
case ExifInterface.WHITEBALANCE_AUTO:
|
|
return "Auto";
|
|
case ExifInterface.WHITEBALANCE_MANUAL:
|
|
return "Manual";
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
// Called when "Details" is clicked.
|
|
// Displays detailed information about the image/video.
|
|
private static boolean onDetailsClicked(MenuInvoker onInvoke,
|
|
final Handler handler,
|
|
final Activity activity) {
|
|
onInvoke.run(new MenuCallback() {
|
|
public void run(Uri u, IImage image) {
|
|
if (image == null) {
|
|
return;
|
|
}
|
|
|
|
final AlertDialog.Builder builder =
|
|
new AlertDialog.Builder(activity);
|
|
|
|
final View d = View.inflate(activity, R.layout.detailsview,
|
|
null);
|
|
|
|
ImageView imageView = (ImageView) d.findViewById(
|
|
R.id.details_thumbnail_image);
|
|
imageView.setImageBitmap(image.miniThumbBitmap());
|
|
|
|
TextView textView = (TextView) d.findViewById(
|
|
R.id.details_image_title);
|
|
textView.setText(image.getTitle());
|
|
|
|
long length = getImageFileSize(image);
|
|
String lengthString = length < 0
|
|
? EMPTY_STRING
|
|
: Formatter.formatFileSize(activity, length);
|
|
((TextView) d
|
|
.findViewById(R.id.details_file_size_value))
|
|
.setText(lengthString);
|
|
|
|
d.findViewById(R.id.details_frame_rate_row)
|
|
.setVisibility(View.GONE);
|
|
d.findViewById(R.id.details_bit_rate_row)
|
|
.setVisibility(View.GONE);
|
|
d.findViewById(R.id.details_format_row)
|
|
.setVisibility(View.GONE);
|
|
d.findViewById(R.id.details_codec_row)
|
|
.setVisibility(View.GONE);
|
|
|
|
int dimensionWidth = 0;
|
|
int dimensionHeight = 0;
|
|
if (ImageManager.isImage(image)) {
|
|
// getWidth is much slower than reading from EXIF
|
|
dimensionWidth = image.getWidth();
|
|
dimensionHeight = image.getHeight();
|
|
d.findViewById(R.id.details_duration_row)
|
|
.setVisibility(View.GONE);
|
|
}
|
|
|
|
String value = null;
|
|
if (dimensionWidth > 0 && dimensionHeight > 0) {
|
|
value = String.format(
|
|
activity.getString(R.string.details_dimension_x),
|
|
dimensionWidth, dimensionHeight);
|
|
}
|
|
|
|
if (value != null) {
|
|
setDetailsValue(d, value, R.id.details_resolution_value);
|
|
} else {
|
|
hideDetailsRow(d, R.id.details_resolution_row);
|
|
}
|
|
|
|
value = EMPTY_STRING;
|
|
long dateTaken = image.getDateTaken();
|
|
if (dateTaken != 0) {
|
|
Date date = new Date(image.getDateTaken());
|
|
SimpleDateFormat dateFormat = new SimpleDateFormat();
|
|
value = dateFormat.format(date);
|
|
}
|
|
if (value != EMPTY_STRING) {
|
|
setDetailsValue(d, value, R.id.details_date_taken_value);
|
|
} else {
|
|
hideDetailsRow(d, R.id.details_date_taken_row);
|
|
}
|
|
|
|
// Show more EXIF header details for JPEG images.
|
|
if (JPEG_MIME_TYPE.equals(image.getMimeType())) {
|
|
showExifInformation(image, d, activity);
|
|
} else {
|
|
hideExifInformation(d);
|
|
}
|
|
|
|
builder.setNeutralButton(R.string.details_ok,
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog,
|
|
int which) {
|
|
dialog.dismiss();
|
|
}
|
|
});
|
|
|
|
handler.post(
|
|
new Runnable() {
|
|
public void run() {
|
|
builder.setIcon(
|
|
android.R.drawable.ic_dialog_info)
|
|
.setTitle(R.string.details_panel_title)
|
|
.setView(d)
|
|
.show();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
return true;
|
|
}
|
|
|
|
// Called when "Rotate left" or "Rotate right" is clicked.
|
|
private static boolean onRotateClicked(MenuInvoker onInvoke,
|
|
final int degree) {
|
|
onInvoke.run(new MenuCallback() {
|
|
public void run(Uri u, IImage image) {
|
|
if (image == null || image.isReadonly()) {
|
|
return;
|
|
}
|
|
image.rotateImageBy(degree);
|
|
}
|
|
});
|
|
return true;
|
|
}
|
|
|
|
// Called when "Crop" is clicked.
|
|
private static boolean onCropClicked(MenuInvoker onInvoke,
|
|
final Activity activity) {
|
|
onInvoke.run(new MenuCallback() {
|
|
public void run(Uri u, IImage image) {
|
|
if (u == null) {
|
|
return;
|
|
}
|
|
|
|
Intent cropIntent = new Intent(
|
|
"com.android.camera.action.CROP");
|
|
cropIntent.setData(u);
|
|
activity.startActivityForResult(
|
|
cropIntent, RESULT_COMMON_MENU_CROP);
|
|
}
|
|
});
|
|
return true;
|
|
}
|
|
|
|
// Called when "Set as" is clicked.
|
|
private static boolean onSetAsClicked(MenuInvoker onInvoke,
|
|
final Activity activity) {
|
|
onInvoke.run(new MenuCallback() {
|
|
public void run(Uri u, IImage image) {
|
|
if (u == null || image == null) {
|
|
return;
|
|
}
|
|
|
|
Intent intent = Util.createSetAsIntent(image);
|
|
activity.startActivity(Intent.createChooser(intent,
|
|
activity.getText(R.string.setImage)));
|
|
}
|
|
});
|
|
return true;
|
|
}
|
|
|
|
// Called when "Share" is clicked.
|
|
private static boolean onImageShareClicked(MenuInvoker onInvoke,
|
|
final Activity activity) {
|
|
onInvoke.run(new MenuCallback() {
|
|
public void run(Uri u, IImage image) {
|
|
if (image == null) return;
|
|
|
|
Intent intent = new Intent();
|
|
intent.setAction(Intent.ACTION_SEND);
|
|
String mimeType = image.getMimeType();
|
|
intent.setType(mimeType);
|
|
intent.putExtra(Intent.EXTRA_STREAM, u);
|
|
boolean isImage = ImageManager.isImage(image);
|
|
try {
|
|
activity.startActivity(Intent.createChooser(intent,
|
|
activity.getText(isImage
|
|
? R.string.sendImage
|
|
: R.string.sendVideo)));
|
|
} catch (android.content.ActivityNotFoundException ex) {
|
|
Toast.makeText(activity, isImage
|
|
? R.string.no_way_to_share_image
|
|
: R.string.no_way_to_share_video,
|
|
Toast.LENGTH_SHORT).show();
|
|
}
|
|
}
|
|
});
|
|
return true;
|
|
}
|
|
|
|
// Called when "Play" is clicked.
|
|
private static boolean onViewPlayClicked(MenuInvoker onInvoke,
|
|
final Activity activity) {
|
|
onInvoke.run(new MenuCallback() {
|
|
public void run(Uri uri, IImage image) {
|
|
if (image != null) {
|
|
Intent intent = new Intent(Intent.ACTION_VIEW,
|
|
image.fullSizeImageUri());
|
|
activity.startActivity(intent);
|
|
}
|
|
}});
|
|
return true;
|
|
}
|
|
|
|
// Called when "Delete" is clicked.
|
|
private static boolean onDeleteClicked(MenuInvoker onInvoke,
|
|
final Activity activity, final Runnable onDelete) {
|
|
onInvoke.run(new MenuCallback() {
|
|
public void run(Uri uri, IImage image) {
|
|
if (image != null) {
|
|
deleteImage(activity, onDelete, image);
|
|
}
|
|
}});
|
|
return true;
|
|
}
|
|
|
|
static MenuItemsResult addImageMenuItems(
|
|
Menu menu,
|
|
int inclusions,
|
|
final Activity activity,
|
|
final Handler handler,
|
|
final Runnable onDelete,
|
|
final MenuInvoker onInvoke) {
|
|
final ArrayList<MenuItem> requiresWriteAccessItems =
|
|
new ArrayList<MenuItem>();
|
|
final ArrayList<MenuItem> requiresNoDrmAccessItems =
|
|
new ArrayList<MenuItem>();
|
|
final ArrayList<MenuItem> requiresImageItems =
|
|
new ArrayList<MenuItem>();
|
|
final ArrayList<MenuItem> requiresVideoItems =
|
|
new ArrayList<MenuItem>();
|
|
|
|
if ((inclusions & INCLUDE_ROTATE_MENU) != 0) {
|
|
SubMenu rotateSubmenu = menu.addSubMenu(Menu.NONE, Menu.NONE,
|
|
POSITION_IMAGE_ROTATE, R.string.rotate)
|
|
.setIcon(android.R.drawable.ic_menu_rotate);
|
|
// Don't show the rotate submenu if the item at hand is read only
|
|
// since the items within the submenu won't be shown anyway. This
|
|
// is really a framework bug in that it shouldn't show the submenu
|
|
// if the submenu has no visible items.
|
|
MenuItem rotateLeft = rotateSubmenu.add(R.string.rotate_left)
|
|
.setOnMenuItemClickListener(
|
|
new MenuItem.OnMenuItemClickListener() {
|
|
public boolean onMenuItemClick(MenuItem item) {
|
|
return onRotateClicked(onInvoke, -90);
|
|
}
|
|
}).setAlphabeticShortcut('l');
|
|
|
|
MenuItem rotateRight = rotateSubmenu.add(R.string.rotate_right)
|
|
.setOnMenuItemClickListener(
|
|
new MenuItem.OnMenuItemClickListener() {
|
|
public boolean onMenuItemClick(MenuItem item) {
|
|
return onRotateClicked(onInvoke, 90);
|
|
}
|
|
}).setAlphabeticShortcut('r');
|
|
|
|
requiresWriteAccessItems.add(rotateSubmenu.getItem());
|
|
requiresWriteAccessItems.add(rotateLeft);
|
|
requiresWriteAccessItems.add(rotateRight);
|
|
|
|
requiresImageItems.add(rotateSubmenu.getItem());
|
|
requiresImageItems.add(rotateLeft);
|
|
requiresImageItems.add(rotateRight);
|
|
}
|
|
|
|
if ((inclusions & INCLUDE_CROP_MENU) != 0) {
|
|
MenuItem autoCrop = menu.add(Menu.NONE, Menu.NONE,
|
|
POSITION_IMAGE_CROP, R.string.camera_crop);
|
|
autoCrop.setIcon(android.R.drawable.ic_menu_crop);
|
|
autoCrop.setOnMenuItemClickListener(
|
|
new MenuItem.OnMenuItemClickListener() {
|
|
public boolean onMenuItemClick(MenuItem item) {
|
|
return onCropClicked(onInvoke, activity);
|
|
}
|
|
});
|
|
requiresWriteAccessItems.add(autoCrop);
|
|
requiresImageItems.add(autoCrop);
|
|
}
|
|
|
|
if ((inclusions & INCLUDE_SET_MENU) != 0) {
|
|
MenuItem setMenu = menu.add(Menu.NONE, Menu.NONE,
|
|
POSITION_IMAGE_SET, R.string.camera_set);
|
|
setMenu.setIcon(android.R.drawable.ic_menu_set_as);
|
|
setMenu.setOnMenuItemClickListener(
|
|
new MenuItem.OnMenuItemClickListener() {
|
|
public boolean onMenuItemClick(MenuItem item) {
|
|
return onSetAsClicked(onInvoke, activity);
|
|
}
|
|
});
|
|
requiresImageItems.add(setMenu);
|
|
}
|
|
|
|
if ((inclusions & INCLUDE_SHARE_MENU) != 0) {
|
|
MenuItem item1 = menu.add(Menu.NONE, MENU_IMAGE_SHARE,
|
|
POSITION_IMAGE_SHARE, R.string.camera_share)
|
|
.setOnMenuItemClickListener(
|
|
new MenuItem.OnMenuItemClickListener() {
|
|
public boolean onMenuItemClick(MenuItem item) {
|
|
return onImageShareClicked(onInvoke, activity);
|
|
}
|
|
});
|
|
item1.setIcon(android.R.drawable.ic_menu_share);
|
|
MenuItem item = item1;
|
|
requiresNoDrmAccessItems.add(item);
|
|
}
|
|
|
|
if ((inclusions & INCLUDE_DELETE_MENU) != 0) {
|
|
MenuItem deleteItem = menu.add(Menu.NONE, Menu.NONE,
|
|
POSITION_IMAGE_TOSS, R.string.camera_toss);
|
|
requiresWriteAccessItems.add(deleteItem);
|
|
deleteItem.setOnMenuItemClickListener(
|
|
new MenuItem.OnMenuItemClickListener() {
|
|
public boolean onMenuItemClick(MenuItem item) {
|
|
return onDeleteClicked(onInvoke, activity,
|
|
onDelete);
|
|
}
|
|
})
|
|
.setAlphabeticShortcut('d')
|
|
.setIcon(android.R.drawable.ic_menu_delete);
|
|
}
|
|
|
|
if ((inclusions & INCLUDE_DETAILS_MENU) != 0) {
|
|
MenuItem detailsMenu = menu.add(Menu.NONE, Menu.NONE,
|
|
POSITION_DETAILS, R.string.details)
|
|
.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
|
|
public boolean onMenuItemClick(MenuItem item) {
|
|
return onDetailsClicked(onInvoke, handler, activity);
|
|
}
|
|
});
|
|
detailsMenu.setIcon(R.drawable.ic_menu_view_details);
|
|
}
|
|
|
|
if ((inclusions & INCLUDE_SHOWMAP_MENU) != 0) {
|
|
MenuItem showOnMapItem = menu.add(Menu.NONE, MENU_IMAGE_SHOWMAP,
|
|
POSITION_SHOWMAP, R.string.show_on_map);
|
|
showOnMapItem.setOnMenuItemClickListener(
|
|
new MenuItem.OnMenuItemClickListener() {
|
|
public boolean onMenuItemClick(MenuItem item) {
|
|
return onShowMapClicked(onInvoke,
|
|
handler, activity);
|
|
}
|
|
}).setIcon(R.drawable.ic_menu_3d_globe);
|
|
requiresImageItems.add(showOnMapItem);
|
|
}
|
|
|
|
if ((inclusions & INCLUDE_VIEWPLAY_MENU) != 0) {
|
|
MenuItem videoPlayItem = menu.add(Menu.NONE, Menu.NONE,
|
|
POSITION_VIEWPLAY, R.string.video_play)
|
|
.setOnMenuItemClickListener(
|
|
new MenuItem.OnMenuItemClickListener() {
|
|
public boolean onMenuItemClick(MenuItem item) {
|
|
return onViewPlayClicked(onInvoke, activity);
|
|
}
|
|
});
|
|
videoPlayItem.setIcon(
|
|
com.android.internal.R.drawable.ic_menu_play_clip);
|
|
requiresVideoItems.add(videoPlayItem);
|
|
}
|
|
|
|
return new MenuItemsResult() {
|
|
public void gettingReadyToOpen(Menu menu, IImage image) {
|
|
// protect against null here. this isn't strictly speaking
|
|
// required but if a client app isn't handling sdcard removal
|
|
// properly it could happen
|
|
if (image == null) {
|
|
return;
|
|
}
|
|
|
|
ArrayList<MenuItem> enableList = new ArrayList<MenuItem>();
|
|
ArrayList<MenuItem> disableList = new ArrayList<MenuItem>();
|
|
ArrayList<MenuItem> list;
|
|
|
|
list = image.isReadonly() ? disableList : enableList;
|
|
list.addAll(requiresWriteAccessItems);
|
|
|
|
list = image.isDrm() ? disableList : enableList;
|
|
list.addAll(requiresNoDrmAccessItems);
|
|
|
|
list = ImageManager.isImage(image) ? enableList : disableList;
|
|
list.addAll(requiresImageItems);
|
|
|
|
list = ImageManager.isVideo(image) ? enableList : disableList;
|
|
list.addAll(requiresVideoItems);
|
|
|
|
for (MenuItem item : enableList) {
|
|
item.setVisible(true);
|
|
item.setEnabled(true);
|
|
}
|
|
|
|
for (MenuItem item : disableList) {
|
|
item.setVisible(false);
|
|
item.setEnabled(false);
|
|
}
|
|
}
|
|
|
|
// must override abstract method
|
|
public void aboutToCall(MenuItem menu, IImage image) {
|
|
}
|
|
};
|
|
}
|
|
|
|
static void deletePhoto(Activity activity, Runnable onDelete) {
|
|
deleteImpl(activity, onDelete, true);
|
|
}
|
|
|
|
static void deleteImage(
|
|
Activity activity, Runnable onDelete, IImage image) {
|
|
deleteImpl(activity, onDelete, ImageManager.isImage(image));
|
|
}
|
|
|
|
static void deleteImpl(
|
|
Activity activity, Runnable onDelete, boolean isImage) {
|
|
boolean needConfirm = PreferenceManager
|
|
.getDefaultSharedPreferences(activity)
|
|
.getBoolean("pref_gallery_confirm_delete_key", true);
|
|
if (!needConfirm) {
|
|
if (onDelete != null) onDelete.run();
|
|
} else {
|
|
String title = activity.getString(R.string.confirm_delete_title);
|
|
String message = activity.getString(isImage
|
|
? R.string.confirm_delete_message
|
|
: R.string.confirm_delete_video_message);
|
|
confirmAction(activity, title, message, onDelete);
|
|
}
|
|
}
|
|
|
|
public static void deleteMultiple(Context context, Runnable action) {
|
|
boolean needConfirm = PreferenceManager
|
|
.getDefaultSharedPreferences(context)
|
|
.getBoolean("pref_gallery_confirm_delete_key", true);
|
|
if (!needConfirm) {
|
|
if (action != null) action.run();
|
|
} else {
|
|
String title = context.getString(R.string.confirm_delete_title);
|
|
String message = context.getString(
|
|
R.string.confirm_delete_multiple_message);
|
|
confirmAction(context, title, message, action);
|
|
}
|
|
}
|
|
|
|
public static void confirmAction(Context context, String title,
|
|
String message, final Runnable action) {
|
|
OnClickListener listener = new OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
switch (which) {
|
|
case DialogInterface.BUTTON_POSITIVE:
|
|
if (action != null) action.run();
|
|
}
|
|
}
|
|
};
|
|
new AlertDialog.Builder(context)
|
|
.setIcon(android.R.drawable.ic_dialog_alert)
|
|
.setTitle(title)
|
|
.setMessage(message)
|
|
.setPositiveButton(android.R.string.ok, listener)
|
|
.setNegativeButton(android.R.string.cancel, listener)
|
|
.create()
|
|
.show();
|
|
}
|
|
|
|
static void addCapturePictureMenuItems(Menu menu, final Activity activity) {
|
|
menu.add(Menu.NONE, Menu.NONE, POSITION_CAPTURE_PICTURE,
|
|
R.string.capture_picture)
|
|
.setOnMenuItemClickListener(
|
|
new MenuItem.OnMenuItemClickListener() {
|
|
public boolean onMenuItemClick(MenuItem item) {
|
|
return onCapturePictureClicked(activity);
|
|
}
|
|
}).setIcon(android.R.drawable.ic_menu_camera);
|
|
}
|
|
|
|
private static boolean onCapturePictureClicked(Activity activity) {
|
|
Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
try {
|
|
activity.startActivity(intent);
|
|
} catch (android.content.ActivityNotFoundException e) {
|
|
// Ignore exception
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void addCaptureVideoMenuItems(Menu menu, final Activity activity) {
|
|
menu.add(Menu.NONE, Menu.NONE, POSITION_CAPTURE_VIDEO,
|
|
R.string.capture_video)
|
|
.setOnMenuItemClickListener(
|
|
new MenuItem.OnMenuItemClickListener() {
|
|
public boolean onMenuItemClick(MenuItem item) {
|
|
return onCaptureVideoClicked(activity);
|
|
}
|
|
}).setIcon(R.drawable.ic_menu_camera_video_view);
|
|
}
|
|
|
|
private static boolean onCaptureVideoClicked(Activity activity) {
|
|
Intent intent = new Intent(MediaStore.INTENT_ACTION_VIDEO_CAMERA);
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
try {
|
|
activity.startActivity(intent);
|
|
} catch (android.content.ActivityNotFoundException e) {
|
|
// Ignore exception
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public static void addCaptureMenuItems(Menu menu, final Activity activity) {
|
|
addCapturePictureMenuItems(menu, activity);
|
|
addCaptureVideoMenuItems(menu, activity);
|
|
}
|
|
|
|
public static String formatDuration(final Context context,
|
|
int durationMs) {
|
|
int duration = durationMs / 1000;
|
|
int h = duration / 3600;
|
|
int m = (duration - h * 3600) / 60;
|
|
int s = duration - (h * 3600 + m * 60);
|
|
String durationValue;
|
|
if (h == 0) {
|
|
durationValue = String.format(
|
|
context.getString(R.string.details_ms), m, s);
|
|
} else {
|
|
durationValue = String.format(
|
|
context.getString(R.string.details_hms), h, m, s);
|
|
}
|
|
return durationValue;
|
|
}
|
|
|
|
public static void showStorageToast(Activity activity) {
|
|
showStorageToast(activity, calculatePicturesRemaining());
|
|
}
|
|
|
|
public static void showStorageToast(Activity activity, int remaining) {
|
|
String noStorageText = null;
|
|
|
|
if (remaining == MenuHelper.NO_STORAGE_ERROR) {
|
|
String state = Environment.getExternalStorageState();
|
|
if (state == Environment.MEDIA_CHECKING) {
|
|
noStorageText = activity.getString(R.string.preparing_sd);
|
|
} else {
|
|
noStorageText = activity.getString(R.string.no_storage);
|
|
}
|
|
} else if (remaining < 1) {
|
|
noStorageText = activity.getString(R.string.not_enough_space);
|
|
}
|
|
|
|
if (noStorageText != null) {
|
|
Toast.makeText(activity, noStorageText, 5000).show();
|
|
}
|
|
}
|
|
|
|
public static int calculatePicturesRemaining() {
|
|
try {
|
|
if (!ImageManager.hasStorage()) {
|
|
return NO_STORAGE_ERROR;
|
|
} else {
|
|
String storageDirectory =
|
|
Environment.getExternalStorageDirectory().toString();
|
|
StatFs stat = new StatFs(storageDirectory);
|
|
float remaining = ((float) stat.getAvailableBlocks()
|
|
* (float) stat.getBlockSize()) / 400000F;
|
|
return (int) remaining;
|
|
}
|
|
} catch (Exception ex) {
|
|
// if we can't stat the filesystem then we don't know how many
|
|
// pictures are remaining. it might be zero but just leave it
|
|
// blank since we really don't know.
|
|
return CANNOT_STAT_ERROR;
|
|
}
|
|
}
|
|
}
|