package com.droidmapper;
import android.app.Activity;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentSender;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.hardware.Camera;
import android.location.Location;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.SystemClock;
import android.provider.MediaStore;
import android.text.Html;
import android.util.Log;
import android.view.OrientationEventListener;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;
import com.dropbox.client2.DropboxAPI;
import com.dropbox.client2.android.AndroidAuthSession;
import com.dropbox.client2.session.AppKeyPair;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.droidmapper.util.Constants;
import com.droidmapper.util.DropboxUploaderThread;
import com.droidmapper.util.GpsUtil;
import com.droidmapper.util.PhotoProcessorThread;
import com.droidmapper.util.Util;
import com.droidmapper.view.CameraView;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* This activity creates the camera screen GUI on the device's screen and handles user input. Its
* purpose is to take photos with back camera in the specified interval and to send taken photos
* to the PhotoProcessorThread for further processing.
*/
public class CameraActivity extends Activity implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
// Constants used as keys for the extras passed to this activity:
public static final String EXTRA_DB_OAUTH2_ACCESS_TOKEN = CameraActivity.class.getName() + "EXTRA_DB_OAUTH2_ACCESS_TOKEN";
public static final String EXTRA_INTERVAL_TYPE = CameraActivity.class.getName() + "EXTRA_INTERVAL_TYPE";
public static final String EXTRA_INTERVAL = CameraActivity.class.getName() + "EXTRA_INTERVAL";
public static final String EXTRA_DELAY = CameraActivity.class.getName() + "EXTRA_DELAY";
public static final String EXTRA_SIZE = CameraActivity.class.getName() + "EXTRA_SIZE";
// Interval type constants:
public static final int INTERVAL_TYPE_DISTANCE = 1;
public static final int INTERVAL_TYPE_TIME = 2;
// Request code to use when launching the Google Play Services API resolution activity:
private static final int REQUEST_RESOLVE_ERROR = 1001;
// Unique tag for the Google Play Services API error dialog fragment:
private static final String DIALOG_ERROR = "dialog_error";
// Key used to preserve the state of the resolvingError field between activity restarts:
private static final String STATE_RESOLVING_ERROR = "resolving_error";
private static final String TAG = CameraActivity.class.getName();
// Views:
private TextView textViewLat, textViewLong, textViewAlt, textViewSpd, textViewPhoto;
private CameraView cameraView;
private Button buttonStop;
// Dropbox API:
private DropboxAPI<AndroidAuthSession> dropboxApi;
private String dbOauth2AccessToken;
// Util threads:
//private PhotoProcessorThread photoProcsThread;
private DropboxUploaderThread dbUpldrThread;
// Location:
private GoogleApiClient googleApiClient;
private boolean resolvingError;
private Location lastLocation;
// Other:
private SimpleDateFormat dateFormat, exifGpsDateFormat, exifDateFormat;
private OrientationEventListener orientationListener;
private volatile long takePicInvocTimestamp;
private int interval, intervalType, delay;
private int devOrien, devOrienAtCapture;
private File mediaStorageDir;
private Handler handler;
private float size;
// TODO: Note that currently util threads, after receiving stop command, stop immediately, they do not finish queued tasks.
// TODO: If this is unwanted, because the app might/will lose a few photos, they should be modified to first finish queued tasks and then exit.
/**
* A framework method that is invoked by the system when this activity is first created. It sets
* up its GUI, retrieves the extras from the Intent that started this activity and initializes
* the Dropbox API.
*
* @param savedInstanceState If the activity is being re-initialized after previously being shut
* down then this Bundle contains the data it most recently supplied
* in onSaveInstanceState(Bundle), otherwise it is <b>null</b>.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Get the extras from the intent that started this activity:
Intent intent = getIntent();
if (!intent.hasExtra(EXTRA_DB_OAUTH2_ACCESS_TOKEN)) {
throw new IllegalArgumentException("EXTRA_DB_OAUTH2_ACCESS_TOKEN was not found in the intent that started this activity!");
} else if (!intent.hasExtra(EXTRA_INTERVAL_TYPE)) {
throw new IllegalArgumentException("EXTRA_INTERVAL_TYPE was not found in the intent that started this activity!");
} else if (!intent.hasExtra(EXTRA_INTERVAL)) {
throw new IllegalArgumentException("EXTRA_INTERVAL was not found in the intent that started this activity!");
} else if (!intent.hasExtra(EXTRA_DELAY)) {
throw new IllegalArgumentException("EXTRA_DELAY was not found in the intent that started this activity!");
} else if (!intent.hasExtra(EXTRA_SIZE)) {
throw new IllegalArgumentException("EXTRA_SIZE was not found in the intent that started this activity!");
} else {
dbOauth2AccessToken = intent.getStringExtra(EXTRA_DB_OAUTH2_ACCESS_TOKEN);
intervalType = intent.getIntExtra(EXTRA_INTERVAL_TYPE, -1);
interval = intent.getIntExtra(EXTRA_INTERVAL, -1);
delay = intent.getIntExtra(EXTRA_DELAY, -1);
size = intent.getFloatExtra(EXTRA_SIZE, 0F);
}
// Inflates the GUI defined in the XML file:
setContentView(R.layout.activity_camera);
// We need to keep the screen on in order for camera to work:
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
// Get references to views defined in the GUI:
textViewLat = (TextView) findViewById(R.id.activityCamera_textViewLat);
textViewLong = (TextView) findViewById(R.id.activityCamera_textViewLong);
textViewAlt = (TextView) findViewById(R.id.activityCamera_textViewAlt);
textViewSpd = (TextView) findViewById(R.id.activityCamera_textViewSpd);
textViewPhoto = (TextView) findViewById(R.id.activityCamera_textViewPhoto);
cameraView = (CameraView) findViewById(R.id.activityCamera_cameraView);
buttonStop = (Button) findViewById(R.id.activityCamera_buttonStop);
// Listen for clicks on the stop button:
buttonStop.setOnClickListener(onClickListener);
// Set text view default texts:
textViewLat.setText(Html.fromHtml(getString(R.string.activityCamera_textViewLat, "")));
textViewLong.setText(Html.fromHtml(getString(R.string.activityCamera_textViewLong, "")));
textViewAlt.setText(Html.fromHtml(getString(R.string.activityCamera_textViewAlt, "")));
textViewSpd.setText(Html.fromHtml(getString(R.string.activityCamera_textViewSpd, "")));
textViewPhoto.setText(Html.fromHtml(getString(R.string.activityCamera_textViewPhoto, "")));
// Initialize the Handler instance. We use it to schedule tasks to run on the GUI thread at
// some point in future.
handler = new Handler();
// Connect to Google Play Service in order to use Fused Location Provider to geo-tag taken photos:
googleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
cameraView.setGeoTaggingLocation(lastLocation);
// Restore the state of the resolvingError variable after activity restart:
if (savedInstanceState != null) {
resolvingError = savedInstanceState.getBoolean(STATE_RESOLVING_ERROR, false);
}
// Initialize the Dropbox API:
AppKeyPair appKeys = new AppKeyPair(Constants.APP_KEY, Constants.APP_SECRET);
AndroidAuthSession session = new AndroidAuthSession(appKeys, dbOauth2AccessToken);
dropboxApi = new DropboxAPI<AndroidAuthSession>(session);
// Create(if it does not exist) and initialize the directory in which the images will be saved:
File picsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
mediaStorageDir = new File(picsDir, getString(R.string.app_name));
if (!mediaStorageDir.exists()) {
mediaStorageDir.mkdirs();
}
// Create a date format using which we will format photos timestamps and create their file
// names:
dateFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss_SSS");
// And date formats for exif tags:
exifGpsDateFormat = new SimpleDateFormat("yyyy:MM:dd", Locale.ENGLISH);
exifDateFormat = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
// We need to listen for device orientation changes in order to know when it is held in
// portrait and when in landscape so that we could properly rotate the captured photos:
orientationListener = new OrientationEventListener(this) {
@Override
public void onOrientationChanged(int orientation) {
if (orientation != ORIENTATION_UNKNOWN) {
// Clamp the device orientation:
int degrees = 0;
if (orientation <= 45 || orientation > 315) {
degrees = 0;
} else if (orientation > 45 && orientation <= 135) {
degrees = 90;
} else if (orientation > 135 && orientation <= 225) {
degrees = 180;
} else {
degrees = 270;
}
devOrien = degrees;
}
}
};
}
/**
* Called after onCreate(Bundle) — or after onRestart() when the activity had been stopped,
* but is now again being displayed to the user. It delays the start of photo capturing by
* the amount of time specified in the Intent that started this activity.
*/
@Override
public void onStart() {
super.onStart();
if (!resolvingError) {
googleApiClient.connect();
}
// Start the thread that will upload the saved photos to Dropbox:
dbUpldrThread = new DropboxUploaderThread(size, dropboxApi);
dbUpldrThread.start();
// Start the thread that will save the photo data to external storage:
//photoProcsThread = new PhotoProcessorThread(this, dbUpldrThread);
//photoProcsThread.start();
if (intervalType == INTERVAL_TYPE_TIME) {
// Delay the start of photo taking:
handler.postDelayed(delayPhotoTakingRunnable, delay);
}
// Start listening for rotation changes:
orientationListener.enable();
}
/**
* Called when you the activity is no longer visible to the user. It cancels the scheduled
* photo capture.
*/
@Override
public void onStop() {
// User is leaving the screen, cancel the photo taking:
handler.removeCallbacks(delayPhotoTakingRunnable);
// Stop the photo processor thread:
/*if (photoProcsThread != null) {
photoProcsThread.halt();
photoProcsThread = null;
}*/
// Stop the Dropbox uploader thread:
if (dbUpldrThread != null) {
dbUpldrThread.halt();
dbUpldrThread = null;
}
// Stop listening for rotation changes:
orientationListener.disable();
if (googleApiClient.isConnected()) {
LocationServices.FusedLocationApi.removeLocationUpdates(googleApiClient, locationListener);
}
googleApiClient.disconnect();
super.onStop();
}
/**
* Called by the system before the activity may be killed so that when it comes back some time
* in the future it can restore its state.
*
* @param outState Bundle in which the state is saved.
*/
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(STATE_RESOLVING_ERROR, resolvingError);
}
/**
* Called when an activity launched by this activity exits, giving us the requestCode we started
* it with, the resultCode it returned, and any additional data from it. The resultCode will be
* RESULT_CANCELED if the activity explicitly returned that, didn't return any result, or
* crashed during its operation.
*
* @param requestCode The integer request code originally supplied to startActivityForResult(),
* allowing us to identify who this result came from.
* @param resultCode The integer result code returned by the child activity through its
* setResult().
* @param data An Intent, which can return result data to the caller.
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_RESOLVE_ERROR) {
resolvingError = false;
if (resultCode == RESULT_OK) {
// Make sure the app is not already connected or attempting to connect:
if (!googleApiClient.isConnecting() && !googleApiClient.isConnected()) {
googleApiClient.connect();
}
}
}
}
/**
* After calling connect() on GoogleApiClient, this method will be invoked asynchronously when
* the connect request has successfully completed.
*
* @param bundle Bundle of data provided to clients by Google Play services. May be null if no
* content is provided by the service.
*/
@Override
public void onConnected(Bundle bundle) {
lastLocation = LocationServices.FusedLocationApi.getLastLocation(googleApiClient);
if (lastLocation != null) {
cameraView.setGeoTaggingLocation(lastLocation);
}
updateShownLocationDataHelper();
// Request location updates from Google Play Services Fused Provider:
if (intervalType == INTERVAL_TYPE_DISTANCE) {
LocationRequest locationRequest = new LocationRequest();
locationRequest.setInterval(1000L);
locationRequest.setFastestInterval(1000L);
locationRequest.setSmallestDisplacement(interval);
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, locationListener);
} else {
LocationRequest locationRequest = new LocationRequest();
locationRequest.setInterval(1000L);
locationRequest.setFastestInterval(1000L);
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, locationListener);
}
}
/**
* Called when the Google Play Services client is temporarily in a disconnected state.
*
* @param i The reason for the disconnection.
*/
@Override
public void onConnectionSuspended(int i) {
}
/**
* Called when there was an error connecting the Google Play Services client to the service.
*
* @param connectionResult A ConnectionResult that can be used for resolving the error, and
* deciding what sort of error occurred.
*/
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
if (resolvingError) {
// Already attempting to resolve an error.
return;
} else if (connectionResult.hasResolution()) {
try {
resolvingError = true;
connectionResult.startResolutionForResult(this, REQUEST_RESOLVE_ERROR);
} catch (IntentSender.SendIntentException e) {
// There was an error with the resolution intent. Try again.
googleApiClient.connect();
}
} else {
// Show dialog using GooglePlayServicesUtil.getErrorDialog():
// Create a fragment for the error dialog:
ErrorDialogFragment dialogFragment = new ErrorDialogFragment();
// Pass the error that should be displayed:
Bundle args = new Bundle();
args.putInt(DIALOG_ERROR, connectionResult.getErrorCode());
dialogFragment.setArguments(args);
dialogFragment.show(getFragmentManager(), DIALOG_ERROR);
resolvingError = true;
}
}
/**
* A helper method that updates the on-screen filename of the last captured image in the GUI thread.
*
* @param filename of the last captured image.
*/
public void postLastCapturedPhotoFilenameUpdate(final String filename) {
handler.post(new Runnable() {
@Override
public void run() {
textViewPhoto.setText(Html.fromHtml(getString(R.string.activityCamera_textViewPhoto, filename)));
}
});
}
/**
* A helper method that updates the on-screen texts with new location data.
*/
private void updateShownLocationDataHelper() {
if (lastLocation != null) {
double lat = lastLocation.getLatitude();
double lon = lastLocation.getLongitude();
double alt = lastLocation.getAltitude();
float spd = lastLocation.getSpeed();
textViewLat.setText(Html.fromHtml(getString(R.string.activityCamera_textViewLat, String.valueOf(lat))));
textViewLong.setText(Html.fromHtml(getString(R.string.activityCamera_textViewLong, String.valueOf(lon))));
textViewAlt.setText(Html.fromHtml(getString(R.string.activityCamera_textViewAlt, String.valueOf(Math.round(alt)))));
textViewSpd.setText(Html.fromHtml(getString(R.string.activityCamera_textViewSpd, String.valueOf(Math.round(spd)))));
}
}
/**
* An instance of the OnClickListener interface that provides a callback method to the start
* Button view in this activity's GUI, so that it can inform the activity when the user has
* clicked on it.
*/
private View.OnClickListener onClickListener = new View.OnClickListener() {
/**
* Called when a view has been clicked.
*
* @param view The view that was clicked.
*/
@Override
public void onClick(View view) {
if (view == buttonStop) {
// User is leaving the screen, cancel the photo taking:
handler.removeCallbacks(delayPhotoTakingRunnable);
// Close the activity:
finish();
}
}
};
/**
* A runnable instance used to schedule photo capturing.
*/
private Runnable delayPhotoTakingRunnable = new Runnable() {
/**
* A callback method which invokes photo capture.
*/
@Override
public void run() {
// If the activity is not being closed, record the current system time and take a
// picture:
if (!isFinishing()) {
takePicInvocTimestamp = SystemClock.elapsedRealtime();
cameraView.takePicture(pictureCallback);
devOrienAtCapture = devOrien;
}
}
};
/**
* An instance of the PictureCallback interface which callback method is invoked by the underling
* API to send us the image data of the captured photo.
*/
private Camera.PictureCallback pictureCallback = new Camera.PictureCallback() {
/**
* Called when image data is available after a picture is taken.
*
* @param data Byte array of the picture encoded as JPG.
* @param camera The camera that took the photo.
*/
@Override
public void onPictureTaken(byte[] data, Camera camera) {
// Log.d(TAG, "pictureCallback.onPictureTaken() :: devOrienAtCapture = " + devOrienAtCapture);
/*
* A choice was made here between two options:<br>
* 1) Write the taken photo to the local storage in the GUI thread, and avoid possible
* OutOfMemoryErrors but also possibly delay the capture of the next image by a second,<br>
* 2) Make a copy of the captured image data and send it to a background thread which
* is supposed to write it to the local storage. But by doing that risk an OutOfMemoryError.<br>
* <br>
* The second approach is better if the captured image isn't in "high resolution".
* Theoretically it would also work for "high resolution" images too but only if the
* android:largeHeap="true" attribute is set in the <application></application> tag of
* the AndroidManifest.xml file.<br>
* Otherwise,the first method is much better because its simpler, less error prone and
* doesn't use as much memory as the second one.
*/
// 1) Write the taken photo in the current thread:
// Create its file:
String tsText = dateFormat.format(new Date(System.currentTimeMillis()));
String filename = tsText + ".jpg";
String filePath = mediaStorageDir.getPath() + File.separator + filename;
File photoFile = new File(filePath);
// If needed, fix the photo rotation:
BitmapFactory.Options bfOptions = new BitmapFactory.Options();
bfOptions.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, bfOptions);
// Log.d(TAG, "pictureCallback.onPictureTaken() :: img wxh = " + bfOptions.outWidth + "x" + bfOptions.outHeight);
if ((devOrienAtCapture == 0 || devOrienAtCapture == 180) && bfOptions.outWidth > bfOptions.outHeight) {
Bitmap src = BitmapFactory.decodeByteArray(data, 0, data.length);
Matrix matrix = new Matrix();
matrix.postRotate(90);
Bitmap out = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
out.compress(Bitmap.CompressFormat.JPEG, 100, baos);
data = baos.toByteArray();
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// Write photo data to the created file:
FileOutputStream fos = null;
try {
fos = new FileOutputStream(photoFile);
fos.write(data);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// Add EXIF data to the captured photo:
Date date = new Date();
try {
ExifInterface exif = new ExifInterface(filePath);
if (lastLocation != null) {
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, GpsUtil.convert(lastLocation.getLatitude()));
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, GpsUtil.latitudeRef(lastLocation.getLatitude()));
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, GpsUtil.convert(lastLocation.getLongitude()));
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, GpsUtil.longitudeRef(lastLocation.getLongitude()));
double alt = lastLocation.getAltitude();
if (alt >= 0) {
exif.setAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF, String.valueOf(0));
} else {
exif.setAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF, String.valueOf(1));
}
exif.setAttribute(ExifInterface.TAG_GPS_ALTITUDE, String.valueOf(Math.round(alt)));
exif.setAttribute(ExifInterface.TAG_GPS_DATESTAMP, exifGpsDateFormat.format(date));
exif.setAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD, lastLocation.getProvider());
}
exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, String.valueOf(bfOptions.outWidth));
exif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, String.valueOf(bfOptions.outHeight));
exif.setAttribute(ExifInterface.TAG_DATETIME, exifDateFormat.format(date));
exif.setAttribute(ExifInterface.TAG_MAKE, Build.MANUFACTURER);
exif.setAttribute(ExifInterface.TAG_MODEL, Build.MODEL);
Camera.Parameters camParams = cameraView.getCameraParams();
String fm = camParams.getFlashMode();
if (fm == null || fm.equals(Camera.Parameters.FLASH_MODE_OFF)) {
exif.setAttribute(ExifInterface.TAG_FLASH, String.valueOf(0));
} else {
exif.setAttribute(ExifInterface.TAG_FLASH, String.valueOf(0));
}
float fl = camParams.getFocalLength();
exif.setAttribute(ExifInterface.TAG_FOCAL_LENGTH, String.valueOf(fl));
String wb = camParams.getWhiteBalance();
if (wb != null) {
if (wb.equals(Camera.Parameters.WHITE_BALANCE_AUTO)) {
exif.setAttribute(ExifInterface.TAG_WHITE_BALANCE, String.valueOf(ExifInterface.WHITEBALANCE_AUTO));
} else {
exif.setAttribute(ExifInterface.TAG_WHITE_BALANCE, String.valueOf(ExifInterface.WHITEBALANCE_MANUAL));
}
}
String ap = camParams.get("aperture");
if (ap != null) {
exif.setAttribute(ExifInterface.TAG_APERTURE, ap);
}
// TAG_EXPOSURE_TIME TAG_ISO
exif.saveAttributes();
} catch (IOException e) {
e.printStackTrace();
}
// Add the saved photo to the device gallery:
try {
String urlToAddedImage = MediaStore.Images.Media.insertImage(getContentResolver(), filePath, filename, getString(R.string.ppThread_photo_description));
Log.d(TAG, "pictureCallback.onPictureTaken() :: urlToAddedImage = " + urlToAddedImage);
String pathToAddedImage = Util.getFilePathFromUri(CameraActivity.this, Uri.parse(urlToAddedImage));
Log.d(TAG, "pictureCallback.onPictureTaken() :: pathToAddedImage = " + pathToAddedImage);
Util.copyExifTags(filePath, pathToAddedImage, bfOptions.outWidth, bfOptions.outHeight);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// Upload the saved photo to Dropbox:
dbUpldrThread.queuePhoto(filePath);
postLastCapturedPhotoFilenameUpdate(filename);
// 2) Send the captured photo data to another thread to save it on external storage:
// photoProcsThread.setCamParams(cameraView.getCameraParams());
// byte[] dataCopy = new byte[data.length];
// System.arraycopy(data, 0, dataCopy, 0, data.length);
// photoProcsThread.queuePhoto(dataCopy, System.currentTimeMillis(), devOrienAtCapture, lastLocation);
// Restart camera preview:
cameraView.restartPreview();
// If the activity is not being closed and selected interval type is time,
// schedule another photo capture:
if (!isFinishing() && intervalType == INTERVAL_TYPE_TIME) {
long schdlNextPicIn = (takePicInvocTimestamp + interval) - SystemClock.elapsedRealtime();
Log.d(TAG, "pictureCallback.onPictureTaken() :: Current time is " + SystemClock.elapsedRealtime() + " schedule pic in " + schdlNextPicIn);
if (schdlNextPicIn < 1L) {
schdlNextPicIn = 1L;
}
handler.postDelayed(delayPhotoTakingRunnable, schdlNextPicIn);
}
}
};
/**
* An instance of the LocationListener interface whose callback method is invoked by the
* Fused Provider API when a new location fix is acquired.
*/
private LocationListener locationListener = new LocationListener() {
/**
* A callback method that will be called to notify the listener that device location has
* changed.
*
* @param location Location acquired from any the Fused Provider.
*/
@Override
public void onLocationChanged(Location location) {
// Update the location the camera uses to geo tag images:
Log.d(TAG, "locationListener.onLocationChanged() :: location = " + location);
updateShownLocationDataHelper();
if (intervalType == INTERVAL_TYPE_DISTANCE && location != null) {
if (lastLocation == null) {
lastLocation = location;
cameraView.setGeoTaggingLocation(location);
takePicInvocTimestamp = SystemClock.elapsedRealtime();
cameraView.takePicture(pictureCallback);
devOrienAtCapture = devOrien;
} else {
float distance = lastLocation.distanceTo(location);
Log.d(TAG, "locationListener.onLocationChanged() :: distance = " + distance);
if (distance >= interval) {
lastLocation = location;
cameraView.setGeoTaggingLocation(location);
takePicInvocTimestamp = SystemClock.elapsedRealtime();
cameraView.takePicture(pictureCallback);
devOrienAtCapture = devOrien;
}
}
} else {
lastLocation = location;
// Update the location the camera uses to geo tag images:
cameraView.setGeoTaggingLocation(location);
}
}
};
/**
* A fragment to display a Google Play Services error dialog.
*/
public static class ErrorDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Get the error code and retrieve the appropriate dialog:
int errorCode = this.getArguments().getInt(DIALOG_ERROR);
return GooglePlayServicesUtil.getErrorDialog(errorCode, this.getActivity(), REQUEST_RESOLVE_ERROR);
}
@Override
public void onDismiss(DialogInterface dialog) {
((CameraActivity) getActivity()).resolvingError = false;
}
}
}