package me.guillaumin.android.osmtracker.activity;
import java.io.File;
import java.util.Date;
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 me.guillaumin.android.osmtracker.layout.GpsStatusRecord;
import me.guillaumin.android.osmtracker.layout.UserDefinedLayout;
import me.guillaumin.android.osmtracker.listener.SensorListener;
import me.guillaumin.android.osmtracker.receiver.MediaButtonReceiver;
import me.guillaumin.android.osmtracker.service.gps.GPSLogger;
import me.guillaumin.android.osmtracker.service.gps.GPSLoggerServiceConnection;
import me.guillaumin.android.osmtracker.util.FileSystemUtils;
import me.guillaumin.android.osmtracker.util.ThemeValidator;
import me.guillaumin.android.osmtracker.view.TextNoteDialog;
import me.guillaumin.android.osmtracker.view.VoiceRecDialog;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.database.Cursor;
import android.location.LocationManager;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.provider.MediaStore.Images.ImageColumns;
import android.provider.Settings;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
/**
* Main track logger activity. Communicate with the GPS service to display GPS
* status, and allow user to record waypoints.
*
* @author Nicolas Guillaumin
*
*/
public class TrackLogger extends Activity {
private static final String TAG = TrackLogger.class.getSimpleName();
/**
* Request code for callback after the camera application had taken a
* picture for us.
*/
private static final int REQCODE_IMAGE_CAPTURE = 0;
/**
* Request code for callback after the gallery was chosen by the user.
*/
private static final int REQCODE_GALLERY_CHOSEN = 1;
/**
* Bundle state key for tracking flag.
*/
public static final String STATE_IS_TRACKING = "isTracking";
/**
* Bundle state key button state.
*/
public static final String STATE_BUTTONS_ENABLED = "buttonsEnabled";
/**
* GPS Logger service, to receive events and be able to update UI.
*/
private GPSLogger gpsLogger;
/**
* GPS Logger service intent, to be used in start/stopService();
*/
private Intent gpsLoggerServiceIntent;
/**
* Main button layout
*/
private UserDefinedLayout mainLayout;
/**
* Flag to check GPS status at startup. Is cleared after the first
* displaying of GPS status dialog, to prevent the dialog to display if user
* goes to settings/about/other screen.
*/
private boolean checkGPSFlag = true;
/**
* Keeps track of the image file when taking a picture.
*/
private File currentImageFile;
/**
* Keeps track of the current track id.
*/
private long currentTrackId;
/**
* Handles the bind to the GPS Logger service
*/
private ServiceConnection gpsLoggerConnection = new GPSLoggerServiceConnection(this);
/**
* Keeps the SharedPreferences
*/
private SharedPreferences prefs = null;
/**
* keeps track of current button status
*/
private boolean buttonsEnabled = false;
/**
* constant for text note dialog
*/
public static final int DIALOG_TEXT_NOTE = 1;
/**
* constant for voice recording dialog
*/
public static final int DIALOG_VOICE_RECORDING = 2;
/**
* sensor listener for the azimuth display
*/
private SensorListener sensorListener;
private AudioManager mAudioManager;
private ComponentName mediaButtonReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
// Get the track id to work with
currentTrackId = getIntent().getExtras().getLong(Schema.COL_TRACK_ID);
Log.v(TAG, "Starting for track id " + currentTrackId);
gpsLoggerServiceIntent = new Intent(this, GPSLogger.class);
gpsLoggerServiceIntent.putExtra(Schema.COL_TRACK_ID, currentTrackId);
// Populate default preference values
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
// get shared preferences
prefs = PreferenceManager.getDefaultSharedPreferences(this);
// Set application theme according to user settings
setTheme(getResources().getIdentifier(ThemeValidator.getValidTheme(prefs, getResources()), null, null));
super.onCreate(savedInstanceState);
setContentView(R.layout.tracklogger);
// set trackLogger to keepScreenOn depending on the user's preference
View trackLoggerView = findViewById(R.id.tracklogger_root);
trackLoggerView.setKeepScreenOn(prefs.getBoolean(OSMTracker.Preferences.KEY_UI_DISPLAY_KEEP_ON, OSMTracker.Preferences.VAL_UI_DISPLAY_KEEP_ON));
// we'll restore previous button state, GPSStatusRecord will enable all buttons, as soon as there's a gps fix
if(savedInstanceState != null){
buttonsEnabled = savedInstanceState.getBoolean(STATE_BUTTONS_ENABLED, false);
}
// create sensor listener
sensorListener = new SensorListener();
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
mediaButtonReceiver = new ComponentName(this, MediaButtonReceiver.class.getName());
}
@Override
protected void onResume() {
setTitle(getResources().getString(R.string.tracklogger) + ": #" + currentTrackId);
// set trackLogger to keepScreenOn depending on the user's preference
View trackLoggerView = findViewById(R.id.tracklogger_root);
trackLoggerView.setKeepScreenOn(prefs.getBoolean(OSMTracker.Preferences.KEY_UI_DISPLAY_KEEP_ON, OSMTracker.Preferences.VAL_UI_DISPLAY_KEEP_ON));
// Fix to the user's preferred orientation (if any)
String preferredOrientation = prefs.getString(OSMTracker.Preferences.KEY_UI_ORIENTATION,
OSMTracker.Preferences.VAL_UI_ORIENTATION);
if (preferredOrientation.equals(OSMTracker.Preferences.VAL_UI_ORIENTATION_PORTRAIT)) {
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
} else if (preferredOrientation.equals(OSMTracker.Preferences.VAL_UI_ORIENTATION_LANDSCAPE)) {
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} else {
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}
// Try to inflate the buttons layout
try {
String userLayout = prefs.getString(
OSMTracker.Preferences.KEY_UI_BUTTONS_LAYOUT, OSMTracker.Preferences.VAL_UI_BUTTONS_LAYOUT);
if (OSMTracker.Preferences.VAL_UI_BUTTONS_LAYOUT.equals(userLayout)) {
// Using default buttons layout
mainLayout = new UserDefinedLayout(this, currentTrackId, null);
} else {
// Using user buttons layout
File layoutFile = new File(
Environment.getExternalStorageDirectory(),
prefs.getString(
OSMTracker.Preferences.KEY_STORAGE_DIR,
OSMTracker.Preferences.VAL_STORAGE_DIR)
+ File.separator + Preferences.LAYOUTS_SUBDIR
+ File.separator + userLayout);
mainLayout = new UserDefinedLayout(this, currentTrackId, layoutFile);
}
((ViewGroup) findViewById(R.id.tracklogger_root)).removeAllViews();
((ViewGroup) findViewById(R.id.tracklogger_root)).addView(mainLayout);
} catch (Exception e) {
Log.e(TAG, "Error while inflating UserDefinedLayout", e);
Toast.makeText(this, R.string.error_userlayout_parsing, Toast.LENGTH_SHORT).show();
}
// Check GPS status
if (checkGPSFlag
&& prefs.getBoolean(OSMTracker.Preferences.KEY_GPS_CHECKSTARTUP,
OSMTracker.Preferences.VAL_GPS_CHECKSTARTUP)) {
checkGPSProvider();
}
// Register GPS status update for upper controls
((GpsStatusRecord) findViewById(R.id.gpsStatus)).requestLocationUpdates(true);
// Start GPS Logger service
startService(gpsLoggerServiceIntent);
// Bind to GPS service.
// We can't use BIND_AUTO_CREATE here, because when we'll ubound
// later, we want to keep the service alive in background
bindService(gpsLoggerServiceIntent, gpsLoggerConnection, 0);
// connect the sensor listener
sensorListener.register(this);
setEnabledActionButtons(buttonsEnabled);
if(!buttonsEnabled){
Toast.makeText(this, R.string.tracklogger_waiting_gps, Toast.LENGTH_LONG).show();
}
mAudioManager.registerMediaButtonEventReceiver(mediaButtonReceiver);
super.onResume();
}
private void checkGPSProvider() {
LocationManager lm = (LocationManager) getSystemService(LOCATION_SERVICE);
if (!lm.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
// GPS isn't enabled. Offer user to go enable it
new AlertDialog.Builder(this)
.setTitle(R.string.tracklogger_gps_disabled)
.setIcon(android.R.drawable.ic_dialog_alert)
.setMessage(getResources().getString(R.string.tracklogger_gps_disabled_hint))
.setCancelable(true).setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
}
}).setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
}).create().show();
checkGPSFlag = false;
}
}
@Override
protected void onPause() {
// Un-register GPS status update for upper controls
((GpsStatusRecord) findViewById(R.id.gpsStatus)).requestLocationUpdates(false);
if (gpsLogger != null) {
if (!gpsLogger.isTracking()) {
Log.v(TAG, "Service is not tracking, trying to stopService()");
unbindService(gpsLoggerConnection);
stopService(gpsLoggerServiceIntent);
} else {
unbindService(gpsLoggerConnection);
}
}
if (sensorListener!=null) {
sensorListener.unregister();
}
mAudioManager.unregisterMediaButtonEventReceiver(mediaButtonReceiver);
super.onPause();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
// Save the fact that we are currently tracking or not
if(gpsLogger != null){
outState.putBoolean(STATE_IS_TRACKING, gpsLogger.isTracking());
}
outState.putBoolean(STATE_BUTTONS_ENABLED, buttonsEnabled);
super.onSaveInstanceState(outState);
}
/**
* Called when GPS is disabled
*/
public void onGpsDisabled() {
// GPS disabled. Grey all.
setEnabledActionButtons(false);
}
/**
* Called when GPS is enabled
*/
public void onGpsEnabled() {
// Buttons can be enabled
if (gpsLogger != null && gpsLogger.isTracking()) {
setEnabledActionButtons(true);
}
}
/**
* Enable buttons associated to tracking
*
* @param enabled true to enable, false to disable
*/
public void setEnabledActionButtons(boolean enabled) {
if (mainLayout != null) {
buttonsEnabled = enabled;
mainLayout.setEnabled(enabled);
}
}
// Create options menu
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.tracklogger_menu, menu);
return true;
}
// Manage options menu selections
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Intent i;
switch (item.getItemId()) {
case R.id.tracklogger_menu_stoptracking:
// Start / Stop tracking
if (gpsLogger.isTracking()) {
Intent intent = new Intent(OSMTracker.INTENT_STOP_TRACKING);
sendBroadcast(intent);
((GpsStatusRecord) findViewById(R.id.gpsStatus)).manageRecordingIndicator(false);
finish();
}
break;
case R.id.tracklogger_menu_settings:
// Start settings activity
startActivity(new Intent(this, Preferences.class));
break;
case R.id.tracklogger_menu_waypointlist:
// Start Waypoint list activity
i = new Intent(this, WaypointList.class);
i.putExtra(Schema.COL_TRACK_ID, currentTrackId);
startActivity(i);
break;
case R.id.tracklogger_menu_about:
// Start About activity
startActivity(new Intent(this, About.class));
break;
case R.id.tracklogger_menu_displaytrack:
// Start display track activity, with or without OSM background
boolean useOpenStreetMapBackground = PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
OSMTracker.Preferences.KEY_UI_DISPLAYTRACK_OSM, OSMTracker.Preferences.VAL_UI_DISPLAYTRACK_OSM);
if (useOpenStreetMapBackground) {
i = new Intent(this, DisplayTrackMap.class);
} else {
i = new Intent(this, DisplayTrack.class);
}
i.putExtra(Schema.COL_TRACK_ID, currentTrackId);
startActivity(i);
break;
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
// Manage back button if we are on a sub-page
if (event.getRepeatCount() == 0) {
if (mainLayout != null && mainLayout.getStackSize() > 1) {
mainLayout.pop();
return true;
}
}
break;
case KeyEvent.KEYCODE_CAMERA:
if (gpsLogger.isTracking()) {
requestStillImage();
return true;
}
break;
case KeyEvent.KEYCODE_DPAD_CENTER:
// API Level 3 doesn't support long presses, so we need to do this manually
if((gpsLogger != null && gpsLogger.isTracking()) && (event.getEventTime() - event.getDownTime()) > OSMTracker.LONG_PRESS_TIME){
// new long press of dpad center detected, start voice recording dialog
this.showDialog(DIALOG_VOICE_RECORDING);
return true;
}
break;
case KeyEvent.KEYCODE_HEADSETHOOK:
if (gpsLogger != null && gpsLogger.isTracking()){
this.showDialog(DIALOG_VOICE_RECORDING);
return true;
}
}
return super.onKeyDown(keyCode, event);
}
/**
* Request a still picture from the camera application, saving the file in
* the current track directory
*/
public void requestStillImage() {
if (gpsLogger.isTracking()) {
final File imageFile = pushImageFile();
if (null != imageFile) {
final String pictureSource = prefs.getString(OSMTracker.Preferences.KEY_UI_PICTURE_SOURCE,
OSMTracker.Preferences.VAL_UI_PICTURE_SOURCE);
if (OSMTracker.Preferences.VAL_UI_PICTURE_SOURCE_CAMERA.equals(pictureSource)) {
startCamera(imageFile);
} else if (OSMTracker.Preferences.VAL_UI_PICTURE_SOURCE_GALLERY.equals(pictureSource)) {
startGallery();
} else {
// Let the user choose between using the camera
// or selecting a picture from the gallery
AlertDialog.Builder getImageFrom = new AlertDialog.Builder(TrackLogger.this);
getImageFrom.setTitle("Select:");
final CharSequence[] opsChars = { getString(R.string.tracklogger_camera), getString(R.string.tracklogger_gallery) };
getImageFrom.setItems(opsChars, new android.content.DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == 0) {
startCamera(imageFile);
} else if (which == 1) {
startGallery();
}
dialog.dismiss();
}
});
getImageFrom.show();
}
} else {
Toast.makeText(getBaseContext(),
getResources().getString(R.string.error_externalstorage_not_writable),
Toast.LENGTH_SHORT).show();
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.v(TAG, "Activity result: " + requestCode + ", resultCode=" + resultCode + ", Intent=" + data);
switch (requestCode) {
case REQCODE_IMAGE_CAPTURE:
if (resultCode == RESULT_OK) {
// A still image has been captured, track the corresponding waypoint
// Send an intent to inform service to track the waypoint.
File imageFile = popImageFile();
if (imageFile != null) {
Intent intent = new Intent(OSMTracker.INTENT_TRACK_WP);
intent.putExtra(Schema.COL_TRACK_ID, currentTrackId);
intent.putExtra(OSMTracker.INTENT_KEY_NAME, getResources().getString(R.string.wpt_stillimage));
intent.putExtra(OSMTracker.INTENT_KEY_LINK, imageFile.getName());
sendBroadcast(intent);
}
}
break;
case REQCODE_GALLERY_CHOSEN:
if (resultCode == RESULT_OK) {
// An image has been selected from the gallery, track the corresponding waypoint
File imageFile = popImageFile();
if (imageFile != null) {
// Copy the file from the gallery
Cursor c = getContentResolver().query(data.getData(), null, null, null, null);
c.moveToFirst();
String f = c.getString(c.getColumnIndex(ImageColumns.DATA));
c.close();
Log.d(TAG, "Copying gallery file '"+f+"' into '"+imageFile+"'");
FileSystemUtils.copyFile(imageFile.getParentFile(), new File(f), imageFile.getName());
// Send an intent to inform service to track the waypoint.
Intent intent = new Intent(OSMTracker.INTENT_TRACK_WP);
intent.putExtra(Schema.COL_TRACK_ID, currentTrackId);
intent.putExtra(OSMTracker.INTENT_KEY_NAME, getResources().getString(R.string.wpt_stillimage));
intent.putExtra(OSMTracker.INTENT_KEY_LINK, imageFile.getName());
sendBroadcast(intent);
}
}
}
super.onActivityResult(requestCode, resultCode, data);
}
/**
* Getter for gpsLogger
*
* @return Activity {@link GPSLogger}
*/
public GPSLogger getGpsLogger() {
return gpsLogger;
}
/**
* Setter for gpsLogger
*
* @param l
* {@link GPSLogger} to set.
*/
public void setGpsLogger(GPSLogger l) {
this.gpsLogger = l;
}
/**
* Gets a File for storing an image in the current track dir
* and stores it in a class variable.
*
* @return A File pointing to an image file inside the current track directory
*/
public File pushImageFile() {
currentImageFile = null;
// Query for current track directory
File trackDir = DataHelper.getTrackDirectory(currentTrackId);
// 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()) {
currentImageFile = new File(trackDir,
DataHelper.FILENAME_FORMATTER.format(new Date()) + DataHelper.EXTENSION_JPG);
} else {
Log.w(TAG, "The directory [" + trackDir.getAbsolutePath() + "] will not allow files to be created");
}
return currentImageFile;
}
/**
* @return The current image file, and clear the internal variable.
*/
public File popImageFile() {
File imageFile = currentImageFile;
currentImageFile = null;
return imageFile;
}
@Override
protected Dialog onCreateDialog(int id) {
switch(id){
case DIALOG_TEXT_NOTE:
// create a new TextNoteDialog
return new TextNoteDialog(this, currentTrackId);
case DIALOG_VOICE_RECORDING:
// create a new VoiceRegDialog
return new VoiceRecDialog(this, currentTrackId);
}
return super.onCreateDialog(id);
}
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
switch(id){
case DIALOG_TEXT_NOTE:
// we need to reset Values like uuid of the dialog,
// otherwise we would overwrite an existing waypoint
((TextNoteDialog)dialog).resetValues();
break;
}
super.onPrepareDialog(id, dialog);
}
@Override
protected void onNewIntent(Intent newIntent) {
if (newIntent.getExtras() != null) {
if (newIntent.getExtras().containsKey(Schema.COL_TRACK_ID)) {
currentTrackId = newIntent.getExtras().getLong(Schema.COL_TRACK_ID);
setIntent(newIntent);
}
if (newIntent.hasExtra("mediaButton") && gpsLogger != null && gpsLogger.isTracking()) {
this.showDialog(DIALOG_VOICE_RECORDING);
}
}
super.onNewIntent(newIntent);
}
public long getCurrentTrackId() {
return this.currentTrackId;
}
/**
* Starts the camera app. to take a picture
* @param imageFile File to save the picture to
*/
private void startCamera(File imageFile) {
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(imageFile));
startActivityForResult(cameraIntent, REQCODE_IMAGE_CAPTURE);
}
/**
* Starts the gallery app. to choose a picture
*/
private void startGallery() {
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent, getString(R.string.tracklogger_choose_gallery_camera)), REQCODE_GALLERY_CHOSEN);
}
}