package me.guillaumin.android.osmtracker.view; import java.io.File; import java.util.Date; import java.util.UUID; import me.guillaumin.android.osmtracker.OSMTracker; import me.guillaumin.android.osmtracker.R; import me.guillaumin.android.osmtracker.db.DataHelper; import me.guillaumin.android.osmtracker.db.TrackContentProvider.Schema; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaRecorder; import android.media.MediaRecorder.OnInfoListener; import android.os.SystemClock; import android.preference.PreferenceManager; import android.util.Log; import android.view.KeyEvent; import android.widget.Toast; public class VoiceRecDialog extends ProgressDialog implements OnInfoListener{ private final static String TAG = VoiceRecDialog.class.getSimpleName(); /** * Id of the track the dialog will add this waypoint to */ private long wayPointTrackId; /** * Unique identifier of the waypoint this dialog working on */ private String wayPointUuid = null; /** * AudioManager, to unmute microphone */ private AudioManager audioManager; /** * the duration of a voice recording in seconds */ private int recordingDuration = -1; /** * Indicates if we are currently recording, to prevent double click. */ private boolean isRecording = false; /** * MediaRecorder used to record audio */ private MediaRecorder mediaRecorder; /** * MediaPlayer used to play a short beepbeep when recording starts */ private MediaPlayer mediaPlayerStart = null; /** * MediaPlayer used to play a short beep when recording stops */ private MediaPlayer mediaPlayerStop = null; /** * the context for this dialog */ private Context context; /** * saves the orientation at the time when the dialog was started */ private int currentOrientation = -1; /** * saves the requested orientation at the time when the dialog was started to restore it when we stop recording */ private int currentRequestedOrientation = -1; /** * saves the time when this dialog was started. * This is needed to check if a key was pressed before the dialog was shown */ private long dialogStartTime = 0; public VoiceRecDialog(Context context, long trackId) { super(context); this.context = context; this.wayPointTrackId = trackId; // Try to un-mute microphone, just in case audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); this.setTitle(context.getResources().getString(R.string.tracklogger_voicerec_title)); this.setButton(context.getResources().getString(R.string.tracklogger_voicerec_stop), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { mediaRecorder.stop(); VoiceRecDialog.this.dismiss(); } }); } /** * @link android.app.Dialog#onStart() */ @Override public void onStart() { // we'll need the start time of this dialog to check if a key has been pressed before the dialog was opened dialogStartTime = SystemClock.uptimeMillis(); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); if (!isRecording) recordingDuration = Integer.parseInt( preferences.getString(OSMTracker.Preferences.KEY_VOICEREC_DURATION, OSMTracker.Preferences.VAL_VOICEREC_DURATION)); else { if (recordingDuration <= 0) recordingDuration = Integer.parseInt(OSMTracker.Preferences.VAL_VOICEREC_DURATION); } this.setMessage( context.getResources().getString(R.string.tracklogger_voicerec_text) .replace("{0}", String.valueOf(recordingDuration))); // we need to avoid screen orientation change during recording because this causes some strange behavior try{ this.currentOrientation = context.getResources().getConfiguration().orientation; this.currentRequestedOrientation = this.getOwnerActivity().getRequestedOrientation(); this.getOwnerActivity().setRequestedOrientation(currentOrientation); }catch(Exception e){ Log.w(TAG, "No OwnerActivity found for this Dialog. Use showDialog method within the activity to handle this Dialog and to avoid voice recording problems."); } Log.d(TAG,"onStart() called"); if(wayPointUuid == null){ Log.d(TAG,"onStart() no UUID set, generating a new UUID"); // there is no UUID set for the waypoint we're working on // so we need to generate a UUID and track this point wayPointUuid = UUID.randomUUID().toString(); Intent intent = new Intent(OSMTracker.INTENT_TRACK_WP); intent.putExtra(Schema.COL_TRACK_ID, wayPointTrackId); intent.putExtra(OSMTracker.INTENT_KEY_UUID, wayPointUuid); intent.putExtra(OSMTracker.INTENT_KEY_NAME, context.getResources().getString(R.string.wpt_voicerec)); context.sendBroadcast(intent); } if (!isRecording) { Log.d(TAG,"onStart() currently not recording, start a new one"); isRecording = true; // Get a new audio filename File audioFile = getAudioFile(); if (audioFile != null) { boolean playSound = preferences.getBoolean(OSMTracker.Preferences.KEY_SOUND_ENABLED, OSMTracker.Preferences.VAL_SOUND_ENABLED); // Some workaround for record problems unMuteMicrophone(); // prepare the media players if (playSound) { mediaPlayerStart = MediaPlayer.create(context, R.raw.beepbeep); if (mediaPlayerStart != null) { mediaPlayerStart.setLooping(false); } mediaPlayerStop = MediaPlayer.create(context, R.raw.beep); if (mediaPlayerStop != null) { mediaPlayerStop.setLooping(false); } } mediaRecorder = new MediaRecorder(); try { // MediaRecorder configuration mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); mediaRecorder.setOutputFile(audioFile.getAbsolutePath()); mediaRecorder.setMaxDuration(recordingDuration * 1000); mediaRecorder.setOnInfoListener(this); Log.d(TAG, "onStart() starting mediaRecorder..."); mediaRecorder.prepare(); mediaRecorder.start(); if (mediaPlayerStart != null) { // short "beep-beep" to notify that recording started mediaPlayerStart.start(); } Log.d(TAG,"onStart() mediaRecorder started..."); } catch (Exception ioe) { Log.w(TAG, "onStart() voice recording has failed", ioe); this.dismiss(); Toast.makeText(context, context.getResources().getString(R.string.error_voicerec_failed), Toast.LENGTH_SHORT).show(); } // Still update waypoint, could be useful even without // the voice file. Intent intent = new Intent(OSMTracker.INTENT_UPDATE_WP); intent.putExtra(Schema.COL_TRACK_ID, wayPointTrackId); intent.putExtra(OSMTracker.INTENT_KEY_UUID, wayPointUuid); intent.putExtra(OSMTracker.INTENT_KEY_LINK, audioFile.getName()); context.sendBroadcast(intent); } else { Log.w(TAG,"onStart() no suitable audioFile could be created"); // The audio file could not be created on the file system // let the user know Toast.makeText(context, context.getResources().getString(R.string.error_voicerec_failed), Toast.LENGTH_SHORT).show(); } } super.onStart(); } @Override public void onInfo(MediaRecorder mr, int what, int extra) { Log.d(TAG, "onInfo() received mediaRecorder info ("+String.valueOf(what)+")"); switch(what){ case MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN: case MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED: case MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED: if (mediaPlayerStop != null) { // short "beep" when we stop to record mediaPlayerStop.start(); } // MediaRecorder has been stopped by system // we're done, so we can dismiss the dialog this.dismiss(); break; } } /** * called when the dialog disappears */ @Override protected void onStop() { Log.d(TAG, "onStop() called"); safeClose(mediaRecorder, false); safeClose(mediaPlayerStart); safeClose(mediaPlayerStop); wayPointUuid = null; isRecording = false; try { this.getOwnerActivity().setRequestedOrientation(currentRequestedOrientation); } catch(Exception e) { Log.w(TAG, "No OwnerActivity found for this Dialog. Use showDialog method within the activity to handle this Dialog and to avoid voice recording problems."); } super.onStop(); } /* (non-Javadoc) * @see android.app.AlertDialog#onKeyDown(int, android.view.KeyEvent) */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // only handle this event if it was raised after the dialog was shown if(event.getDownTime() > dialogStartTime){ switch (keyCode) { case KeyEvent.KEYCODE_DPAD_CENTER: case KeyEvent.KEYCODE_HEADSETHOOK: // stop recording / dismiss the dialog this.dismiss(); return true; } } return super.onKeyDown(keyCode, event); } /** * Un-mute the microphone, to prevent a blank-recording * on certain devices (Acer Liquid ?) */ private void unMuteMicrophone() { Log.v(TAG, "unMuteMicrophone()"); if (audioManager.isMicrophoneMute()) { audioManager.setMicrophoneMute(false); } } /** * @return a new File in the current track directory. */ public File getAudioFile() { File audioFile = null; // Query for current track directory File trackDir = DataHelper.getTrackDirectory(wayPointTrackId); // Create the track storage directory if it does not yet exist if (!trackDir.exists()) { if ( !trackDir.mkdirs() ) { Log.w(TAG, "Directory [" + trackDir.getAbsolutePath() + "] does not exist and cannot be created"); } } // Ensure that this location can be written to if (trackDir.exists() && trackDir.canWrite()) { audioFile = new File(trackDir, DataHelper.FILENAME_FORMATTER.format(new Date()) + DataHelper.EXTENSION_3GPP); } else { Log.w(TAG, "The directory [" + trackDir.getAbsolutePath() + "] will not allow files to be created"); } return audioFile; } /** * Safely close a {@link MediaPlayer} without throwing * exceptions * @param mp */ private void safeClose(MediaPlayer mp) { if (mp != null) { try { mp.stop(); } catch (Exception e) { Log.w(TAG, "Failed to stop media player",e); } finally { mp.release(); } } } /** * Safely close a {@link MediaRecorder} without throwing * exceptions * @param mr */ private void safeClose(MediaRecorder mr, boolean stopIt) { if (mr != null) { try { if (stopIt) { mr.stop(); } } catch (Exception e) { Log.w(TAG, "Failed to stop media recorder",e); } finally { mr.release(); } } } }