/*
* Copyright (C) 2011 Cuong Bui
*
* 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.android.server.location;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.Criteria;
import android.location.IGpsStatusListener;
import android.location.IGpsStatusProvider;
import android.location.ILocationManager;
import android.location.Location;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.WorkSource;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseIntArray;
import com.android.internal.app.IBatteryStats;
public class BTGpsLocationProvider implements LocationProviderInterface {
private static final boolean D = true;
private final String PROVIDER = "External Bleutooth Location Provider";
private final String TAG = "BTGpsLocationProvider";
private final NMEAParser nmeaparser = new NMEAParser(PROVIDER);
private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
// GPS update codes
public static final int GPS_DATA_AVAILABLE = 1000;
public static final int GPS_STATUS_UPDATE = 1001;
public static final int GPS_CUSTOM_COMMAND = 1002;
// Wakelocks
private final static String WAKELOCK_KEY = "GpsLocationProvider";
private final PowerManager.WakeLock mWakeLock;
// bitfield of pending messages to our Handler
// used only for messages that cannot have multiple instances queued
private int mPendingMessageBits;
// separate counter for ADD_LISTENER and REMOVE_LISTENER messages,
// which might have multiple instances queued
private int mPendingListenerMessages;
private final IBatteryStats mBatteryStats;
private final SparseIntArray mClientUids = new SparseIntArray();
// Handler messages
private static final int CHECK_LOCATION = 1;
private static final int ENABLE = 2;
private static final int ENABLE_TRACKING = 3;
private static final int UPDATE_NETWORK_STATE = 4;
private static final int INJECT_NTP_TIME = 5;
private static final int DOWNLOAD_XTRA_DATA = 6;
private static final int UPDATE_LOCATION = 7;
private static final int ADD_LISTENER = 8;
private static final int REMOVE_LISTENER = 9;
private static final int REQUEST_SINGLE_SHOT = 10;
// for calculating time to first fix
private long mFixRequestTime = 0;
// time to first fix for most recent session
private int mTTFF = 0;
// time we received our last fix
private long mLastFixTime;
// time for last status update
private long mStatusUpdateTime = SystemClock.elapsedRealtime();
// true if we are enabled
private volatile boolean mEnabled;
// true if GPS is navigating
private boolean mNavigating;
private int mSvCount;
// current status
private int mStatus = LocationProvider.TEMPORARILY_UNAVAILABLE;
private Bundle mLocationExtras = new Bundle();
private Location mLocation = new Location(PROVIDER);
private final Context mContext;
private final ILocationManager mLocationManager;
private final IntentFilter mIntentBTFilter;
private final Thread mMessageLoopThread = new BTGPSMessageThread();
private final CountDownLatch mInitializedLatch = new CountDownLatch(1);
/**
*Listen for BT changes. If BT is turned off, disable GPS services
*/
private final BroadcastReceiver mReceiver;
/**
* Message handler
* Receives nmea sentences
* receives connection lost signals
* enabling/disabling gps signals
* adding/removing listeners
*/
private Handler mHandler;
// BT gps service. This class handles the actual BT connection and data xfer
private final BTGPSService btsvc;
// BT Location provider , uses the same method signature as the org GPS location provider
public BTGpsLocationProvider(Context context, ILocationManager locationManager) {
mContext = context;
mLocationManager = locationManager;
// innit message handler
mMessageLoopThread.start();
// wait for message handler to be ready
while (true) {
try {
mInitializedLatch.await();
break;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// instantiate BTGPSService
btsvc = new BTGPSService(mHandler);
// Create a wake lock.
PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
mWakeLock.setReferenceCounted(false);
// Battery statistics service to be notified when GPS turns on or off
mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
// receive BT state changes
mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
BluetoothAdapter.ERROR);
switch (state) {
case BluetoothAdapter.STATE_ON:
if (D) Log.i(TAG, "BT turned on -> notify services?");
break;
case BluetoothAdapter.STATE_TURNING_OFF:
if (btsvc.getServiceState() != BTGPSService.STATE_NONE) {
if (D) Log.i(TAG, "BT turned off -> stopping services");
btsvc.stop();
}
break;
}
}
}
};
mIntentBTFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
mContext.registerReceiver(mReceiver, mIntentBTFilter);
}
private final class BTGPSMessageThread extends Thread {
public void run() {
try {
Looper.prepare();
} catch (RuntimeException e) {
// ignored: Looper already prepared
}
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
int message = msg.what;
switch (message) {
case GPS_DATA_AVAILABLE:
char[] writeBuf = (char[]) msg.obj;
int bytes = msg.arg1;
if ((writeBuf != null) && (mEnabled && bytes > 0)) {
String writeMessage = new String(writeBuf, 0, bytes);
handleNMEAMessages(writeMessage);
java.util.Arrays.fill(writeBuf, (char) ' ');
}
break;
case GPS_STATUS_UPDATE:
notifyEnableDisableGPS(msg.arg1 == 1);
break;
case GPS_CUSTOM_COMMAND:
if (mEnabled && btsvc.getServiceState() == BTGPSService.STATE_CONNECTED) {
// sends custom commands
byte[] cmds = (byte[]) msg.obj;
btsvc.write(cmds);
}
break;
case ENABLE:
if (msg.arg1 == 1) {
handleEnable();
} else {
handleDisable();
}
break;
case REQUEST_SINGLE_SHOT:
case ENABLE_TRACKING:
case UPDATE_NETWORK_STATE:
case INJECT_NTP_TIME:
case DOWNLOAD_XTRA_DATA:
break;
case UPDATE_LOCATION:
handleUpdateLocation((Location)msg.obj);
break;
case ADD_LISTENER:
handleAddListener(msg.arg1);
break;
case REMOVE_LISTENER:
handleRemoveListener(msg.arg1);
break;
}
// release wake lock if no messages are pending
synchronized (mWakeLock) {
mPendingMessageBits &= ~(1 << message);
if (message == ADD_LISTENER || message == REMOVE_LISTENER) {
mPendingListenerMessages--;
}
if (mPendingMessageBits == 0 && mPendingListenerMessages == 0) {
mWakeLock.release();
}
}
}
};
mInitializedLatch.countDown();
Looper.loop();
}
}
@Override
public void enable() {
synchronized (mHandler) {
sendMessage(ENABLE, 1, null);
}
}
/**
* Enables BT GPS provider
*/
private synchronized void handleEnable() {
if (D) Log.d(TAG, "handleEnable");
if (mEnabled) return;
// check if BT is enabled
if (!mAdapter.isEnabled()) {
int state = mAdapter.getState();
if (state == BluetoothAdapter.STATE_OFF) {
if (D) Log.d(TAG, "BT not available. Enable and wait for it...");
mAdapter.enable();
}
// wait for adapter to be ready
while (true) {
try {
state = mAdapter.getState();
if (state == BluetoothAdapter.STATE_ON) {
break;
} else if (state == BluetoothAdapter.STATE_TURNING_ON) {
if (D) Log.d(TAG, "BT not available yet. waiting for another 400ms");
Thread.sleep(400);
} else {
if (D) Log.d(TAG, "BT got disabled or interrupted by other source");
return;
}
} catch (InterruptedException e) {
Log.w(TAG, e.getMessage());
}
}
}
if (D) Log.d(TAG, "mEnabled -> true");
mEnabled = true;
if (D) Log.d(TAG, "mStatus -> temp unavailable");
mStatus = LocationProvider.TEMPORARILY_UNAVAILABLE;
if (D) Log.d(TAG, "btservice start");
btsvc.start();
mFixRequestTime = System.currentTimeMillis();
mTTFF = 0;
String btDevice = Settings.System.getString(mContext.getContentResolver(),
Settings.Secure.EXTERNAL_GPS_BT_DEVICE);
if (D) Log.d(TAG, "Connecting to saved pref: " + btDevice);
if ((btDevice != null) && !"0".equals(btDevice)) {
if ((mAdapter != null) && (mAdapter.isEnabled())) {
for (BluetoothDevice d: mAdapter.getBondedDevices()) {
if (btDevice.equals(d.getAddress())) {
if (D) Log.d(TAG, "Connecting...");
btsvc.connect(d);
return;
}
}
}
}
}
/**
* Disables this provider.
*/
@Override
public void disable() {
synchronized (mHandler) {
sendMessage(ENABLE, 0, null);
}
}
private synchronized void handleDisable() {
if (D) Log.d(TAG, "handleDisable");
if (!mEnabled) return;
if (D) Log.d(TAG, "mEnabled -> false");
mEnabled = false;
if (D) Log.d(TAG, "reportstatus notify listeners and system");
notifyEnableDisableGPS(false);
if (D) Log.d(TAG, "update to out of service");
updateStatus(LocationProvider.OUT_OF_SERVICE, mSvCount);
if (D) Log.d(TAG, "btservice Stop");
btsvc.stop();
}
/* We do not need to implement scheduled tracking. With internal gps providers it makes sence
* to hibernate and resume periodically. With BT GPS providers it doesn't make sense
* @see com.android.server.location.LocationProviderInterface#enableLocationTracking(boolean)
*/
@Override
public void enableLocationTracking(boolean enable) {
}
@Override
public int getAccuracy() {
return Criteria.ACCURACY_FINE;
}
/* Debug native state used by normal GPS provider only
* @see com.android.server.location.LocationProviderInterface#getInternalState()
*/
@Override
public String getInternalState() {
return null;
}
@Override
public String getName() {
return LocationManager.GPS_PROVIDER;
}
/**
* Returns the power requirement for this provider.
*
* @return the power requirement for this provider, as one of the
* constants Criteria.POWER_REQUIREMENT_*.
*/
public int getPowerRequirement() {
return Criteria.POWER_MEDIUM;
}
/**
* Returns true if this provider meets the given criteria,
* false otherwise.
*/
public boolean meetsCriteria(Criteria criteria) {
return (criteria.getPowerRequirement() != Criteria.POWER_LOW);
}
@Override
public int getStatus(Bundle extras) {
if (extras != null) {
extras.putInt("satellites", mSvCount);
}
return mStatus;
}
@Override
public long getStatusUpdateTime() {
return mStatusUpdateTime;
}
@Override
public boolean hasMonetaryCost() {
return false;
}
@Override
public boolean isEnabled() {
return mEnabled;
}
@Override
public boolean requestSingleShotFix() {
return false;
}
@Override
public boolean requiresCell() {
return false;
}
@Override
public boolean requiresNetwork() {
return false;
}
@Override
public boolean requiresSatellite() {
return true;
}
@Override
public boolean sendExtraCommand(String command, Bundle extras) {
if (TextUtils.isEmpty(command)) return false;
synchronized (mHandler) {
String customCommand = command + "\r\n";
sendMessage(GPS_CUSTOM_COMMAND, customCommand.length(), customCommand.getBytes());
}
return true;
}
/* GPS scheduling stuff, not needed
* @see com.android.server.location.LocationProviderInterface#setMinTime(long, android.os.WorkSource)
*/
@Override
public void setMinTime(long minTime, WorkSource ws) {
}
@Override
public boolean supportsAltitude() {
return mLocation.hasAltitude();
}
@Override
public boolean supportsBearing() {
return mLocation.hasBearing();
}
@Override
public boolean supportsSpeed() {
return mLocation.hasSpeed();
}
@Override
/**
* This is called to inform us when another location provider returns a location.
* Someday we might use this for network location injection to aid the GPS
*/
public void updateLocation(Location location) {
sendMessage(UPDATE_LOCATION, 0, location);
}
private void handleUpdateLocation(Location location) {
if (location.hasAccuracy()) {
// Allow other provider GPS data ? discard for now
}
}
/* unneeded by BT GPS provider
* @see com.android.server.location.LocationProviderInterface#updateNetworkState(int, android.net.NetworkInfo)
*/
@Override
public void updateNetworkState(int state, NetworkInfo info) {
// TODO Auto-generated method stub
}
/**
* @param loc Location object representing the fix
* @param isValid true if fix was valid
*/
private void reportLocation(Location loc, boolean isValid) {
if (!isValid) {
if (mStatus == LocationProvider.AVAILABLE && mTTFF > 0) {
if (D) Log.d(TAG, "Invalid sat fix -> sending notification to system");
// send an intent to notify that the GPS is no longer receiving fixes.
Intent intent = new Intent(LocationManager.GPS_FIX_CHANGE_ACTION);
intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, false);
mContext.sendBroadcast(intent);
updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, mSvCount);
}
return;
}
synchronized (mLocation) {
mLocation.set(loc);
mLocation.setProvider(this.getName());
if (D) {
Log.d(TAG, "reportLocation lat: " + mLocation.getLatitude() +
" long: " + mLocation.getLongitude() + " alt: " + mLocation.getAltitude() +
" accuracy: " + mLocation.getAccuracy() + " timestamp: " + mLocation.getTime());
}
try {
mLocationManager.reportLocation(mLocation, false);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException calling reportLocation");
}
}
mLastFixTime = System.currentTimeMillis();
// report time to first fix
if ((mTTFF == 0) && (isValid)) {
mTTFF = (int)(mLastFixTime - mFixRequestTime);
if (D) Log.d(TAG, "TTFF: " + mTTFF);
// notify status listeners
synchronized(mListeners) {
int size = mListeners.size();
for (int i = 0; i < size; i++) {
Listener listener = mListeners.get(i);
try {
listener.mListener.onFirstFix(mTTFF);
} catch (RemoteException e) {
Log.w(TAG, "RemoteException in first fix notification");
mListeners.remove(listener);
// adjust for size of list changing
size--;
}
}
}
}
if (mStatus != LocationProvider.AVAILABLE) {
if (D) Log.d(TAG,"Notify that we're receiving fixes");
// send an intent to notify that the GPS is receiving fixes.
Intent intent = new Intent(LocationManager.GPS_FIX_CHANGE_ACTION);
intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, true);
mContext.sendBroadcast(intent);
updateStatus(LocationProvider.AVAILABLE, mSvCount);
}
}
/* report sats status
*/
private void reportSvStatus(int svCount, int mSvs[], float mSnrs[],
float mSvElevations[], float mSvAzimuths[], int mSvMasks[]) {
if (D) Log.d(TAG,"About to report sat status svcount: " + svCount);
synchronized(mListeners) {
int size = mListeners.size();
for (int i = 0; i < size; i++) {
Listener listener = mListeners.get(i);
try {
listener.mListener.onSvStatusChanged(svCount, mSvs, mSnrs, mSvElevations,
mSvAzimuths, mSvMasks[NMEAParser.EPHEMERIS_MASK],
mSvMasks[NMEAParser.ALMANAC_MASK],
mSvMasks[NMEAParser.USED_FOR_FIX_MASK]);
} catch (RemoteException e) {
Log.w(TAG, "RemoteException in reportSvInfo");
mListeners.remove(listener);
// adjust for size of list changing
size--;
}
}
}
// return number of sets used in fix instead of total
updateStatus(mStatus, Integer.bitCount(mSvMasks[NMEAParser.USED_FOR_FIX_MASK]));
}
/**
* Handles GPS status.
* will also inform listeners when GPS started/stopped
* @param status new GPS status
*/
private void notifyEnableDisableGPS(boolean status) {
if (D) Log.v(TAG, "notifyEnableDisableGPS status: " + status);
synchronized(mListeners) {
mNavigating = status;
int size = mListeners.size();
for (int i = 0; i < size; i++) {
Listener listener = mListeners.get(i);
try {
if (status) {
listener.mListener.onGpsStarted();
} else {
listener.mListener.onGpsStopped();
}
} catch (RemoteException e) {
Log.w(TAG, "RemoteException in reportStatus");
mListeners.remove(listener);
// adjust for size of list changing
size--;
}
}
try {
// update battery stats
for (int i=mClientUids.size() - 1; i >= 0; i--) {
int uid = mClientUids.keyAt(i);
if (mNavigating) {
mBatteryStats.noteStartGps(uid);
} else {
mBatteryStats.noteStopGps(uid);
}
}
} catch (RemoteException e) {
Log.w(TAG, "RemoteException in reportStatus");
}
// send an intent to notify that the GPS has been enabled or disabled.
Intent intent = new Intent(LocationManager.GPS_ENABLED_CHANGE_ACTION);
intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, status);
mContext.sendBroadcast(intent);
}
try {
if (D) Log.d(TAG, "Setting System GPS status to " + status);
Settings.Secure.setLocationProviderEnabled(mContext.getContentResolver(),
LocationManager.GPS_PROVIDER, status);
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
/**
* sends nmea sentences to NMEA parsers. Some apps use raw nmea data
* @param nmeaString nmea string
* @param timestamp time stamp
*/
private void reportNmea(String nmeaString, long timestamp) {
synchronized(mListeners) {
int size = mListeners.size();
if (size > 0) {
// don't bother creating the String if we have no listeners
for (int i = 0; i < size; i++) {
Listener listener = mListeners.get(i);
try {
listener.mListener.onNmeaReceived(timestamp, nmeaString);
} catch (RemoteException e) {
Log.w(TAG, "RemoteException in reportNmea");
mListeners.remove(listener);
// adjust for size of list changing
size--;
}
}
}
}
}
/**
* This methods parses the nmea sentences and sends the location updates
* and sats updates to listeners.
* @param sentences raw nmea sentences received by BT GPS Mouse
*/
private void handleNMEAMessages(String sentences) {
String sentenceArray[] = sentences.split("\r\n");
nmeaparser.reset();
for (int i = 0; i < sentenceArray.length; i++) {
if (D) Log.d(TAG, "About to parse: " + sentenceArray[i]);
if ((sentenceArray[i] != null) && ("".equals(sentenceArray[i]))) continue;
boolean parsed = nmeaparser.parseNMEALine(sentenceArray[i]);
// handle nmea message. Also report messages that we could not parse as these
// might be propriatery messages that other listeners could support.
reportNmea(sentenceArray[i], System.currentTimeMillis());
}
Location loc = nmeaparser.getLocation();
// handle location update if valid
reportLocation(loc , nmeaparser.isValid());
if (nmeaparser.isSatdataReady()) {
reportSvStatus(nmeaparser.getmSvCount(), nmeaparser.getmSvs(), nmeaparser.getmSnrs(),
nmeaparser.getmSvElevations(), nmeaparser.getmSvAzimuths(),
nmeaparser.getmSvMasks());
}
// adjust refresh rate based on received timestamp of mouse
// min 1hz and max 10 hz
long newRate = nmeaparser.getApproximatedRefreshRate();
if (btsvc.getRefreshRate() != newRate) {
if (D) Log.d(TAG, "Setting refresh rate to: " + newRate
+ " was: " + btsvc.getRefreshRate());
btsvc.setRefreshRate(newRate);
}
}
/*
* Stuff below is taken from the android GPS location provider.
* Does handling of messages/listeners and so on.
*/
private void sendMessage(int message, int arg, Object obj) {
// hold a wake lock while messages are pending
synchronized (mWakeLock) {
mPendingMessageBits |= (1 << message);
mWakeLock.acquire();
mHandler.removeMessages(message);
Message m = Message.obtain(mHandler, message);
m.arg1 = arg;
m.obj = obj;
mHandler.sendMessage(m);
}
}
private void updateStatus(int status, int svCount) {
if (status != mStatus || svCount != mSvCount) {
mStatus = status;
mSvCount = svCount;
mLocationExtras.putInt("satellites", svCount);
mStatusUpdateTime = SystemClock.elapsedRealtime();
}
}
private ArrayList<Listener> mListeners = new ArrayList<Listener>();
private final class Listener implements IBinder.DeathRecipient {
final IGpsStatusListener mListener;
Listener(IGpsStatusListener listener) {
mListener = listener;
}
public void binderDied() {
if (D) Log.d(TAG, "GPS status listener died");
synchronized(mListeners) {
mListeners.remove(this);
}
if (mListener != null) {
mListener.asBinder().unlinkToDeath(this, 0);
}
}
}
private final IGpsStatusProvider mGpsStatusProvider = new IGpsStatusProvider.Stub() {
public void addGpsStatusListener(IGpsStatusListener listener) throws RemoteException {
if (listener == null) {
throw new NullPointerException("listener is null in addGpsStatusListener");
}
synchronized(mListeners) {
IBinder binder = listener.asBinder();
int size = mListeners.size();
for (int i = 0; i < size; i++) {
Listener test = mListeners.get(i);
if (binder.equals(test.mListener.asBinder())) {
// listener already added
return;
}
}
Listener l = new Listener(listener);
binder.linkToDeath(l, 0);
mListeners.add(l);
}
}
public void removeGpsStatusListener(IGpsStatusListener listener) {
if (listener == null) {
throw new NullPointerException("listener is null in addGpsStatusListener");
}
synchronized(mListeners) {
IBinder binder = listener.asBinder();
Listener l = null;
int size = mListeners.size();
for (int i = 0; i < size && l == null; i++) {
Listener test = mListeners.get(i);
if (binder.equals(test.mListener.asBinder())) {
l = test;
}
}
if (l != null) {
mListeners.remove(l);
binder.unlinkToDeath(l, 0);
}
}
}
};
public IGpsStatusProvider getGpsStatusProvider() {
return mGpsStatusProvider;
}
public void addListener(int uid) {
synchronized (mWakeLock) {
mPendingListenerMessages++;
mWakeLock.acquire();
Message m = Message.obtain(mHandler, ADD_LISTENER);
m.arg1 = uid;
mHandler.sendMessage(m);
}
}
private void handleAddListener(int uid) {
synchronized(mListeners) {
if (mClientUids.indexOfKey(uid) >= 0) {
// Shouldn't be here -- already have this uid.
Log.w(TAG, "Duplicate add listener for uid " + uid);
return;
}
mClientUids.put(uid, 0);
if (mNavigating) {
try {
mBatteryStats.noteStartGps(uid);
} catch (RemoteException e) {
Log.w(TAG, "RemoteException in addListener");
}
}
}
}
public void removeListener(int uid) {
synchronized (mWakeLock) {
mPendingListenerMessages++;
mWakeLock.acquire();
Message m = Message.obtain(mHandler, REMOVE_LISTENER);
m.arg1 = uid;
mHandler.sendMessage(m);
}
}
private void handleRemoveListener(int uid) {
synchronized(mListeners) {
if (mClientUids.indexOfKey(uid) < 0) {
// Shouldn't be here -- don't have this uid.
Log.w(TAG, "Unneeded remove listener for uid " + uid);
return;
}
mClientUids.delete(uid);
if (mNavigating) {
try {
mBatteryStats.noteStopGps(uid);
} catch (RemoteException e) {
Log.w(TAG, "RemoteException in removeListener");
}
}
}
}
}