/******************************************************************************* * Copyright 2011 The Regents of the University of California * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package org.ohmage.service; import android.content.ContentUris; import android.content.ContentValues; import android.content.Intent; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import org.ohmage.db.DbContract.Responses; import org.ohmage.db.Models.Response; import org.ohmage.logprobe.Analytics; import org.ohmage.logprobe.LogProbe.Status; import java.util.LinkedList; import java.util.List; public class SurveyGeotagService extends WakefulService implements LocationListener{ private static final String TAG = "SurveyGeotagService"; public static final String LOCATION_VALID = "valid"; public static final String LOCATION_UNAVAILABLE = "unavailable"; public static final long LOCATION_STALENESS_LIMIT = 2 * 60 * 1000; public static final long LOCATION_ACCURACY_THRESHOLD = 30; private static final long UPDATE_FREQ = 0; // 5 * 60 * 1000; private LocationManager mLocManager; private final List<ResponseLocationSaver> responses = new LinkedList<ResponseLocationSaver>(); private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { // The timer expired without a gps location so tell the saver to save the network location ((ResponseLocationSaver) msg.obj).saveLocation(null); // Remove the response from the queue responses.remove(msg.obj); // If there are no more responses we should stop if(!mHandler.hasMessages(0)) { releaseLock(); stopSelf(); } } }; private class ResponseLocationSaver implements LocationListener { private final long mId; private Location mLocation; public ResponseLocationSaver(long id) { mId = id; mLocManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, UPDATE_FREQ, 0, this); } /** * Saves this response to the db with the given location. If the * location provided is null, we will try to use the network location * that we have * * @param location */ public void saveLocation(Location location) { //Remove our location listener if it still exists mLocManager.removeUpdates(this); // The default values that say this response is no longer waiting for a location ContentValues values = new ContentValues(); values.put(Responses.RESPONSE_STATUS, Response.STATUS_STANDBY); values.put(Responses.RESPONSE_LOCATION_STATUS, LOCATION_UNAVAILABLE); // If the location provided is null, use the network location if(location == null) location = mLocation; // If we have a location, add the values if(location != null) { values.put(Responses.RESPONSE_LOCATION_STATUS, LOCATION_VALID); values.put(Responses.RESPONSE_LOCATION_LATITUDE, location.getLatitude()); values.put(Responses.RESPONSE_LOCATION_LONGITUDE, location.getLongitude()); values.put(Responses.RESPONSE_LOCATION_PROVIDER, location.getProvider()); values.put(Responses.RESPONSE_LOCATION_ACCURACY, location.getAccuracy()); values.put(Responses.RESPONSE_LOCATION_TIME, location.getTime()); } // Update the db with the location information SurveyGeotagService.this.getContentResolver().update( Responses.buildResponseUri(mId), values, Responses.RESPONSE_STATUS + "=" + Response.STATUS_WAITING_FOR_LOCATION, null); } @Override public void onLocationChanged(Location location) { // If this location is accurate at all, save it as the network location if(location.hasAccuracy()) { mLocation = location; mLocManager.removeUpdates(this); } } @Override public void onStatusChanged(String provider, int status, Bundle extras) { // TODO Auto-generated method stub } @Override public void onProviderEnabled(String provider) { // TODO Auto-generated method stub } @Override public void onProviderDisabled(String provider) { // TODO Auto-generated method stub } } @Override public void onCreate() { super.onCreate(); Analytics.service(this, Status.ON); // Create the location manager and start listening to the GPS mLocManager = (LocationManager) getSystemService(LOCATION_SERVICE); mLocManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, UPDATE_FREQ, 0, this); } @Override protected void doWakefulWork(Intent intent) { // Create a response location saver to get a location for this response and send it to the handler ResponseLocationSaver response = new ResponseLocationSaver(ContentUris.parseId(intent.getData())); responses.add(response); Message msg = mHandler.obtainMessage(0, response); mHandler.sendMessageDelayed(msg, LOCATION_STALENESS_LIMIT); } @Override public void onDestroy() { super.onDestroy(); Analytics.service(this, Status.OFF); // Make sure the location listener has been removed mLocManager.removeUpdates(this); // If there are any response location savers still here we need to remove them flushResponses(); } /** * For all the responses this should set the network location if it exists */ private void flushResponses() { setResponsesLocation(null); } /** * For all the responses set a location * @param location */ private void setResponsesLocation(Location location) { mHandler.removeMessages(0); for (ResponseLocationSaver response : responses) { response.saveLocation(location); } responses.clear(); } @Override public void onLocationChanged(Location location) { if (locationValid(location)) // If we have a good enough location, we can save the responses and finish the service mLocManager.removeUpdates(this); try { setResponsesLocation(location); } finally { releaseLock(); stopSelf(); } } /** * Checks to see that this location is accurate enough * @param location * @return */ public static boolean locationValid(Location location) { return LocationManager.GPS_PROVIDER.equals(location.getProvider()) && location != null && location.hasAccuracy() && (location.getAccuracy() < LOCATION_ACCURACY_THRESHOLD); } @Override public void onStatusChanged(String provider, int status, Bundle extras) { // TODO Auto-generated method stub } @Override public void onProviderEnabled(String provider) { // TODO Auto-generated method stub } @Override public void onProviderDisabled(String provider) { // TODO Auto-generated method stub } @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } }