/* * Copyright (C) 2014 Sean J. Barbeau (sjbarbeau@gmail.com), University of South Florida * * 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.onebusaway.android.util; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GoogleApiAvailability; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationServices; import org.onebusaway.android.R; import org.onebusaway.android.app.Application; import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; import android.location.Location; import android.location.LocationManager; import android.os.Bundle; import android.support.v7.app.AlertDialog; import android.util.Log; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import static com.google.android.gms.location.LocationServices.FusedLocationApi; /** * A helper class that keeps listeners updated with the best location available from * multiple providers */ public class LocationHelper implements com.google.android.gms.location.LocationListener, android.location.LocationListener, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { public interface Listener { /** * Called every time there is an update to the best location available */ void onLocationChanged(Location location); } static final String TAG = "LocationHelper"; Context mContext; LocationManager mLocationManager; ArrayList<Listener> mListeners = new ArrayList<Listener>(); /** * GoogleApiClient being used for Location Services */ protected GoogleApiClient mGoogleApiClient; LocationRequest mLocationRequest; private static final int MILLISECONDS_PER_SECOND = 1000; public static final int UPDATE_INTERVAL_IN_SECONDS = 5; private static final long UPDATE_INTERVAL = MILLISECONDS_PER_SECOND * UPDATE_INTERVAL_IN_SECONDS; private static final int FASTEST_INTERVAL_IN_SECONDS = 1; private static final long FASTEST_INTERVAL = MILLISECONDS_PER_SECOND * FASTEST_INTERVAL_IN_SECONDS; private static final String PREFERENCE_SHOWED_DIALOG = "showed_location_security_exception_dialog"; public LocationHelper(Context context) { mContext = context; mLocationManager = (LocationManager) Application.get().getBaseContext() .getSystemService(Context.LOCATION_SERVICE); setupGooglePlayServices(); } public synchronized void registerListener(Listener listener) { if (!mListeners.contains(listener)) { mListeners.add(listener); } // If this is the first listener, make sure we're monitoring the sensors to provide updates if (mListeners.size() == 1) { // Listen for location try { registerAllProviders(); } catch (SecurityException e) { Log.e(TAG, "User may have denied location permission - " + e); maybeShowSecurityDialog(); } } } public synchronized void unregisterListener(Listener listener) { if (mListeners.contains(listener)) { mListeners.remove(listener); } if (mListeners.size() == 0) { mLocationManager.removeUpdates(this); } } /** * Returns the GoogleApiClient being used for fused provider location updates * * @return the GoogleApiClient being used for fused provider location updates */ public GoogleApiClient getGoogleApiClient() { return mGoogleApiClient; } public synchronized void onResume() { try { registerAllProviders(); } catch (SecurityException e) { Log.e(TAG, "User may have denied location permission - " + e); maybeShowSecurityDialog(); } } public synchronized void onPause() { try { mLocationManager.removeUpdates(this); // Tear down GoogleApiClient if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) { FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this); mGoogleApiClient.disconnect(); } } catch (SecurityException e) { Log.e(TAG, "User may have denied location permission - " + e); maybeShowSecurityDialog(); } } @Override public void onLocationChanged(Location location) { // Offer this location to the centralized location store, it case its better than currently // stored location Application.setLastKnownLocation(location); // Notify listeners with the newest location from the central store (which could be the one // that was just generated above) Location lastLocation = Application.getLastKnownLocation(mContext, mGoogleApiClient); if (lastLocation != null) { // We need to copy the location, it case this object is reset in Application Location locationForListeners = new Location("for listeners"); locationForListeners.set(lastLocation); for (Listener l : mListeners) { l.onLocationChanged(locationForListeners); } } } @Override public void onStatusChanged(String provider, int status, Bundle extras) { } @Override public void onProviderEnabled(String provider) { } @Override public void onProviderDisabled(String provider) { } private void registerAllProviders() throws SecurityException { List<String> providers = mLocationManager.getProviders(true); for (Iterator<String> i = providers.iterator(); i.hasNext(); ) { mLocationManager.requestLocationUpdates(i.next(), 0, 0, this); } // Make sure GoogleApiClient is connected, if available if (mGoogleApiClient != null && !mGoogleApiClient.isConnected()) { mGoogleApiClient.connect(); } } private void setupGooglePlayServices() { // Create the LocationRequest object mLocationRequest = LocationRequest.create(); // Use high accuracy mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); // Set the update interval to 5 seconds mLocationRequest.setInterval(UPDATE_INTERVAL); // Set the fastest update interval to 1 second mLocationRequest.setFastestInterval(FASTEST_INTERVAL); // Init Google Play Services as early as possible in the Fragment lifecycle to give it time GoogleApiAvailability api = GoogleApiAvailability.getInstance(); if (api.isGooglePlayServicesAvailable(mContext) == ConnectionResult.SUCCESS) { mGoogleApiClient = new GoogleApiClient.Builder(mContext) .addApi(LocationServices.API) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .build(); mGoogleApiClient.connect(); } } /** * Shows the security dialog once if the user has disabled location permissions manually */ private void maybeShowSecurityDialog() { if (mContext != null && UIUtils.canManageDialog(mContext)) { final SharedPreferences sp = Application.getPrefs(); if (!sp.getBoolean(PREFERENCE_SHOWED_DIALOG, false)) { final AlertDialog.Builder builder = new AlertDialog.Builder(mContext); builder.setTitle(R.string.location_security_exception_title); builder.setCancelable(false); builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { sp.edit().putBoolean(PREFERENCE_SHOWED_DIALOG, true).commit(); } }); builder.setMessage(R.string.location_security_exception_message); builder.create().show(); } } } @Override public void onConnected(Bundle bundle) { Log.d(TAG, "Location Services connected"); // Request location updates try { FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this); } catch (SecurityException e) { Log.e(TAG, "User may have denied location permission - " + e); maybeShowSecurityDialog(); } } @Override public void onConnectionSuspended(int i) { } @Override public void onConnectionFailed(ConnectionResult connectionResult) { } }