/* * Copyright 2011 - AndroidQuery.com (tinyeeliu@gmail.com) * * 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 com.smartandroid.sa.aq; import java.util.Date; import java.util.Timer; import java.util.TimerTask; import android.content.Context; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.os.Looper; /** * The callback handler for handling Aquery.location() methods. */ public class LocationAjaxCallback extends AbstractAjaxCallback<Location, LocationAjaxCallback>{ private LocationManager lm; private long timeout = 30000; private long interval = 1000; private float tolerance = 10; private float accuracy = 1000; private int iteration = 3; private int n = 0; private boolean networkEnabled = false; private boolean gpsEnabled = false; //private long expire = 0; private Listener networkListener; private Listener gpsListener; private long initTime; public LocationAjaxCallback(){ type(Location.class).url("device"); } @Override public void async(Context context){ lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); gpsEnabled = lm.isProviderEnabled(LocationManager.GPS_PROVIDER); networkEnabled = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER); work(); } public LocationAjaxCallback timeout(long timeout){ this.timeout = timeout; return this; } public LocationAjaxCallback accuracy(float accuracy){ this.accuracy = accuracy; return this; } public LocationAjaxCallback tolerance(float tolerance){ this.tolerance = tolerance; return this; } public LocationAjaxCallback iteration(int iteration){ this.iteration = iteration; return this; } private void check(Location loc){ if(loc != null){ if(isBetter(loc)){ n++; boolean last = n >= iteration; boolean accurate = isAccurate(loc); boolean diff = isDiff(loc); boolean best = !gpsEnabled || LocationManager.GPS_PROVIDER.equals(loc.getProvider()); AQUtility.debug(n, iteration); AQUtility.debug("acc", accurate); AQUtility.debug("best", best); if(diff){ if(last){ if(accurate && best){ stop(); callback(loc); } }else{ if(accurate && best){ stop(); } callback(loc); } } } } } private void callback(Location loc){ result = loc; status(loc, 200); callback(); } private void status(Location loc, int code){ if(status == null){ status = new AjaxStatus(); } if(loc != null){ status.time(new Date(loc.getTime())); } status.code(code).done().source(AjaxStatus.DEVICE); } private boolean isAccurate(Location loc){ return loc.getAccuracy() < accuracy; } private boolean isDiff(Location loc){ if(result == null) return true; float diff = distFrom(result.getLatitude(), result.getLongitude(), loc.getLatitude(), loc.getLongitude()); if(diff < tolerance){ AQUtility.debug("duplicate location"); return false; }else{ return true; } } private boolean isBetter(Location loc){ if(result == null) return true; // if this loc is network and there's already an recent async gps update if(result.getTime() > initTime && result.getProvider().equals(LocationManager.GPS_PROVIDER) && loc.getProvider().equals(LocationManager.NETWORK_PROVIDER)){ AQUtility.debug("inferior location"); return false; } return true; } private void failure(){ if(gpsListener == null && networkListener == null) return; AQUtility.debug("fail"); result = null; status(null, AjaxStatus.TRANSFORM_ERROR); stop(); callback(); } public void stop(){ AQUtility.debug("stop"); Listener gListener = gpsListener; if(gListener != null){ lm.removeUpdates(gListener); gListener.cancel(); } Listener nListener = networkListener; if(nListener != null){ lm.removeUpdates(nListener); nListener.cancel(); } gpsListener = null; networkListener = null; } private void work(){ Location loc = getBestLocation(); Timer timer = new Timer(false); if(networkEnabled){ AQUtility.debug("register net"); networkListener = new Listener(); lm.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, interval, 0, networkListener, Looper.getMainLooper()); timer.schedule(networkListener, timeout); } if(gpsEnabled){ AQUtility.debug("register gps"); gpsListener = new Listener(); lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, interval, 0, gpsListener, Looper.getMainLooper()); timer.schedule(gpsListener, timeout); } if(iteration > 1 && loc != null){ n++; callback(loc); } initTime = System.currentTimeMillis(); } private Location getBestLocation(){ Location loc1 = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER); Location loc2 = lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); if(loc2 == null) return loc1; if(loc1 == null) return loc2; if(loc1.getTime() > loc2.getTime()){ return loc1; }else{ return loc2; } } private static float distFrom(double lat1, double lng1, double lat2, double lng2) { double earthRadius = 3958.75; double dLat = Math.toRadians(lat2-lat1); double dLng = Math.toRadians(lng2-lng1); double a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.sin(dLng/2) * Math.sin(dLng/2); double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); double dist = earthRadius * c; int meterConversion = 1609; return (float) dist * meterConversion; } private class Listener extends TimerTask implements LocationListener { public void onLocationChanged(Location location) { AQUtility.debug("changed", location); check(location); } public void onStatusChanged(String provider, int status, Bundle extras) { AQUtility.debug("onStatusChanged"); } public void onProviderEnabled(String provider) { AQUtility.debug("onProviderEnabled"); check(getBestLocation()); lm.removeUpdates(this); } public void onProviderDisabled(String provider) { AQUtility.debug("onProviderDisabled"); } @Override public void run() { failure(); } } }