Back to project page android-accelerometer-gps.
The source code is released under:
MIT License
If you think the Android project android-accelerometer-gps listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
/* Think I'm going to do away with this as a service. If I want the UI to be more responsive while polling, this should run in another thread. Easiest way to do that is to stop using it as a service. Instead, it'll be instantiated when the broadcast receiver gets triggered, and stored in the MainActivity. /*from w ww . j ava 2s. c om*/ I'm not interested in having a long-running notification, though I may create one whenever the state is RUNNING. If the app gets killed, the alarm may persist and start things up again. Not certain. */ package com.szlosek.whenmoving; import java.lang.Math; import java.util.ArrayList; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Locale; import java.util.TimeZone; import android.app.AlarmManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.os.IBinder; import android.text.format.DateFormat; import android.util.Log; public class MainService extends Service implements SensorEventListener, LocationListener { // NOTIFICATION RELATED // The PendingIntent to launch our activity if the user selects this notification protected PendingIntent mPendingIntent; protected int iNotificationId = 123; protected int iLocations = 0; protected float fSince = 0; protected int iIntervals = 0; protected Notification mNotification; // INTERNAL STATE private boolean moving = false; // Accelerometer-related private int iAccelReadings, iAccelSignificantReadings; private long iAccelTimestamp; private SensorManager mSensorManager; // GPS-related private static final int LOCATION_BUFFER = 5; //private static final int BETWEEN_GPS = 30; private static final int TWO_MINUTES = 1000 * 60 * 2; public static Location currentBestLocation; private long lGPSTimestamp; private LocationManager mLocationManager = null; private Location bestLocation = null; private PendingIntent pi; private CircularBuffer locations; private boolean cellOnly = false; private float lowestAccuracy; // ACCELEROMETER METHODS public void startAccelerometer() { iAccelReadings = 0; iAccelSignificantReadings = 0; iAccelTimestamp = System.currentTimeMillis(); // should probably store handles to these earlier, when service is created mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); Sensor mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL); } public void stopAccelerometer() { mSensorManager.unregisterListener(this); } @Override public void onSensorChanged(SensorEvent event) { double accel, x, y, z; if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) { return; } iAccelReadings++; x = event.values[0]; y = event.values[1]; z = event.values[2]; accel = Math.abs( Math.sqrt( Math.pow(x,2) + Math.pow(y,2) + Math.pow(z,2) ) ); // Was 0.6. Lowered to 0.3 (plus gravity) to account for smooth motion from Portland Streetcar if (accel > 10.1) { iAccelSignificantReadings++; } //Debug(String.format("event: %f %f %f %f %f", x, y, z, accel, 0.600)); // Get readings for 1 second // Maybe we should sample for longer given that I've lowered the threshold if ( (System.currentTimeMillis() - iAccelTimestamp) < 2000) return; stopAccelerometer(); Debug(String.format("Accelerometer readings: %d Significant: %d", iAccelReadings, iAccelSignificantReadings)); // Appeared to be moving 30% of the time? // If the bar is this low, why not report motion at the first significant reading and be done with it? if (((1.0*iAccelSignificantReadings) / iAccelReadings) > 0.30) { setMoving(true); Debug("Moving"); // Get new lock for GPS so we can turn off screen MainApplication.wakeLock2(true); MainApplication.wakeLock1(false); // Start GPS startGPS(); } else { setMoving(false); Debug("Stationary"); sleep(); MainApplication.wakeLock1(false); } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { // can be safely ignored } // GPS METHODS public void startGPS() { // Set timeout for 30 seconds AlarmManager mgr = null; Intent i = null; GregorianCalendar cal = null; int iProviders = 0; // Make sure at least one provider is available if (mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 500, 0, this); iProviders++; } if (mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 500, 0, this); iProviders++; if (iProviders == 1) { cellOnly = true; } } if (iProviders == 0) { Debug("No providers available"); sleep(); MainApplication.wakeLock2(false); return; } lGPSTimestamp = System.currentTimeMillis(); lowestAccuracy = 9999; mgr = (AlarmManager)getSystemService(ALARM_SERVICE); cal = new GregorianCalendar(); i = new Intent(this, TimeoutReceiver.class); this.pi = PendingIntent.getBroadcast(this, 0, i, 0); cal.add(Calendar.SECOND, MainApplication.prefTimeout); mgr.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), this.pi); locations = new CircularBuffer(MainService.LOCATION_BUFFER); } public void stopGPS() { lGPSTimestamp = 0; if (this.pi != null) { AlarmManager mgr = (AlarmManager)getSystemService(ALARM_SERVICE); mgr.cancel(this.pi); this.pi = null; } mLocationManager.removeUpdates(this); } @Override public void onLocationChanged(Location location) { int a = 10; int i; float accuracyDiff = 0; Debug(String.format( "lat: %f lon: %f acc: %f provider: %s", location.getLatitude(), location.getLongitude(), location.getAccuracy(), location.getProvider() )); // Determine whether to discard the location or not ... // hope this ends up being a quick calculation if (isBetterLocation(location, MainService.currentBestLocation)){ MainService.currentBestLocation = location; } locations.insert( location ); // Only care about circular buffer max/min compare if using gps // or maybe only if we have 5 if (cellOnly == false) { // Can't do much without significant measurement if (locations.size() < MainService.LOCATION_BUFFER) { return; } /* lowestAccuracy = Math.min(lowestAccuracy, location.getAccuracy() ); accuracyDiff = Math.abs( lowestAccuracy - location.getAccuracy() ); // Getting worse? accuracyDiff < 5 */ float minAccuracy, maxAccuracy; minAccuracy = 9999; maxAccuracy = 0; for (i = 0; i < locations.size(); i++) { Location l = (Location) locations.get(i); if (l == null) continue; minAccuracy = Math.min(minAccuracy, l.getAccuracy()); maxAccuracy = Math.max(maxAccuracy, l.getAccuracy()); } if (maxAccuracy - minAccuracy > 3) { return; } Debug(String.format("Not much change in last %d accuracies", MainService.LOCATION_BUFFER)); } stopGPS(); // THINGS I'D LIKE TO LOG // Compared to current millis, how old is this location? // How long does it take to get location saveLocation(); // Don't like this being hardcoded here ... need a better scheme for handling this // Would rather wait a minute between GPS attempts sleep(); MainApplication.wakeLock2(false); } protected void saveLocation() { ContentValues data; Location l; SQLiteOpenHelper dbHelper; SQLiteDatabase db; GregorianCalendar cal; l = MainService.currentBestLocation; if (l == null) { return; } dbHelper = new DatabaseHelper(MainService.this); db = dbHelper.getWritableDatabase(); cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"), Locale.US); cal.setTimeInMillis( l.getTime() ); data = new ContentValues(); data.put("day", DateFormat.format("yyyyMMdd", cal).toString() ); data.put("hour", DateFormat.format("hh", cal).toString() ); data.put("milliseconds", l.getTime()); data.put("longitude", l.getLongitude()); data.put("latitude", l.getLatitude()); data.put("altitude", l.getAltitude()); data.put("gpsStart", lGPSTimestamp); data.put("accuracy", l.getAccuracy()); data.put("bearing", l.getBearing()); data.put("speed", l.getSpeed()); data.put("provider", l.getProvider()); db.insert("locations", null, data); db.close(); dbHelper.close(); } @Override public void onStatusChanged(String provider, int status, Bundle extras) { Debug(String.format("onStatusChanged: %s status: %d", provider, status)); } @Override public void onProviderEnabled(String provider) { Debug(String.format("onProviderEnabled: %s", provider)); if (lGPSTimestamp == 0) { // Not currently interested return; } // If it's a provider we care about, and we're listening, listen! if (provider == LocationManager.GPS_PROVIDER || provider == LocationManager.NETWORK_PROVIDER) { mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 500, 0, this); } } @Override public void onProviderDisabled(String provider) { Debug(String.format("onProviderDisabled: %s", provider)); if (lGPSTimestamp == 0) { // Not currently interested return; } // If it's a provider we care about, and we're listening, listen! if (provider == LocationManager.GPS_PROVIDER && mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 500, 0, this); } else if (provider == LocationManager.NETWORK_PROVIDER && mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 500, 0, this); } } /** Determines whether one Location reading is better than the current Location fix * @param location The new Location that you want to evaluate * @param currentBestLocation The current Location fix, to which you want to compare the new one */ protected boolean isBetterLocation(Location location, Location currentBestLocation) { if (currentBestLocation == null) { // A new location is always better than no location return true; } // Check whether the new location fix is newer or older long timeDelta = location.getTime() - currentBestLocation.getTime(); boolean isSignificantlyNewer = timeDelta > TWO_MINUTES; boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES; boolean isNewer = timeDelta > 0; // If it's been more than two minutes since the current location, use the new location // because the user has likely moved if (isSignificantlyNewer) { return true; // If the new location is more than two minutes older, it must be worse } else if (isSignificantlyOlder) { return false; } // Check whether the new location fix is more or less accurate int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy()); boolean isLessAccurate = accuracyDelta > 0; boolean isMoreAccurate = accuracyDelta < 0; boolean isSignificantlyLessAccurate = accuracyDelta > 200; // Check if the old and new location are from the same provider boolean isFromSameProvider = isSameProvider(location.getProvider(), currentBestLocation.getProvider()); // Determine location quality using a combination of timeliness and accuracy if (isMoreAccurate) { return true; } else if (isNewer && !isLessAccurate) { return true; } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) { return true; } return false; } /** Checks whether two providers are the same */ private boolean isSameProvider(String provider1, String provider2) { if (provider1 == null) { return provider2 == null; } return provider1.equals(provider2); } // OTHER public void sleep() { // Check desired state if (MainApplication.trackingOn == false) { Debug("Tracking has been toggled off. Not scheduling any more wakeup alarms"); stopSelf(); // Tracking has been turned off, don't schedule any new alarms return; } AlarmManager mgr = (AlarmManager)getSystemService(ALARM_SERVICE); Intent i = new Intent(this, MainReceiver.class); Calendar cal = new GregorianCalendar(); Debug(String.format("Waiting %d seconds", MainApplication.prefInterval)); cal.add(Calendar.SECOND, MainApplication.prefInterval); this.pi = PendingIntent.getBroadcast(this, 0, i, 0); mgr.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), this.pi); } public void gpsTimeout() { Debug("GPS timeout"); stopGPS(); saveLocation(); sleep(); MainApplication.wakeLock2(false); } @Override public void onCreate() { super.onCreate(); MainApplication.mServiceInstance = this; Debug("Service.onCreate"); mPendingIntent = PendingIntent.getActivity( this, 0, new Intent(this, MainActivity.class), 0 ); mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); setMoving(false); startForeground(iNotificationId, mNotification); /* //HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND); HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND); thread.setDaemon(true); thread.start(); // Get the HandlerThread's Looper and use it for our Handler mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); */ } @Override public int onStartCommand(Intent intent, int flags, int startId) { Debug("Service.onStartCommand"); if (MainApplication.trackingOn == false) { Debug("Tracking has been toggled off. Not scheduling any more wakeup alarms"); stopSelf(); MainApplication.wakeLock1(false); // Tracking has been turned off, don't schedule any new alarms return START_NOT_STICKY; } startAccelerometer(); return START_REDELIVER_INTENT; //return START_STICKY; } @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { Debug("Service.onDestroy"); MainApplication.mServiceInstance = null; } public static void Debug(String message) { Log.d("WhenMoving.Service", message); } protected void setMoving(boolean newState) { Context mContext; String s, fuzzy; NotificationManager nm; // Reset counts if (moving == newState) { iIntervals++; } else { // New state. Reset intervals iIntervals = 0; fSince = System.currentTimeMillis(); } // Prepare new Notification string /* if (newState == true) { s = String.format("On the move"); } else { s = String.format("Sitting still"); } */ // Could detect long periods of motion here and give a badge or say something funny // UPDATE NOTIFICATION if (mNotification != null && moving == newState) { // Update existing notification // No update // mNotification.setLatestEventInfo(this, "WhenMoving.Service", s, mPendingIntent); } else { // New notification /* Notification mNotification = new NotificationCompat.Builder(mContext) .setContentTitle( (moving ? "Moving" : "Stationary") ) .setContentText(t) .setSmallIcon( (moving ? R.drawable.moving : R.drawable.status) ) .setContentIntent(contentIntent) .build(); */ if (newState == true) { s = String.format("You're on the move"); } else { s = String.format("You're a sitting duck"); } // don't keep creating this if state hasn't transitioned mNotification = new Notification( (newState ? R.drawable.moving : R.drawable.stationary), s, System.currentTimeMillis() ); mNotification.setLatestEventInfo(this, "When Moving", s, mPendingIntent); } nm = (NotificationManager) this.getSystemService(NOTIFICATION_SERVICE); nm.notify(123, mNotification); // TELL UI TO UPDATE moving = newState; /* int accuracy = 9999; if (this.currentBestLocation != null) { Float f = new Float(this.currentBestLocation.getAccuracy()); accuracy = f.intValue(); } */ } }