/* * This file is part of WhereYouGo. * * WhereYouGo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * WhereYouGo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with WhereYouGo. If not, see <http://www.gnu.org/licenses/>. * * Copyright (C) 2012 Menion <whereyougo@asamm.cz> */ package menion.android.whereyougo.hardware.location; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.Hashtable; import java.util.Iterator; import locus.api.android.utils.LocusUtils; import locus.api.objects.extra.Location; import menion.android.whereyougo.R; import menion.android.whereyougo.gui.extension.MainApplication; import menion.android.whereyougo.gui.location.SatelliteScreen; import menion.android.whereyougo.settings.SettingValues; import menion.android.whereyougo.settings.Settings; import menion.android.whereyougo.utils.A; import menion.android.whereyougo.utils.Const; import menion.android.whereyougo.utils.Logger; import menion.android.whereyougo.utils.Utils; import menion.android.whereyougo.utils.geometry.Point2D; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Color; import android.location.GpsSatellite; import android.location.GpsStatus; import android.location.LocationManager; import android.os.Bundle; import android.widget.CheckBox; import android.widget.LinearLayout; import android.widget.TextView; /** * @author menion * @since 25.1.2010 2010 */ public class LocationState { private static String TAG = "LocationState"; public static final String SIMULATE_LOCATION = "SIMULATE_LOCATION"; private static final String KEY_B_GPS_ENABLE_ASK_ON_ENABLE = "KEY_B_GPS_ENABLE_ASK_ON_ENABLE"; private static final int GPS_ON = 0; private static final int GPS_OFF = 1; // gps connection "service" private static GpsConnection gpsConn; // last known location private static Location location; // actual gps mSource private static int mSource = GPS_OFF; // last GPS fix time private static long mLastGpsFixTime = 0L; // count of satellites private static Point2D.Int mSatsCount = new Point2D.Int(); // is speed correction enabled private static boolean speedCorrection = false; // location listeners private static ArrayList<LocationEventListener> mListeners; // last current mSource private static int lastSource; public static void init(Context c) { if (LocationState.location == null) { LocationState.location = Settings.getLastKnownLocation(c); mListeners = new ArrayList<LocationEventListener>(); lastSource = -1; } } public static void setGpsOff(Context context) { setLocation(context, GPS_OFF, null, true, null); } public static void destroy(Context context) { //Logger.d(TAG, "destroy(" + context + ")"); setLocation(context, GPS_OFF, location, false, null); mListeners.clear(); gpsConn = null; location = null; } public static void setGpsOn(Context context) { if (mSource == GPS_ON) setGpsOff(context); setLocation(context, GPS_ON, null, true, null); } private static void setLocation(final Context context, int source, Location location, boolean writeToSettings, String addData) { //Logger.w(TAG, "setLocation(" + context + ", " + source + ", " + (location != null ? location.getProvider() : "null") + // ", " + writeToSettings + ", " + addData + "), actual:" + LocationState.mSource); if (LocationState.mSource == source && ((location == null) || (LocationState.location.getLatitude() == location.getLatitude() && LocationState.location.getLongitude() == location.getLongitude()))) { //Logger.w(TAG, "setLocation(), do nothing ..."); return; } // save to start GPS with new start if (writeToSettings && context != null) Settings.setPrefBoolean(context, Settings.KEY_B_START_GPS_AUTOMATICALLY, source == GPS_ON); if (source == GPS_ON && context != null) { LocationState.mSource = GPS_ON; if (gpsConn != null) { gpsConn.destroy(); gpsConn = null; } boolean gpsNotEnabled = false; // test if GPS allowed String provider = android.provider.Settings.Secure.getString( context.getContentResolver(), android.provider.Settings.Secure.LOCATION_PROVIDERS_ALLOWED); //Logger.d(TAG, " - provider allowed:'" + provider + "'"); if (provider != null && provider.contains("network") || provider.contains("gps")) { //activity.startService(new Intent(activity, GpsConnectionService.class)); gpsConn = new GpsConnection(context); } else { if (Settings.getPrefBoolean(context, KEY_B_GPS_ENABLE_ASK_ON_ENABLE, true)) { LinearLayout ll = new LinearLayout(context); ll.setOrientation(LinearLayout.VERTICAL); int pad = (int) Utils.getDpPixels(5.0f); TextView tv = new TextView(context); tv.setPadding(pad, pad, pad, pad); tv.setText(R.string.gps_not_enabled_show_system_settings); tv.setTextColor(Color.BLACK); ll.addView(tv); final CheckBox chb = new CheckBox(context); chb.setText(R.string.dont_ask); chb.setTextColor(Color.BLACK); ll.addView(chb); // show dialog AlertDialog.Builder b = new AlertDialog.Builder(context); b.setCancelable(true); b.setTitle(R.string.question); b.setIcon(R.drawable.ic_question_alt); b.setView(ll); b.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (chb.isChecked()) { Settings.setPrefBoolean(context, KEY_B_GPS_ENABLE_ASK_ON_ENABLE, false); } Intent intent = new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS); context.startActivity(intent); } }); b.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (chb.isChecked()) { Settings.setPrefBoolean(context, KEY_B_GPS_ENABLE_ASK_ON_ENABLE, false); } } }); b.show(); } gpsNotEnabled = true; } if (gpsNotEnabled) { if (context instanceof SatelliteScreen) { ((SatelliteScreen) context).notifyGpsDisable(); } setLocation(context, GPS_OFF, null, true, null); } } else { LocationState.mSource = GPS_OFF; onGpsStatusChanged(GpsStatus.GPS_EVENT_STOPPED, null); if (gpsConn != null) { gpsConn.destroy(); gpsConn = null; } } // notify about changes onLocationChanged(LocationState.location); } public static void setLocationDirectly(Location location) { if (!Const.STATE_RELEASE && !LocationState.isActuallyHardwareGpsOn()) { // XXX location.setSpeed(20.0f); if (LocationState.location != null) location.setBearing(LocationState.location.bearingTo(location)); } onLocationChanged(location); } public static Location getLocation() { if (location == null) return new Location(TAG); return new Location(location); } public static boolean isActuallyHardwareGpsOn() { return mSource == GPS_ON; } public static boolean isActualLocationHardwareGps() { return mSource == GPS_ON && location.getProvider().equals(LocationManager.GPS_PROVIDER); } public static boolean isActualLocationHardwareNetwork() { return mSource == GPS_ON && location.getProvider().equals(LocationManager.NETWORK_PROVIDER); } /* * Returns the last known location of the device using its GPS and network location providers. * May be null if location access is disabled, or if the location providers don't exist. */ public static Location getLastKnownLocation(Activity activity) { LocationManager lm = (LocationManager) activity.getSystemService(Context.LOCATION_SERVICE); Location gpsLocation = null; try { gpsLocation = LocusUtils.convertToL( lm.getLastKnownLocation(LocationManager.GPS_PROVIDER)); } catch (SecurityException e) { Logger.w(TAG, "Failed to retrieve location: access appears to be disabled."); } catch (IllegalArgumentException e) { Logger.w(TAG, "Failed to retrieve location: device has no GPS provider."); } Location networkLocation = null; try { networkLocation = LocusUtils.convertToL( lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)); } catch (SecurityException e) { Logger.w(TAG, "Failed to retrieve location: access appears to be disabled."); } catch (IllegalArgumentException e) { Logger.w(TAG, "Failed to retrieve location: device has no network provider."); } if (gpsLocation != null && networkLocation != null) { if (gpsLocation.getTime() > networkLocation.getTime()) return gpsLocation; else return networkLocation; } else if (gpsLocation != null) return gpsLocation; else if (networkLocation != null) return networkLocation; else return null; } /** Registers a listener for GPS events*/ public static synchronized void addLocationChangeListener(LocationEventListener listener) { if (listener == null || mListeners == null) return; if (!mListeners.contains(listener)) { mListeners.add(listener); // sort listeners if (mListeners.size() > 0) Collections.sort(mListeners, new Comparator<LocationEventListener>() { public int compare(LocationEventListener object1, LocationEventListener object2) { return object1.getPriority() - object2.getPriority(); } }); onScreenOn(true); } } /** Unregisters a listener. */ public static synchronized void removeLocationChangeListener(LocationEventListener listener) { if (mListeners.size() == 0) return; if (listener != null && mListeners.contains(listener)) { mListeners.remove(listener); Logger.i(TAG, "removeLocationChangeListener(" + listener + "), actualSize:" + mListeners.size()); } } public static void onActivityPauseInstant(Context context) { try { boolean screenOff = (A.getApp() != null && ((MainApplication) A.getApp()).isScreenOff()); boolean disableWhenHide = context == null ? false : Settings.getPrefBoolean(context, Settings.KEY_B_GPS_DISABLE_WHEN_HIDE, Settings.DEFAULT_GPS_DISABLE_WHEN_HIDE); // also disable wake-lock here if (!Settings.existCurrentActivity() || screenOff) { Settings.disableWakeLock(); } // do not change gps state when ... if (!disableWhenHide) return; // disable GPS if not needed and hidden if (mListeners.size() == 0 && !Settings.existCurrentActivity()) { lastSource = mSource; setLocation(context, GPS_OFF, null, true, null); // disable gps when screen off or no activity visible (widget only) } else if (screenOff || !Settings.existCurrentActivity()) { if (!isGpsRequired()) { lastSource = mSource; setLocation(context, GPS_OFF, null, true, null); } else { lastSource = -1; } } else { lastSource = -1; } } catch (Exception e) { Logger.e(TAG, "onActivityPauseInstant()", e); } } public static boolean isGpsRequired() { if (mListeners == null) return false; boolean required = false; for (int i = 0; i < mListeners.size(); i++) { //Logger.d(TAG, "isGpsRequired() - list:" + mListeners.get(i) + ", req:" + mListeners.get(i).isRequired()); if (mListeners.get(i).isRequired()) { required = true; break; } } return required; } public static void onScreenOn(boolean force) { //Logger.i(TAG, "onScreenOn(), activity:" + Settings.getCurrentActivity() + ", exist:" + Settings.existCurrentActivity() + ", " + isGpsRequired()); if (lastSource != -1 && mListeners != null && mListeners.size() > 0 && (Settings.existCurrentActivity() || force)) { setLocation(Settings.getCurrentActivity(), lastSource, null, true, null); lastSource = -1; } } public static long getLastFixTime() { return mLastGpsFixTime; } protected static void onLocationChanged(Location location) { try { // check if location is valid if (LocationState.location == null) return; // if first location from Network, and new from GPS but with worst precision, do not set if (LocationState.location.getProvider().equals(LocationManager.NETWORK_PROVIDER) && location.getProvider().equals(LocationManager.GPS_PROVIDER) && (LocationState.location.getAccuracy() * 3) < location.getAccuracy()) { return; } // check incorrect speed (if speed bigger then 100, and after 2sec increase more then 50%, set old speed if (!speedCorrection && (location.getTime() - LocationState.location.getTime()) < 5000 && location.getSpeed() > 100.0f && location.getSpeed() / LocationState.location.getSpeed() > 2) { location.setSpeed(LocationState.location.getSpeed()); speedCorrection = true; } else { speedCorrection = false; } // set last gps fix if (LocationState.location.getProvider().equals(LocationManager.GPS_PROVIDER)) { mLastGpsFixTime = System.currentTimeMillis();//LocationState.location.getTime(); } // check incorrect azimuth changes when almost zero speed if (location.getSpeed() < 0.5f) { if (Math.abs(location.getBearing() - LocationState.location.getBearing()) > 25.0) { location.setBearing(LocationState.location.getBearing()); } } if (location.getProvider().equals(LocationManager.GPS_PROVIDER)) { // set altitude correction location.setAltitude(location.getAltitude() + SettingValues.GPS_ALTITUDE_CORRECTION); } // finally set new location LocationState.location = location; for (int i = 0; i < mListeners.size(); i++) { LocationEventListener list = mListeners.get(i); list.onLocationChanged(location); } } catch (Exception e) { Logger.e(TAG, "onLocationChanged(" + location + ")", e); } } protected static void onProviderDisabled(String provider) { //Logger.w(TAG, "onProviderDisabled(" + provider + ")"); } protected static void onProviderEnabled(String provider) { //Logger.w(TAG, "onProviderEnabled(" + provider + ")"); } protected static void onStatusChanged(String provider, int status, Bundle extras) { //Logger.w(TAG, "onStatusChanged(" + provider + ", " + status + ", " + extras + ")"); for (int i = 0; i < mListeners.size(); i++) { mListeners.get(i).onStatusChanged(provider, status, extras); } // status 1 - provider disabled // status 2 - provider enabled // if GPS provider is disabled, set location only as network if (provider.equals(LocationManager.GPS_PROVIDER) && status == 1) { location.setProvider(LocationManager.NETWORK_PROVIDER); onLocationChanged(location); } } protected static void onGpsStatusChanged(int event, GpsStatus gpsStatus) { if (mListeners == null || mListeners.size() == 0) return; if (event == GpsStatus.GPS_EVENT_STARTED || event == GpsStatus.GPS_EVENT_STOPPED) { for (int i = 0; i < mListeners.size(); i++) { mListeners.get(i).onStatusChanged(LocationManager.GPS_PROVIDER, event == GpsStatus.GPS_EVENT_STARTED ? 2 : 1, null); } } else if (event == GpsStatus.GPS_EVENT_SATELLITE_STATUS) { ArrayList<SatellitePosition> pos = null; if (gpsStatus != null) { pos = new ArrayList<SatellitePosition>(); Iterator<GpsSatellite> enuSat = gpsStatus.getSatellites().iterator(); // clear sats count mSatsCount.x = 0; mSatsCount.y = 0; while (enuSat.hasNext()) { GpsSatellite sat = enuSat.next(); //pos.add(enuPos.nextElement()); SatellitePosition satPos = new SatellitePosition(); satPos.azimuth = sat.getAzimuth(); satPos.elevation = sat.getElevation(); satPos.prn = sat.getPrn(); satPos.snr = (int) sat.getSnr(); satPos.fixed = sat.usedInFix(); if (satPos.fixed) mSatsCount.x++; mSatsCount.y++; pos.add(satPos); } } postGpsSatelliteChange(pos); } } protected static void onGpsStatusChanged(Hashtable<Integer, SatellitePosition> sats) { //Logger.w(TAG, "onGpsStatusChanged(" + sats + ")"); ArrayList<SatellitePosition> pos = null; if (sats != null) { pos = new ArrayList<SatellitePosition>(); Enumeration<SatellitePosition> enuPos = sats.elements(); mSatsCount.x = 0; mSatsCount.y = 0; while (enuPos.hasMoreElements()) { SatellitePosition sat = enuPos.nextElement(); pos.add(sat); if (sat.fixed) mSatsCount.x++; mSatsCount.y++; } } postGpsSatelliteChange(pos); } public static Point2D.Int getSatCount() { return mSatsCount; } private static void postGpsSatelliteChange(final ArrayList<SatellitePosition> pos) { if (Settings.getCurrentActivity() == null) return; Settings.getCurrentActivity().runOnUiThread(new Runnable() { public void run() { for (int i = 0; i < mListeners.size(); i++) { mListeners.get(i).onGpsStatusChanged(GpsStatus.GPS_EVENT_SATELLITE_STATUS, pos); } } }); } }