package net.osmand.plus.audionotes;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.AssetFileDescriptor;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.PictureCallback;
import android.media.AudioManager;
import android.media.CamcorderProfile;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnPreparedListener;
import android.media.MediaRecorder;
import android.media.SoundPool;
import android.net.Uri;
import android.os.Build;
import android.os.StatFs;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AlertDialog;
import android.view.Display;
import android.view.KeyEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import net.osmand.AndroidUtils;
import net.osmand.IProgress;
import net.osmand.IndexConstants;
import net.osmand.Location;
import net.osmand.PlatformUtil;
import net.osmand.data.DataTileManager;
import net.osmand.data.LatLon;
import net.osmand.data.PointDescription;
import net.osmand.plus.ApplicationMode;
import net.osmand.plus.ContextMenuAdapter;
import net.osmand.plus.ContextMenuAdapter.ItemClickListener;
import net.osmand.plus.ContextMenuItem;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.OsmandSettings;
import net.osmand.plus.OsmandSettings.CommonPreference;
import net.osmand.plus.OsmandSettings.OsmandPreference;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.activities.SavingTrackHelper;
import net.osmand.plus.activities.TabActivity.TabItem;
import net.osmand.plus.dashboard.tools.DashFragmentData;
import net.osmand.plus.helpers.AndroidUiHelper;
import net.osmand.plus.mapcontextmenu.MapContextMenu;
import net.osmand.plus.monitoring.OsmandMonitoringPlugin;
import net.osmand.plus.myplaces.FavoritesActivity;
import net.osmand.plus.views.MapInfoLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.mapwidgets.TextInfoWidget;
import net.osmand.util.Algorithms;
import net.osmand.util.GeoPointParserUtil.GeoParsedPoint;
import net.osmand.util.MapUtils;
import org.apache.commons.logging.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
public class AudioVideoNotesPlugin extends OsmandPlugin {
public static final int NOTES_TAB = R.string.notes;
public static final String ID = "osmand.audionotes";
public static final String THREEGP_EXTENSION = "3gp";
public static final String MPEG4_EXTENSION = "mp4";
public static final String IMG_EXTENSION = "jpg";
private static final Log log = PlatformUtil.getLog(AudioVideoNotesPlugin.class);
public static final int CAMERA_FOR_VIDEO_REQUEST_CODE = 101;
public static final int CAMERA_FOR_PHOTO_REQUEST_CODE = 102;
public static final int AUDIO_REQUEST_CODE = 103;
private static Method mRegisterMediaButtonEventReceiver;
private static Method mUnregisterMediaButtonEventReceiver;
private OsmandApplication app;
private TextInfoWidget recordControl;
public final CommonPreference<Boolean> AV_EXTERNAL_RECORDER;
public final CommonPreference<Boolean> AV_EXTERNAL_PHOTO_CAM;
public final CommonPreference<Boolean> AV_PHOTO_PLAY_SOUND;
public static final int VIDEO_OUTPUT_MP4 = 0;
public static final int VIDEO_OUTPUT_3GP = 1;
public static final int VIDEO_QUALITY_DEFAULT = CamcorderProfile.QUALITY_HIGH; // High (highest res)
public static final int AUDIO_FORMAT_DEFAULT = MediaRecorder.AudioEncoder.AAC; // AAC
public static final int AUDIO_BITRATE_DEFAULT = 64 * 1024; // 64 kbps
public final CommonPreference<Integer> AV_VIDEO_FORMAT;
public final CommonPreference<Integer> AV_VIDEO_QUALITY;
public final CommonPreference<Integer> AV_AUDIO_FORMAT;
public final CommonPreference<Integer> AV_AUDIO_BITRATE;
public static final int AV_DEFAULT_ACTION_AUDIO = 0;
public static final int AV_DEFAULT_ACTION_VIDEO = 1;
public static final int AV_DEFAULT_ACTION_TAKEPICTURE = 2;
public static final int AV_DEFAULT_ACTION_CHOOSE = -1;
// camera picture size:
public static final int AV_PHOTO_SIZE_DEFAULT = -1;
public static int cameraPictureSizeDefault = 0;
// camera focus type
public static final int AV_CAMERA_FOCUS_AUTO = 0;
public static final int AV_CAMERA_FOCUS_HIPERFOCAL = 1;
public static final int AV_CAMERA_FOCUS_EDOF = 2;
public static final int AV_CAMERA_FOCUS_INFINITY = 3;
public static final int AV_CAMERA_FOCUS_MACRO = 4;
public static final int AV_CAMERA_FOCUS_CONTINUOUS = 5;
// photo shot:
private static int shotId = 0;
private SoundPool sp = null;
public static final int FULL_SCEEN_RESULT_DELAY_MS = 3000;
public final CommonPreference<Integer> AV_CAMERA_PICTURE_SIZE;
public final CommonPreference<Integer> AV_CAMERA_FOCUS_TYPE;
public final CommonPreference<Integer> AV_DEFAULT_ACTION;
public final OsmandPreference<Boolean> SHOW_RECORDINGS;
public static final int CLIP_LENGTH_DEFAULT = 5;
public static final int STORAGE_SIZE_DEFAULT = 5;
public final CommonPreference<Boolean> AV_RECORDER_SPLIT;
public final CommonPreference<Integer> AV_RS_CLIP_LENGTH;
public final CommonPreference<Integer> AV_RS_STORAGE_SIZE;
private DataTileManager<Recording> recordings = new DataTileManager<AudioVideoNotesPlugin.Recording>(14);
private Map<String, Recording> recordingByFileName = new LinkedHashMap<>();
private AudioNotesLayer audioNotesLayer;
private MapActivity mapActivity;
private static File mediaRecFile;
private static MediaRecorder mediaRec;
private File lastTakingPhoto;
private byte[] photoJpegData;
private Timer photoTimer;
private Camera cam;
private List<Camera.Size> mSupportedPreviewSizes;
private int requestedOrientation;
private boolean autofocus;
private AudioVideoNoteRecordingMenu recordingMenu;
private CurrentRecording currentRecording;
private boolean recordingDone;
private MediaPlayer player;
private Recording recordingPlaying;
private Timer playerTimer;
private final static char SPLIT_DESC = ' ';
private double actionLat;
private double actionLon;
private int runAction = -1;
public enum AVActionType {
REC_AUDIO,
REC_VIDEO,
REC_PHOTO
}
public static class CurrentRecording {
private AVActionType type;
public CurrentRecording(AVActionType type) {
this.type = type;
}
public AVActionType getType() {
return type;
}
}
public static class Recording {
public Recording(File f) {
this.file = f;
}
private File file;
private double lat;
private double lon;
private long duration = -1;
private boolean available = true;
public double getLatitude() {
return lat;
}
public double getLongitude() {
return lon;
}
private void updateInternalDescription() {
if (duration == -1) {
duration = 0;
if (!isPhoto()) {
MediaPlayer mediaPlayer = new MediaPlayer();
try {
mediaPlayer.setDataSource(file.getAbsolutePath());
mediaPlayer.prepare();
duration = mediaPlayer.getDuration();
available = true;
} catch (Exception e) {
log.error("Error reading recording " + file.getAbsolutePath(), e);
available = false;
}
}
}
}
public File getFile() {
return file;
}
public boolean setName(String name) {
File directory = file.getParentFile();
String fileName = getFileName();
File to = new File(directory, name + SPLIT_DESC + getOtherName(fileName));
if (file.renameTo(to)) {
file = to;
return true;
}
return false;
}
public boolean setLocation(LatLon latLon) {
File directory = file.getParentFile();
lat = latLon.getLatitude();
lon = latLon.getLongitude();
File to = getBaseFileName(lat, lon, directory, Algorithms.getFileExtension(file));
if (file.renameTo(to)) {
file = to;
return true;
}
return false;
}
public String getFileName() {
return file.getName();
}
public String getDescriptionName(String fileName) {
int hashInd = fileName.lastIndexOf(SPLIT_DESC);
//backward compatibility
if (fileName.indexOf('.') - fileName.indexOf('_') > 12 &&
hashInd < fileName.indexOf('_')) {
hashInd = fileName.indexOf('_');
}
if (hashInd == -1) {
return null;
} else {
return fileName.substring(0, hashInd);
}
}
public String getOtherName(String fileName) {
String descriptionName = getDescriptionName(fileName);
if (descriptionName != null) {
return fileName.substring(descriptionName.length() + 1); // SPLIT_DESC
} else {
return fileName;
}
}
private String formatDateTime(Context ctx, long dateTime) {
DateFormat dateFormat = android.text.format.DateFormat.getMediumDateFormat(ctx);
DateFormat timeFormat = android.text.format.DateFormat.getTimeFormat(ctx);
return dateFormat.format(dateTime) + " " + timeFormat.format(dateTime);
}
public String getName(Context ctx, boolean includingType) {
String fileName = file.getName();
String desc = getDescriptionName(fileName);
if (desc != null) {
return desc;
} else if (this.isAudio() || this.isVideo() || this.isPhoto()) {
if (includingType) {
return getType(ctx) + " " + formatDateTime(ctx, file.lastModified());
} else {
return formatDateTime(ctx, file.lastModified());
}
}
return "";
}
public String getType(Context ctx) {
if (this.isAudio()) {
return ctx.getResources().getString(R.string.shared_string_audio);
} else if (this.isVideo()) {
return ctx.getResources().getString(R.string.shared_string_video);
} else if (this.isPhoto()) {
return ctx.getResources().getString(R.string.shared_string_photo);
} else {
return "";
}
}
public String getSearchHistoryType() {
if (isPhoto()) {
return PointDescription.POINT_TYPE_PHOTO_NOTE;
} else if (isVideo()) {
return PointDescription.POINT_TYPE_VIDEO_NOTE;
} else {
return PointDescription.POINT_TYPE_PHOTO_NOTE;
}
}
public boolean isPhoto() {
return file.getName().endsWith(IMG_EXTENSION);
}
public boolean isVideo() {
return file.getName().endsWith(MPEG4_EXTENSION);// || file.getName().endsWith(THREEGP_EXTENSION);
}
public boolean isAudio() {
return file.getName().endsWith(THREEGP_EXTENSION);
}
private String convertDegToExifRational(double l) {
if (l < 0) {
l = -l;
}
String s = ((int) l) + "/1,"; // degrees
l = (l - ((int) l)) * 60.0;
s += (int) l + "/1,"; // minutes
l = (l - ((int) l)) * 60000.0;
s += (int) l + "/1000"; // seconds
// log.info("deg rational: " + s);
return s;
}
@SuppressWarnings("rawtypes")
public void updatePhotoInformation(double lat, double lon, Location loc, double rot) throws IOException {
try {
Class exClass = Class.forName("android.media.ExifInterface");
Constructor c = exClass.getConstructor(new Class[]{String.class});
Object exInstance = c.newInstance(file.getAbsolutePath());
Method setAttribute = exClass.getMethod("setAttribute", new Class[]{String.class, String.class});
setAttribute.invoke(exInstance, "GPSLatitude", convertDegToExifRational(lat));
setAttribute.invoke(exInstance, "GPSLatitudeRef", lat > 0 ? "N" : "S");
setAttribute.invoke(exInstance, "GPSLongitude", convertDegToExifRational(lon));
setAttribute.invoke(exInstance, "GPSLongitudeRef", lon > 0 ? "E" : "W");
if (!Double.isNaN(rot)) {
setAttribute.invoke(exInstance, "GPSImgDirectionRef", "T");
while (rot < 0) {
rot += 360;
}
while (rot > 360) {
rot -= 360;
}
int abs = (int) (Math.abs(rot) * 100.0);
String rotString = abs / 100f + "";
setAttribute.invoke(exInstance, "GPSImgDirection", rotString);
}
if (loc != null && loc.hasAltitude()) {
double alt = loc.getAltitude();
String altString = (int) (Math.abs(alt) * 100.0) + "/100";
setAttribute.invoke(exInstance, "GPSAltitude", altString);
setAttribute.invoke(exInstance, "GPSAltitudeRef", alt < 0 ? "1" : "0");
}
Method saveAttributes = exClass.getMethod("saveAttributes", new Class[]{});
saveAttributes.invoke(exInstance);
} catch (Exception e) {
e.printStackTrace();
log.error(e);
}
}
@SuppressWarnings("rawtypes")
private int getExifOrientation() {
int orientation = 0;
try {
Class exClass = Class.forName("android.media.ExifInterface");
Constructor c = exClass.getConstructor(new Class[]{String.class});
Object exInstance = c.newInstance(file.getAbsolutePath());
Method getAttributeInt = exClass.getMethod("getAttributeInt", new Class[]{String.class, Integer.TYPE});
Integer it = (Integer) getAttributeInt.invoke(exInstance, "Orientation", 1);
orientation = it;
} catch (Exception e) {
e.printStackTrace();
log.error(e);
}
return orientation;
}
public int getBitmapRotation() {
int rotation = 0;
switch (getExifOrientation()) {
case 3:
rotation = 180;
break;
case 6:
rotation = 90;
break;
case 8:
rotation = 270;
break;
}
return rotation;
}
public String getDescription(Context ctx) {
String time = AndroidUtils.formatDateTime(ctx, file.lastModified());
if (isPhoto()) {
return ctx.getString(R.string.recording_photo_description, "", time).trim();
}
updateInternalDescription();
return ctx.getString(R.string.recording_description, "", getDuration(ctx), time)
.trim();
}
public String getSmallDescription(Context ctx) {
String time = AndroidUtils.formatDateTime(ctx, file.lastModified());
if (isPhoto()) {
return time;
}
updateInternalDescription();
return time + " " + getDuration(ctx);
}
public String getPlainDuration(boolean accessibilityEnabled) {
updateInternalDescription();
if (duration > 0) {
int d = (int) (duration / 1000);
return Algorithms.formatDuration(d, accessibilityEnabled);
} else {
return "";
}
}
private String getDuration(Context ctx) {
String additional = "";
if (duration > 0) {
int d = (int) (duration / 1000);
additional += "(" + Algorithms.formatDuration(d, ((OsmandApplication)ctx.getApplicationContext()).accessibilityEnabled()) + ")";
}
if (!available) {
additional += "[" + ctx.getString(R.string.recording_unavailable) + "]";
}
return additional;
}
}
private static void initializeRemoteControlRegistrationMethods() {
try {
// API 8
if (mRegisterMediaButtonEventReceiver == null) {
mRegisterMediaButtonEventReceiver = AudioManager.class.getMethod("registerMediaButtonEventReceiver",
new Class[]{ComponentName.class});
}
if (mUnregisterMediaButtonEventReceiver == null) {
mUnregisterMediaButtonEventReceiver = AudioManager.class.getMethod("unregisterMediaButtonEventReceiver",
new Class[]{ComponentName.class});
}
/* success, this device will take advantage of better remote */
/* control event handling */
} catch (NoSuchMethodException nsme) {
/* failure, still using the legacy behavior, but this app */
/* is future-proof! */
}
}
@Override
public String getId() {
return ID;
}
public AudioVideoNotesPlugin(OsmandApplication app) {
this.app = app;
OsmandSettings settings = app.getSettings();
ApplicationMode.regWidgetVisibility("audionotes", (ApplicationMode[]) null);
AV_EXTERNAL_RECORDER = settings.registerBooleanPreference("av_external_recorder", false).makeGlobal();
AV_EXTERNAL_PHOTO_CAM = settings.registerBooleanPreference("av_external_cam", true).makeGlobal();
AV_VIDEO_FORMAT = settings.registerIntPreference("av_video_format", VIDEO_OUTPUT_MP4).makeGlobal();
AV_VIDEO_QUALITY = settings.registerIntPreference("av_video_quality", VIDEO_QUALITY_DEFAULT).makeGlobal();
AV_AUDIO_FORMAT = settings.registerIntPreference("av_audio_format", AUDIO_FORMAT_DEFAULT).makeGlobal();
AV_AUDIO_BITRATE = settings.registerIntPreference("av_audio_bitrate", AUDIO_BITRATE_DEFAULT).makeGlobal();
AV_DEFAULT_ACTION = settings.registerIntPreference("av_default_action", AV_DEFAULT_ACTION_CHOOSE).makeGlobal();
// camera picture size:
AV_CAMERA_PICTURE_SIZE = settings.registerIntPreference("av_camera_picture_size", AV_PHOTO_SIZE_DEFAULT).makeGlobal();
// camera focus type:
AV_CAMERA_FOCUS_TYPE = settings.registerIntPreference("av_camera_focus_type", AV_CAMERA_FOCUS_AUTO).makeGlobal();
// camera sound:
AV_PHOTO_PLAY_SOUND = settings.registerBooleanPreference("av_photo_play_sound", true).makeGlobal();
SHOW_RECORDINGS = settings.registerBooleanPreference("show_recordings", true).makeGlobal();
AV_RECORDER_SPLIT = settings.registerBooleanPreference("av_recorder_split", false).makeGlobal();
AV_RS_CLIP_LENGTH = settings.registerIntPreference("av_rs_clip_length", CLIP_LENGTH_DEFAULT).makeGlobal();
AV_RS_STORAGE_SIZE = settings.registerIntPreference("av_rs_storage_size", STORAGE_SIZE_DEFAULT).makeGlobal();
}
@Override
public String getDescription() {
return app.getString(R.string.audionotes_plugin_description);
}
@Override
public String getName() {
return app.getString(R.string.audionotes_plugin_name);
}
@Override
public String getHelpFileName() {
return "feature_articles/audio-video-notes-plugin.html";
}
@Override
public boolean init(final OsmandApplication app, Activity activity) {
initializeRemoteControlRegistrationMethods();
AudioManager am = (AudioManager) app.getSystemService(Context.AUDIO_SERVICE);
if (am != null) {
registerMediaListener(am);
}
return true;
}
@Override
public void registerLayers(MapActivity activity) {
this.mapActivity = activity;
if (audioNotesLayer != null) {
activity.getMapView().removeLayer(audioNotesLayer);
}
audioNotesLayer = new AudioNotesLayer(activity, this);
activity.getMapView().addLayer(audioNotesLayer, 3.5f);
registerWidget(activity);
}
public CurrentRecording getCurrentRecording() {
return currentRecording;
}
private void registerMediaListener(AudioManager am) {
ComponentName receiver = new ComponentName(app.getPackageName(), MediaRemoteControlReceiver.class.getName());
try {
if (mRegisterMediaButtonEventReceiver == null) {
return;
}
mRegisterMediaButtonEventReceiver.invoke(am, receiver);
} catch (Exception ite) {
log.error(ite.getMessage(), ite);
}
}
private void unregisterMediaListener(AudioManager am) {
ComponentName receiver = new ComponentName(app.getPackageName(), MediaRemoteControlReceiver.class.getName());
try {
if (mUnregisterMediaButtonEventReceiver == null) {
return;
}
mUnregisterMediaButtonEventReceiver.invoke(am, receiver);
} catch (Exception ite) {
log.error(ite.getMessage(), ite);
}
}
@Override
public void registerLayerContextMenuActions(final OsmandMapTileView mapView, ContextMenuAdapter adapter, final MapActivity mapActivity) {
ItemClickListener listener = new ContextMenuAdapter.ItemClickListener() {
@Override
public boolean onContextMenuClick(ArrayAdapter<ContextMenuItem> adapter, int itemId, int pos, boolean isChecked) {
if (itemId == R.string.layer_recordings) {
SHOW_RECORDINGS.set(!SHOW_RECORDINGS.get());
adapter.getItem(pos).setColorRes(SHOW_RECORDINGS.get() ?
R.color.osmand_orange : ContextMenuItem.INVALID_ID);
adapter.notifyDataSetChanged();
updateLayers(mapView, mapActivity);
}
return true;
}
};
adapter.addItem(new ContextMenuItem.ItemBuilder().setTitleId(R.string.layer_recordings, app)
.setSelected(SHOW_RECORDINGS.get())
.setIcon(R.drawable.ic_action_micro_dark)
.setColor(SHOW_RECORDINGS.get() ? R.color.osmand_orange : ContextMenuItem.INVALID_ID)
.setPosition(12)
.setListener(listener).createItem());
}
@Override
public void registerMapContextMenuActions(final MapActivity mapActivity, final double latitude, final double longitude,
ContextMenuAdapter adapter, Object selectedObj) {
if (isRecording()) {
return;
}
adapter.addItem(new ContextMenuItem.ItemBuilder().setTitleId(R.string.recording_context_menu_arecord, app)
.setIcon(R.drawable.ic_action_micro_dark)
.setListener(new ContextMenuAdapter.ItemClickListener() {
@Override
public boolean onContextMenuClick(ArrayAdapter<ContextMenuItem> adapter, int itemId, int pos, boolean isChecked) {
recordAudio(latitude, longitude, mapActivity);
return true;
}
})
.createItem());
adapter.addItem(new ContextMenuItem.ItemBuilder().setTitleId(R.string.recording_context_menu_vrecord, app)
.setIcon(R.drawable.ic_action_video_dark)
.setListener(new ItemClickListener() {
@Override
public boolean onContextMenuClick(ArrayAdapter<ContextMenuItem> adapter, int itemId, int pos, boolean isChecked) {
recordVideo(latitude, longitude, mapActivity);
return true;
}
})
.createItem());
adapter.addItem(new ContextMenuItem.ItemBuilder().setTitleId(R.string.recording_context_menu_precord, app)
.setIcon(R.drawable.ic_action_photo_dark)
.setListener(new ItemClickListener() {
@Override
public boolean onContextMenuClick(ArrayAdapter<ContextMenuItem> adapter, int itemId, int pos, boolean isChecked) {
takePhoto(latitude, longitude, mapActivity, false);
return true;
}
})
.createItem());
}
@Override
public void updateLayers(OsmandMapTileView mapView, MapActivity activity) {
if (isActive()) {
if (SHOW_RECORDINGS.get()) {
if (audioNotesLayer == null) {
registerLayers(activity);
} else if (!mapView.getLayers().contains(audioNotesLayer)) {
mapView.addLayer(audioNotesLayer, 3.5f);
}
} else if (audioNotesLayer != null) {
mapView.removeLayer(audioNotesLayer);
}
if (recordControl == null) {
registerWidget(activity);
}
} else {
if (audioNotesLayer != null) {
mapView.removeLayer(audioNotesLayer);
audioNotesLayer = null;
}
MapInfoLayer mapInfoLayer = activity.getMapLayers().getMapInfoLayer();
if (recordControl != null && mapInfoLayer != null) {
mapInfoLayer.removeSideWidget(recordControl);
recordControl = null;
mapInfoLayer.recreateControls();
}
recordControl = null;
}
}
private void registerWidget(MapActivity activity) {
MapInfoLayer mapInfoLayer = activity.getMapLayers().getMapInfoLayer();
if (mapInfoLayer != null) {
recordControl = new TextInfoWidget(activity);
if (mediaRec != null && mediaRecFile != null) {
updateRecordControl(activity, mediaRecFile);
} else {
recordControl.setImageDrawable(activity.getResources().getDrawable(R.drawable.monitoring_rec_inactive));
setRecordListener(recordControl, activity);
}
mapInfoLayer.registerSideWidget(recordControl, R.drawable.ic_action_micro_dark,
R.string.map_widget_av_notes, "audionotes", false, 32);
mapInfoLayer.recreateControls();
}
}
private void setRecordListener(final TextInfoWidget recordPlaceControl, final MapActivity mapActivity) {
recordPlaceControl.setText(app.getString(R.string.shared_string_control_start), "");
updateWidgetIcon(recordPlaceControl);
recordPlaceControl.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
defaultAction(mapActivity);
}
});
}
private void updateWidgetIcon(final TextInfoWidget recordPlaceControl) {
recordPlaceControl.setIcons(R.drawable.widget_icon_av_inactive_day,
R.drawable.widget_icon_av_inactive_night);
if (AV_DEFAULT_ACTION.get() == AV_DEFAULT_ACTION_VIDEO) {
recordPlaceControl.setIcons(R.drawable.widget_av_video_day,
R.drawable.widget_av_video_night);
} else if (AV_DEFAULT_ACTION.get() == AV_DEFAULT_ACTION_TAKEPICTURE) {
recordPlaceControl.setIcons(R.drawable.widget_av_photo_day,
R.drawable.widget_av_photo_night);
} else if (AV_DEFAULT_ACTION.get() == AV_DEFAULT_ACTION_AUDIO) {
recordPlaceControl.setIcons(R.drawable.widget_av_audio_day,
R.drawable.widget_av_audio_night);
}
}
public void defaultAction(final MapActivity mapActivity) {
final Location loc = app.getLocationProvider().getLastKnownLocation();
// double lat = mapActivity.getMapView().getLatitude();
// double lon = mapActivity.getMapView().getLongitude();
if (loc == null) {
Toast.makeText(app, R.string.audionotes_location_not_defined, Toast.LENGTH_LONG).show();
return;
}
double lon = loc.getLongitude();
double lat = loc.getLatitude();
int action = AV_DEFAULT_ACTION.get();
if (action == AV_DEFAULT_ACTION_CHOOSE) {
chooseDefaultAction(lat, lon, mapActivity);
} else {
takeAction(mapActivity, lon, lat, action);
}
}
private void chooseDefaultAction(final double lat, final double lon, final MapActivity mapActivity) {
AlertDialog.Builder ab = new AlertDialog.Builder(mapActivity);
ab.setItems(
new String[]{mapActivity.getString(R.string.recording_context_menu_arecord),
mapActivity.getString(R.string.recording_context_menu_vrecord),
mapActivity.getString(R.string.recording_context_menu_precord),}, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
int action = which == 0 ? AV_DEFAULT_ACTION_AUDIO : (which == 1 ? AV_DEFAULT_ACTION_VIDEO
: AV_DEFAULT_ACTION_TAKEPICTURE);
takeAction(mapActivity, lon, lat, action);
}
});
ab.show();
}
private void takeAction(final MapActivity mapActivity, double lon, double lat, int action) {
if (action == AV_DEFAULT_ACTION_VIDEO) {
recordVideo(lat, lon, mapActivity);
} else if (action == AV_DEFAULT_ACTION_TAKEPICTURE) {
takePhoto(lat, lon, mapActivity, false);
} else if (action == AV_DEFAULT_ACTION_AUDIO) {
recordAudio(lat, lon, mapActivity);
}
}
private static File getBaseFileName(double lat, double lon, OsmandApplication app, String ext) {
File baseDir = app.getAppPath(IndexConstants.AV_INDEX_DIR);
return getBaseFileName(lat, lon, baseDir, ext);
}
private static File getBaseFileName(double lat, double lon, @NonNull File baseDir, @NonNull String ext) {
String basename = MapUtils.createShortLinkString(lat, lon, 15);
int k = 1;
baseDir.mkdirs();
File fl;
do {
fl = new File(baseDir, basename + "." + (k++) + "." + ext);
} while (fl.exists());
return fl;
}
public void captureImage(double lat, double lon, final MapActivity mapActivity) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
Uri fileUri = Uri.fromFile(getBaseFileName(lat, lon, app, IMG_EXTENSION));
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // set the image file name
// start the image capture Intent
mapActivity.startActivityForResult(intent, 105);
}
public void captureVideoExternal(double lat, double lon, final MapActivity mapActivity) {
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
String ext = MPEG4_EXTENSION;
// if (AV_VIDEO_FORMAT.get() == VIDEO_OUTPUT_3GP) {
// ext = THREEGP_EXTENSION;
// }
Uri fileUri = Uri.fromFile(getBaseFileName(lat, lon, app, ext));
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // set the image file name
intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1); // set the video image quality to high
// start the video capture Intent
mapActivity.startActivityForResult(intent, 205);
}
@Override
public void mapActivityScreenOff(MapActivity activity) {
stopRecording(activity, false);
}
@Override
public void mapActivityResume(MapActivity activity) {
this.mapActivity = activity;
((AudioManager) activity.getSystemService(Context.AUDIO_SERVICE)).registerMediaButtonEventReceiver(
new ComponentName(activity, MediaRemoteControlReceiver.class));
if (runAction != -1) {
takeAction(activity, actionLon, actionLat, runAction);
runAction = -1;
}
}
@Override
public void mapActivityPause(MapActivity activity) {
if (isRecording()) {
if (currentRecording.getType() == AVActionType.REC_PHOTO) {
finishPhotoRecording(false);
} else {
activity.getContextMenu().close();
if (currentRecording.getType() == AVActionType.REC_VIDEO && AV_RECORDER_SPLIT.get()) {
runAction = AV_DEFAULT_ACTION_VIDEO;
LatLon latLon = getNextRecordingLocation();
actionLat = latLon.getLatitude();
actionLon = latLon.getLongitude();
}
stopRecording(activity, false);
}
finishRecording();
}
this.mapActivity = null;
}
public MapActivity getMapActivity() {
return mapActivity;
}
public boolean isRecording() {
return currentRecording != null;
}
private void initRecMenu(AVActionType actionType, double lat, double lon) {
currentRecording = new CurrentRecording(actionType);
if (actionType == AVActionType.REC_PHOTO) {
recordingMenu = new AudioVideoNoteRecordingMenuFullScreen(this, lat, lon);
} else {
recordingMenu = new AudioVideoNoteRecordingMenu(this, lat, lon);
}
recordingDone = false;
lockScreenOrientation();
}
public void recordVideo(final double lat, final double lon, final MapActivity mapActivity) {
if (AV_EXTERNAL_RECORDER.get()) {
captureVideoExternal(lat, lon, mapActivity);
} else {
if (ActivityCompat.checkSelfPermission(mapActivity, Manifest.permission.CAMERA)
== PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(mapActivity, Manifest.permission.RECORD_AUDIO)
== PackageManager.PERMISSION_GRANTED) {
openCamera();
if (cam != null) {
initRecMenu(AVActionType.REC_VIDEO, lat, lon);
recordVideoCamera(lat, lon, mapActivity);
}
} else {
actionLat = lat;
actionLon = lon;
ActivityCompat.requestPermissions(mapActivity,
new String[]{Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO},
CAMERA_FOR_VIDEO_REQUEST_CODE);
}
}
}
public void recordVideoCamera(final double lat, final double lon, final MapActivity mapActivity) {
final CamcorderProfile p = CamcorderProfile.get(AV_VIDEO_QUALITY.get());
final Camera.Size mPreviewSize = getPreviewSize();
final SurfaceView view;
if (mPreviewSize != null) {
view = recordingMenu.prepareSurfaceView(mPreviewSize.width, mPreviewSize.height);
} else {
view = recordingMenu.prepareSurfaceView();
}
view.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
view.getHolder().addCallback(new Callback() {
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
MediaRecorder mr = new MediaRecorder();
try {
startCamera(mPreviewSize, holder);
cam.unlock();
mr.setCamera(cam);
} catch (Exception e) {
logErr(e);
closeRecordingMenu();
closeCamera();
finishRecording();
return;
}
final File f = getBaseFileName(lat, lon, app, MPEG4_EXTENSION);
initMediaRecorder(mr, p, f);
try {
if (AV_RECORDER_SPLIT.get()) {
cleanupSpace();
}
runMediaRecorder(mapActivity, mr, f);
} catch (Exception e) {
logErr(e);
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
});
recordingMenu.show();
}
private void initMediaRecorder(MediaRecorder mr, CamcorderProfile p, File f) {
mr.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
mr.setVideoSource(MediaRecorder.VideoSource.CAMERA);
giveMediaRecorderHintRotatedScreen(mapActivity, mr);
//mr.setPreviewDisplay(holder.getSurface());
mr.setProfile(p);
mr.setOutputFile(f.getAbsolutePath());
}
private void giveMediaRecorderHintRotatedScreen(final MapActivity mapActivity, final MediaRecorder mr) {
if (Build.VERSION.SDK_INT >= 9) {
try {
Method m = mr.getClass().getDeclaredMethod("setOrientationHint", Integer.TYPE);
Display display = ((WindowManager) mapActivity.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
if (display.getOrientation() == Surface.ROTATION_0) {
m.invoke(mr, 90);
} else if (display.getOrientation() == Surface.ROTATION_270) {
m.invoke(mr, 180);
} else if (display.getOrientation() == Surface.ROTATION_180) {
m.invoke(mr, 270);
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
private void logErr(Exception e) {
log.error("Error starting recorder ", e);
Toast.makeText(app, app.getString(R.string.recording_error) + " : " + e.getMessage(), Toast.LENGTH_LONG).show();
}
protected void openCamera() {
if (cam != null) {
try {
cam.release();
cam = null;
} catch (Exception e) {
logErr(e);
}
}
try {
cam = Camera.open();
if (mSupportedPreviewSizes == null) {
mSupportedPreviewSizes = cam.getParameters().getSupportedPreviewSizes();
}
} catch (Exception e) {
logErr(e);
}
}
protected void closeCamera() {
if (cam != null) {
try {
cam.release();
} catch (Exception e) {
logErr(e);
}
cam = null;
}
}
private void lockScreenOrientation() {
requestedOrientation = mapActivity.getRequestedOrientation();
mapActivity.setRequestedOrientation(AndroidUiHelper.getScreenOrientation(mapActivity));
}
private void restoreScreenOrientation() {
mapActivity.setRequestedOrientation(requestedOrientation);
}
private Camera.Size getPreviewSize() {
final CamcorderProfile p = CamcorderProfile.get(AV_VIDEO_QUALITY.get());
final Camera.Size mPreviewSize;
if (mSupportedPreviewSizes != null) {
int width;
int height;
if (recordingMenu.isLandscapeLayout()) {
width = p.videoFrameWidth;
height = p.videoFrameHeight;
} else {
height = p.videoFrameWidth;
width = p.videoFrameHeight;
}
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
} else {
mPreviewSize = null;
}
return mPreviewSize;
}
protected void startCamera(Camera.Size mPreviewSize, SurfaceHolder holder) throws IOException {
Parameters parameters = cam.getParameters();
// camera focus type
List<String> sfm = parameters.getSupportedFocusModes();
if (sfm.contains("continuous-video")) {
parameters.setFocusMode("continuous-video");
}
int cameraOrientation = getCamOrientation(mapActivity, Camera.CameraInfo.CAMERA_FACING_BACK);
cam.setDisplayOrientation(cameraOrientation);
parameters.set("rotation", cameraOrientation);
if (mPreviewSize != null) {
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
}
cam.setParameters(parameters);
if (holder != null) {
cam.setPreviewDisplay(holder);
}
cam.startPreview();
}
protected void stopCamera() {
try {
if (cam != null) {
cam.cancelAutoFocus();
cam.stopPreview();
cam.setPreviewDisplay(null);
}
} catch (Exception e) {
logErr(e);
} finally {
closeCamera();
}
}
private boolean stopCameraRecording(boolean restart) {
boolean res = true;
if (mediaRec != null) {
mediaRec.stop();
AVActionType type = currentRecording.type;
indexFile(true, mediaRecFile);
mediaRec.release();
mediaRec = null;
mediaRecFile = null;
if (restart) {
try {
cam.lock();
if (AV_RECORDER_SPLIT.get()) {
cleanupSpace();
}
currentRecording = new CurrentRecording(type);
MediaRecorder mr = new MediaRecorder();
LatLon latLon = getNextRecordingLocation();
final File f = getBaseFileName(latLon.getLatitude(), latLon.getLongitude(), app, MPEG4_EXTENSION);
cam.unlock();
mr.setCamera(cam);
initMediaRecorder(mr, CamcorderProfile.get(AV_VIDEO_QUALITY.get()), f);
mr.prepare();
mr.start();
mediaRec = mr;
mediaRecFile = f;
} catch (Exception e) {
Toast.makeText(app, e.getMessage(), Toast.LENGTH_LONG).show();
e.printStackTrace();
res = false;
}
}
}
return res;
}
public void recordAudio(double lat, double lon, final MapActivity mapActivity) {
if (ActivityCompat.checkSelfPermission(mapActivity, Manifest.permission.RECORD_AUDIO)
== PackageManager.PERMISSION_GRANTED) {
initRecMenu(AVActionType.REC_AUDIO, lat, lon);
MediaRecorder mr = new MediaRecorder();
final File f = getBaseFileName(lat, lon, app, THREEGP_EXTENSION);
mr.setAudioSource(MediaRecorder.AudioSource.MIC);
mr.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mr.setAudioEncoder(AV_AUDIO_FORMAT.get());
mr.setAudioEncodingBitRate(AV_AUDIO_BITRATE.get());
mr.setOutputFile(f.getAbsolutePath());
try {
runMediaRecorder(mapActivity, mr, f);
} catch (Exception e) {
log.error("Error starting audio recorder ", e);
Toast.makeText(app, app.getString(R.string.recording_error) + " : "
+ e.getMessage(), Toast.LENGTH_LONG).show();
}
} else {
actionLat = lat;
actionLon = lon;
ActivityCompat.requestPermissions(mapActivity,
new String[]{Manifest.permission.RECORD_AUDIO},
AUDIO_REQUEST_CODE);
}
}
public void takePhoto(final double lat, final double lon, final MapActivity mapActivity,
final boolean forceInternal) {
if (ActivityCompat.checkSelfPermission(mapActivity, Manifest.permission.CAMERA)
== PackageManager.PERMISSION_GRANTED) {
if (!AV_EXTERNAL_PHOTO_CAM.get() || forceInternal) {
takePhotoInternalOrExternal(lat, lon, mapActivity);
} else {
takePhotoExternal(lat, lon, mapActivity);
}
} else {
actionLat = lat;
actionLon = lon;
ActivityCompat.requestPermissions(mapActivity,
new String[]{Manifest.permission.CAMERA},
CAMERA_FOR_PHOTO_REQUEST_CODE);
}
}
private void takePhotoInternalOrExternal(double lat, double lon, MapActivity mapActivity) {
openCamera();
if (cam != null) {
initRecMenu(AVActionType.REC_PHOTO, lat, lon);
takePhotoWithCamera(lat, lon, mapActivity);
} else {
takePhotoExternal(lat, lon, mapActivity);
}
}
private void takePhotoWithCamera(final double lat, final double lon,
final MapActivity mapActivity) {
try {
lastTakingPhoto = getBaseFileName(lat, lon, app, IMG_EXTENSION);
final Camera.Size mPreviewSize;
Parameters parameters = cam.getParameters();
List<Camera.Size> psps = parameters.getSupportedPictureSizes();
int camPicSizeIndex = AV_CAMERA_PICTURE_SIZE.get();
// camera picture size
log.debug("takePhotoWithCamera() index=" + camPicSizeIndex);
if (camPicSizeIndex == AV_PHOTO_SIZE_DEFAULT) {
camPicSizeIndex = cameraPictureSizeDefault;
log.debug("takePhotoWithCamera() Default value of picture size. Set index to cameraPictureSizeDefault. Now index="
+ camPicSizeIndex);
}
final Camera.Size selectedCamPicSize = psps.get(camPicSizeIndex);
if (mSupportedPreviewSizes != null) {
int width = selectedCamPicSize.width;
int height = selectedCamPicSize.height;
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
} else {
mPreviewSize = null;
}
final SurfaceView view;
if (mPreviewSize != null) {
view = recordingMenu.prepareSurfaceView(mPreviewSize.width, mPreviewSize.height);
} else {
view = recordingMenu.prepareSurfaceView();
}
view.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
view.getHolder().addCallback(new Callback() {
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
// load sound befor shot
if (AV_PHOTO_PLAY_SOUND.get()) {
if (sp == null)
sp = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);
if (shotId == 0) {
try {
AssetFileDescriptor assetFileDescriptor = app.getAssets().openFd("sounds/camera_click.ogg");
shotId = sp.load(assetFileDescriptor, 1);
assetFileDescriptor.close();
} catch (Exception e) {
log.error("cannot get shotId for sounds/camera_click.ogg");
}
}
}
Parameters parameters = cam.getParameters();
parameters.setPictureSize(selectedCamPicSize.width, selectedCamPicSize.height);
log.debug("takePhotoWithCamera() set Picture size: width=" + selectedCamPicSize.width
+ " height=" + selectedCamPicSize.height);
// camera focus type
autofocus = true;
parameters.removeGpsData();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
parameters.setGpsLatitude(lat);
parameters.setGpsLongitude(lon);
}
switch (AV_CAMERA_FOCUS_TYPE.get()) {
case AV_CAMERA_FOCUS_HIPERFOCAL:
parameters.setFocusMode(Parameters.FOCUS_MODE_FIXED);
autofocus = false;
log.info("Osmand:AudioNotes set camera FOCUS_MODE_FIXED");
break;
case AV_CAMERA_FOCUS_EDOF:
parameters.setFocusMode(Parameters.FOCUS_MODE_EDOF);
autofocus = false;
log.info("Osmand:AudioNotes set camera FOCUS_MODE_EDOF");
break;
case AV_CAMERA_FOCUS_INFINITY:
parameters.setFocusMode(Parameters.FOCUS_MODE_INFINITY);
autofocus = false;
log.info("Osmand:AudioNotes set camera FOCUS_MODE_INFINITY");
break;
case AV_CAMERA_FOCUS_MACRO:
parameters.setFocusMode(Parameters.FOCUS_MODE_MACRO);
log.info("Osmand:AudioNotes set camera FOCUS_MODE_MACRO");
break;
case AV_CAMERA_FOCUS_CONTINUOUS:
parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
log.info("Osmand:AudioNotes set camera FOCUS_MODE_CONTINUOUS_PICTURE");
break;
default:
parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO);
log.info("Osmand:AudioNotes set camera FOCUS_MODE_AUTO");
break;
}
if (parameters.getSupportedWhiteBalance() != null
&& parameters.getSupportedWhiteBalance().contains(Parameters.WHITE_BALANCE_AUTO)) {
parameters.setWhiteBalance(Parameters.WHITE_BALANCE_AUTO);
}
if (parameters.getSupportedFlashModes() != null
&& parameters.getSupportedFlashModes().contains(Parameters.FLASH_MODE_AUTO)) {
//parameters.setFlashMode(Parameters.FLASH_MODE_AUTO);
}
int cameraOrientation = getCamOrientation(mapActivity, Camera.CameraInfo.CAMERA_FACING_BACK);
cam.setDisplayOrientation(cameraOrientation);
parameters.set("rotation", cameraOrientation);
if (mPreviewSize != null) {
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
}
cam.setParameters(parameters);
cam.setPreviewDisplay(holder);
cam.startPreview();
internalShoot();
} catch (Exception e) {
logErr(e);
closeRecordingMenu();
closeCamera();
finishRecording();
e.printStackTrace();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
});
recordingMenu.show();
} catch (RuntimeException e) {
logErr(e);
closeCamera();
}
}
private void internalShoot() {
getMapActivity().getMyApplication().runInUIThread(new Runnable() {
@Override
public void run() {
if (!autofocus) {
cam.takePicture(null, null, new JpegPhotoHandler());
} else {
cam.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
try {
cam.takePicture(null, null, new JpegPhotoHandler());
} catch (Exception e) {
logErr(e);
closeRecordingMenu();
closeCamera();
finishRecording();
e.printStackTrace();
}
}
});
}
}
}, 200);
}
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.1;
double targetRatio;
if (w > h) {
targetRatio = (double) w / h;
} else {
targetRatio = (double) h / w;
}
if (sizes == null) return null;
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
if (Math.abs(size.height - h) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - h);
}
}
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - h) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - h);
}
}
}
return optimalSize;
}
private static int getCamOrientation(MapActivity mapActivity, int cameraId) {
android.hardware.Camera.CameraInfo info =
new android.hardware.Camera.CameraInfo();
android.hardware.Camera.getCameraInfo(cameraId, info);
int rotation = mapActivity.getWindowManager().getDefaultDisplay()
.getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0:
degrees = 0;
break;
case Surface.ROTATION_90:
degrees = 90;
break;
case Surface.ROTATION_180:
degrees = 180;
break;
case Surface.ROTATION_270:
degrees = 270;
break;
}
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
} else { // back-facing
result = (info.orientation - degrees + 360) % 360;
}
return result;
}
public void shoot() {
if (!recordingDone) {
recordingDone = true;
if (cam != null && lastTakingPhoto != null) {
try {
cam.takePicture(null, null, new JpegPhotoHandler());
} catch (RuntimeException e) {
closeRecordingMenu();
closeCamera();
finishRecording();
}
}
}
}
public void takePhotoExternal(double lat, double lon, final MapActivity mapActivity) {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
final File f = getBaseFileName(lat, lon, app, IMG_EXTENSION);
lastTakingPhoto = f;
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(f));
try {
mapActivity.startActivityForResult(takePictureIntent, 205);
} catch (Exception e) {
log.error("Error taking a picture ", e);
Toast.makeText(app, app.getString(R.string.recording_error) + " : " + e.getMessage(), Toast.LENGTH_LONG).show();
}
}
private void cleanupSpace() {
File[] files = app.getAppPath(IndexConstants.AV_INDEX_DIR).listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String filename) {
return filename.endsWith("." + MPEG4_EXTENSION);
}
});
if (files != null) {
double usedSpace = 0;
for (File f : files) {
usedSpace += f.length();
}
usedSpace /= (1 << 30); // gigabytes
final CamcorderProfile p = CamcorderProfile.get(AV_VIDEO_QUALITY.get());
double bitrate = (((p.videoBitRate + p.audioBitRate) / 8f) * 60f) / (1 << 30); // gigabytes per minute
double clipSpace = bitrate * AV_RS_CLIP_LENGTH.get();
double storageSize = AV_RS_STORAGE_SIZE.get();
double availableSpace = storageSize;
File dir = app.getAppPath("").getParentFile();
if (dir.canRead()) {
StatFs fs = new StatFs(dir.getAbsolutePath());
availableSpace = (double) (fs.getAvailableBlocks()) * fs.getBlockSize() / (1 << 30) - clipSpace;
}
if (usedSpace + clipSpace > storageSize || clipSpace > availableSpace) {
Arrays.sort(files, new Comparator<File>() {
@Override
public int compare(File lhs, File rhs) {
return lhs.lastModified() < rhs.lastModified() ? -1 : (lhs.lastModified() == rhs.lastModified() ? 0 : 1);
}
});
boolean wasAnyDeleted = false;
ArrayList<File> arr = new ArrayList<>(Arrays.asList(files));
while (arr.size() > 0
&& (usedSpace + clipSpace > storageSize || clipSpace > availableSpace)) {
File f = arr.remove(0);
double length = ((double) f.length()) / (1 << 30);
Recording r = recordingByFileName.get(f.getName());
if (r != null) {
deleteRecording(r, false);
wasAnyDeleted = true;
usedSpace -= length;
availableSpace += length;
} else if (f.delete()) {
usedSpace -= length;
availableSpace += length;
}
}
if (wasAnyDeleted) {
app.runInUIThread(new Runnable() {
@Override
public void run() {
mapActivity.refreshMap();
}
}, 20);
}
}
}
}
private void runMediaRecorder(final MapActivity mapActivity, MediaRecorder mr, final File f) throws IOException {
mr.prepare();
mr.start();
mediaRec = mr;
mediaRecFile = f;
recordingMenu.show();
updateRecordControl(mapActivity, f);
}
private void updateRecordControl(final MapActivity mapActivity, final File f) {
recordControl.setText(app.getString(R.string.shared_string_control_stop), "");
recordControl.setIcons(R.drawable.widget_icon_av_active, R.drawable.widget_icon_av_active);
final MapInfoLayer mil = mapActivity.getMapLayers().getMapInfoLayer();
if (!recordControl.isVisible()) {
recordControl.setExplicitlyVisible(true);
mil.recreateControls();
mapActivity.getMapView().refreshMap(true);
}
recordControl.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
stopRecording(mapActivity, false);
}
});
}
public void stopRecording(final MapActivity mapActivity, boolean restart) {
if (!recordingDone) {
if (!restart || !stopCameraRecording(true)) {
recordingDone = true;
if (!recordControl.isVisible()) {
recordControl.setExplicitlyVisible(false);
mapActivity.getMapLayers().getMapInfoLayer().recreateControls();
}
stopCameraRecording(false);
if (recordControl != null) {
setRecordListener(recordControl, mapActivity);
}
SHOW_RECORDINGS.set(true);
mapActivity.getMapView().refreshMap();
updateWidgetIcon(recordControl);
closeRecordingMenu();
}
}
}
private LatLon getNextRecordingLocation() {
double lat = mapActivity.getMapLocation().getLatitude();
double lon = mapActivity.getMapLocation().getLongitude();
Location loc = app.getLocationProvider().getLastKnownLocation();
if (loc != null) {
lat = loc.getLatitude();
lon = loc.getLongitude();
}
return new LatLon(lat, lon);
}
private void updateContextMenu(Recording rec) {
if (mapActivity != null && rec != null) {
MapContextMenu menu = mapActivity.getContextMenu();
menu.show(new LatLon(rec.lat, rec.lon), audioNotesLayer.getObjectName(rec), rec);
if (app.getRoutingHelper().isFollowingMode()) {
menu.hideWithTimeout(3000);
}
}
}
private void finishRecording() {
currentRecording = null;
}
@Override
public void addMyPlacesTab(FavoritesActivity favoritesActivity, List<TabItem> mTabs, Intent intent) {
if (getAllRecordings().size() > 0) {
mTabs.add(favoritesActivity.getTabIndicator(NOTES_TAB, NotesFragment.class));
if (intent != null && "AUDIO".equals(intent.getStringExtra("TAB"))) {
app.getSettings().FAVORITES_TAB.set(NOTES_TAB);
}
}
}
public boolean indexSingleFile(File f) {
boolean oldFileExist = recordingByFileName.containsKey(f.getName());
if (oldFileExist) {
return false;
}
Recording r = new Recording(f);
String fileName = f.getName();
String otherName = r.getOtherName(fileName);
int i = otherName.indexOf('.');
if (i > 0) {
otherName = otherName.substring(0, i);
}
r.file = f;
GeoParsedPoint geo = MapUtils.decodeShortLinkString(otherName);
r.lat = geo.getLatitude();
r.lon = geo.getLongitude();
Float heading = app.getLocationProvider().getHeading();
Location loc = app.getLocationProvider().getLastKnownLocation();
if (lastTakingPhoto != null && lastTakingPhoto.getName().equals(f.getName())) {
float rot = heading != null ? heading : 0;
try {
r.updatePhotoInformation(r.lat, r.lon, loc, rot == 0 ? Double.NaN : rot);
} catch (IOException e) {
log.error("Error updating EXIF information " + e.getMessage(), e);
}
lastTakingPhoto = null;
}
recordings.registerObject(r.lat, r.lon, r);
Map<String, Recording> newMap = new LinkedHashMap<>(recordingByFileName);
newMap.put(f.getName(), r);
recordingByFileName = newMap;
if (isRecording()) {
AVActionType type = currentRecording.type;
finishRecording();
if (!AV_RECORDER_SPLIT.get() || type != AVActionType.REC_VIDEO) {
final Recording recordingForMenu = r;
app.runInUIThread(new Runnable() {
@Override
public void run() {
updateContextMenu(recordingForMenu);
}
}, 200);
}
}
return true;
}
@Override
public void disable(OsmandApplication app) {
AudioManager am = (AudioManager) app.getSystemService(Context.AUDIO_SERVICE);
if (am != null) {
unregisterMediaListener(am);
}
}
@Override
public List<String> indexingFiles(IProgress progress) {
return indexingFiles(progress, true, false);
}
public List<String> indexingFiles(IProgress progress, boolean reIndexAndKeepOld, boolean registerNew) {
File avPath = app.getAppPath(IndexConstants.AV_INDEX_DIR);
if (avPath.canRead()) {
if (!reIndexAndKeepOld) {
recordings.clear();
recordingByFileName = new LinkedHashMap<>();
}
File[] files = avPath.listFiles();
if (files != null) {
for (File f : files) {
indexFile(registerNew, f);
}
}
}
return null;
}
private void indexFile(boolean registerInGPX, File f) {
if (f.getName().endsWith(THREEGP_EXTENSION) || f.getName().endsWith(MPEG4_EXTENSION)
|| f.getName().endsWith(IMG_EXTENSION)) {
boolean newFileIndexed = indexSingleFile(f);
if (newFileIndexed && registerInGPX) {
Recording rec = recordingByFileName.get(f.getName());
if (rec != null &&
(app.getSettings().SAVE_TRACK_TO_GPX.get()
|| app.getSettings().SAVE_GLOBAL_TRACK_TO_GPX.get())
&& OsmandPlugin.getEnabledPlugin(OsmandMonitoringPlugin.class) != null) {
String name = f.getName();
SavingTrackHelper savingTrackHelper = app.getSavingTrackHelper();
savingTrackHelper.insertPointData(rec.lat, rec.lon, System.currentTimeMillis(), null, name, null, 0);
}
}
}
}
public DataTileManager<Recording> getRecordings() {
return recordings;
}
private void checkRecordings() {
Iterator<Recording> it = recordingByFileName.values().iterator();
while (it.hasNext()) {
Recording r = it.next();
if (!r.file.exists()) {
it.remove();
recordings.unregisterObject(r.lat, r.lon, r);
}
}
}
public void deleteRecording(Recording r, boolean updateUI) {
recordings.unregisterObject(r.lat, r.lon, r);
Map<String, Recording> newMap = new LinkedHashMap<>(recordingByFileName);
newMap.remove(r.file.getName());
recordingByFileName = newMap;
Algorithms.removeAllFiles(r.file);
if (mapActivity != null && updateUI) {
if (mapActivity.getContextMenu().getObject() == r) {
mapActivity.getContextMenu().close();
}
mapActivity.getMapView().refreshMap();
}
}
@Override
public Class<? extends Activity> getSettingsActivity() {
return SettingsAudioVideoActivity.class;
}
@Override
public void onMapActivityExternalResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 205 || requestCode == 105) {
indexingFiles(null, true, true);
}
}
public boolean onMapActivityKeyEvent(KeyEvent key) {
if (KeyEvent.KEYCODE_CAMERA == key.getKeyCode()) {
defaultAction(mapActivity);
return true;
}
return false;
}
public Collection<Recording> getAllRecordings() {
return recordingByFileName.values();
}
protected Recording[] getRecordingsSorted() {
checkRecordings();
Collection<Recording> allObjects = getAllRecordings();
Recording[] res = allObjects.toArray(new Recording[allObjects.size()]);
Arrays.sort(res, new Comparator<Recording>() {
@Override
public int compare(Recording object1, Recording object2) {
long l1 = object1.file.lastModified();
long l2 = object2.file.lastModified();
return l1 < l2 ? 1 : -1;
}
});
return res;
}
public boolean isPlaying() {
try {
return player != null && player.isPlaying();
} catch (Exception e) {
return false;
}
}
public boolean isPlaying(Recording r) {
return isPlaying() && recordingPlaying == r;
}
public int getPlayingPosition() {
if (isPlaying()) {
return player.getCurrentPosition();
} else if (player != null) {
return player.getDuration();
} else {
return -1;
}
}
public void stopPlaying() {
if (isPlaying()) {
try {
player.stop();
} finally {
player.release();
player = null;
updateContextMenu();
}
}
}
private void updateContextMenu() {
app.runInUIThread(new Runnable() {
@Override
public void run() {
getMapActivity().getContextMenu().updateMenuUI();
}
});
}
private void closeRecordingMenu() {
if (mapActivity != null) {
mapActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (recordingMenu != null) {
recordingMenu.hide();
recordingMenu = null;
}
restoreScreenOrientation();
}
});
}
}
public void playRecording(final Context ctx, final Recording r) {
if (r.isVideo()) {
Intent vint = new Intent(Intent.ACTION_VIEW);
vint.setDataAndType(Uri.fromFile(r.file), "video/*");
vint.setFlags(0x10000000);
try {
ctx.startActivity(vint);
} catch (Exception e) {
e.printStackTrace();
}
return;
} else if (r.isPhoto()) {
Intent vint = new Intent(Intent.ACTION_VIEW);
vint.setDataAndType(Uri.fromFile(r.file), "image/*");
vint.setFlags(0x10000000);
ctx.startActivity(vint);
return;
}
if (isPlaying()) {
stopPlaying();
}
recordingPlaying = r;
player = new MediaPlayer();
try {
player.setDataSource(r.file.getAbsolutePath());
player.setOnPreparedListener(new OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
try {
player.start();
if (playerTimer != null) {
playerTimer.cancel();
}
playerTimer = new Timer();
playerTimer.schedule(new TimerTask() {
@Override
public void run() {
updateContextMenu();
if (!isPlaying()) {
cancel();
playerTimer = null;
}
}
}, 10, 1000);
} catch (Exception e) {
logErr(e);
}
}
});
player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
recordingPlaying = null;
}
});
player.prepareAsync();
} catch (Exception e) {
logErr(e);
}
}
@Override
public boolean mapActivityKeyUp(MapActivity mapActivity, int keyCode) {
if (keyCode == KeyEvent.KEYCODE_CAMERA) {
defaultAction(mapActivity);
return true;
}
return false;
}
@TargetApi(Build.VERSION_CODES.M)
@Override
public void handleRequestPermissionsResult(int requestCode, String[] permissions,
int[] grantResults) {
runAction = -1;
if (requestCode == CAMERA_FOR_VIDEO_REQUEST_CODE) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
runAction = AV_DEFAULT_ACTION_VIDEO;
} else {
app.showToastMessage(R.string.no_camera_permission);
}
} else if (requestCode == CAMERA_FOR_PHOTO_REQUEST_CODE) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
runAction = AV_DEFAULT_ACTION_TAKEPICTURE;
} else {
app.showToastMessage(R.string.no_camera_permission);
}
} else if (requestCode == AUDIO_REQUEST_CODE) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
runAction = AV_DEFAULT_ACTION_AUDIO;
} else {
app.showToastMessage(R.string.no_microphone_permission);
}
}
/*
MapActivity mapActivity = getMapActivity();
if (mapActivity != null && !mapActivity.isDestroyed()) {
takeAction(mapActivity, actionLon, actionLat, runAction);
runAction = -1;
}
*/
}
public class JpegPhotoHandler implements PictureCallback {
public JpegPhotoHandler() {
}
@Override
public void onPictureTaken(final byte[] data, Camera camera) {
photoJpegData = data;
if (AV_PHOTO_PLAY_SOUND.get()) {
if (sp != null && shotId != 0) {
sp.play(shotId, 0.7f, 0.7f, 0, 0, 1);
}
}
if (recordingMenu != null) {
recordingMenu.showFinalPhoto(data, FULL_SCEEN_RESULT_DELAY_MS);
}
startPhotoTimer();
}
}
private void startPhotoTimer() {
if (photoTimer != null) {
cancelPhotoTimer();
}
photoTimer = new Timer();
photoTimer.schedule(new TimerTask() {
@Override
public void run() {
finishPhotoRecording(false);
}
}, FULL_SCEEN_RESULT_DELAY_MS);
}
private void cancelPhotoTimer() {
if (photoTimer != null) {
photoTimer.cancel();
photoTimer = null;
}
}
public synchronized void shootAgain() {
cancelPhotoTimer();
photoJpegData = null;
if (cam != null) {
try {
cam.cancelAutoFocus();
cam.stopPreview();
if (recordingMenu != null) {
recordingMenu.hideFinalPhoto();
}
cam.startPreview();
internalShoot();
} catch (Exception e) {
logErr(e);
closeRecordingMenu();
closeCamera();
finishRecording();
e.printStackTrace();
}
}
}
public synchronized void finishPhotoRecording(boolean cancel) {
cancelPhotoTimer();
if (photoJpegData != null && photoJpegData.length > 0 && lastTakingPhoto != null) {
try {
if (!cancel) {
FileOutputStream fos = new FileOutputStream(lastTakingPhoto);
fos.write(photoJpegData);
fos.close();
indexFile(true, lastTakingPhoto);
}
} catch (Exception error) {
logErr(error);
} finally {
photoJpegData = null;
closeRecordingMenu();
if (!cancel) {
finishRecording();
}
}
} else if (cancel) {
closeRecordingMenu();
}
}
@Override
public int getLogoResourceId() {
return R.drawable.ic_action_micro_dark;
}
@Override
public int getAssetResourceName() {
return R.drawable.audio_video_notes;
}
@Override
public DashFragmentData getCardFragment() {
return DashAudioVideoNotesFragment.FRAGMENT_DATA;
}
}