summaryrefslogtreecommitdiff
path: root/location/java
diff options
context:
space:
mode:
author Soonil Nagarkar <sooniln@google.com> 2020-05-31 23:12:47 -0700
committer Soonil Nagarkar <sooniln@google.com> 2020-05-31 23:12:47 -0700
commit7f78f206b76f4b567508e81678876b583c19b1a8 (patch)
treeeaf167430e2d92db104a3abff54c38c12d6902bc /location/java
parent1a1eea681aee6aaba8f59f7e42cf37c0de47df8e (diff)
Adopt new ListenerTransport framework
This makes all LM listeners static, saving memory across LM instances in an application. In addition, listeners for rarely used APIs such as GNSS APIs are now initialized on-demand to save memory. Finally, the new transport framework is adopted for location listeners, which means a new transport object will be used for every location request, eliminating several edge case bugs around listener removal. Test: manual + cts Change-Id: I1f374248baf695323177f347873fed72841f85d0
Diffstat (limited to 'location/java')
-rw-r--r--location/java/android/location/ILocationManager.aidl8
-rw-r--r--location/java/android/location/LocationManager.java390
-rw-r--r--location/java/android/location/util/LocationListenerTransportManager.java176
3 files changed, 301 insertions, 273 deletions
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 9102a16ba480..6d848c912d95 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -50,9 +50,11 @@ interface ILocationManager
in ICancellationSignal cancellationSignal, in ILocationListener listener,
String packageName, String attributionTag, String listenerId);
- void requestLocationUpdates(in LocationRequest request, in ILocationListener listener,
- in PendingIntent intent, String packageName, String attributionTag, String listenerId);
- void removeUpdates(in ILocationListener listener, in PendingIntent intent);
+ void registerLocationListener(in LocationRequest request, in ILocationListener listener, String packageName, String attributionTag, String listenerId);
+ void unregisterLocationListener(in ILocationListener listener);
+
+ void registerLocationPendingIntent(in LocationRequest request, in PendingIntent intent, String packageName, String attributionTag);
+ void unregisterLocationPendingIntent(in PendingIntent intent);
void requestGeofence(in Geofence geofence, in PendingIntent intent, String packageName, String attributionTag);
void removeGeofence(in PendingIntent intent);
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 5eaca78aa4a9..99fce9ce0e55 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -44,7 +44,7 @@ import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.os.Binder;
+import android.location.util.LocationListenerTransportManager;
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -88,23 +88,6 @@ import java.util.function.Consumer;
@RequiresFeature(PackageManager.FEATURE_LOCATION)
public class LocationManager {
- @GuardedBy("mLock")
- private PropertyInvalidatedCache<Integer, Boolean> mLocationEnabledCache =
- new PropertyInvalidatedCache<Integer, Boolean>(
- 4,
- CACHE_KEY_LOCATION_ENABLED_PROPERTY) {
- @Override
- protected Boolean recompute(Integer userHandle) {
- try {
- return mService.isLocationEnabledForUser(userHandle);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- };
-
- private final Object mLock = new Object();
-
/**
* For apps targeting Android R and above, {@link #getProvider(String)} will no longer throw any
* security exceptions.
@@ -324,27 +307,42 @@ public class LocationManager {
private static final long GET_CURRENT_LOCATION_MAX_TIMEOUT_MS = 30 * 1000;
+ @GuardedBy("mProviderLocationListeners")
+ private static final ArrayMap<String, LocationListenerTransportManager>
+ sProviderLocationListeners = new ArrayMap<>();
+
final Context mContext;
@UnsupportedAppUsage
final ILocationManager mService;
- @GuardedBy("mListeners")
- private final ArrayMap<LocationListener, LocationListenerTransport> mListeners =
- new ArrayMap<>();
+ private final Object mLock = new Object();
- private final GnssStatusTransportMultiplexer mGnssStatusListenerManager =
- new GnssStatusTransportMultiplexer();
- private final GnssMeasurementsTransportMultiplexer mGnssMeasurementsListenerManager =
- new GnssMeasurementsTransportMultiplexer();
- private final GnssNavigationTransportMultiplexer mGnssNavigationListenerTransport =
- new GnssNavigationTransportMultiplexer();
- private final GnssAntennaInfoTransportMultiplexer mGnssAntennaInfoListenerManager =
- new GnssAntennaInfoTransportMultiplexer();
+ @GuardedBy("mLock")
+ private PropertyInvalidatedCache<Integer, Boolean> mLocationEnabledCache =
+ new PropertyInvalidatedCache<Integer, Boolean>(
+ 4,
+ CACHE_KEY_LOCATION_ENABLED_PROPERTY) {
+ @Override
+ protected Boolean recompute(Integer userHandle) {
+ try {
+ return mService.isLocationEnabledForUser(userHandle);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ };
- private final Object mBatchedLocationCallbackLock = new Object();
+ @GuardedBy("mLock")
+ @Nullable private GnssStatusTransportMultiplexer mGnssStatusTransportMultiplexer;
+ @GuardedBy("mLock")
+ @Nullable private GnssMeasurementsTransportMultiplexer mGnssMeasurementsTransportMultiplexer;
+ @GuardedBy("mLock")
+ @Nullable private GnssNavigationTransportMultiplexer mGnssNavigationTransportMultiplexer;
+ @GuardedBy("mLock")
+ @Nullable private GnssAntennaInfoTransportMultiplexer mGnssAntennaInfoTransportMultiplexer;
- @GuardedBy("mBatchLocationCallbackLock")
- private @Nullable BatchedLocationCallbackTransport mBatchedLocationCallbackTransport;
+ @GuardedBy("mLock")
+ @Nullable private BatchedLocationCallbackTransport mBatchedLocationCallbackTransport;
/**
* @hide
@@ -354,6 +352,42 @@ public class LocationManager {
mContext = context;
}
+ private GnssStatusTransportMultiplexer getGnssStatusTransportMultiplexer() {
+ synchronized (mLock) {
+ if (mGnssStatusTransportMultiplexer == null) {
+ mGnssStatusTransportMultiplexer = new GnssStatusTransportMultiplexer();
+ }
+ return mGnssStatusTransportMultiplexer;
+ }
+ }
+
+ private GnssMeasurementsTransportMultiplexer getGnssMeasurementsTransportMultiplexer() {
+ synchronized (mLock) {
+ if (mGnssMeasurementsTransportMultiplexer == null) {
+ mGnssMeasurementsTransportMultiplexer = new GnssMeasurementsTransportMultiplexer();
+ }
+ return mGnssMeasurementsTransportMultiplexer;
+ }
+ }
+
+ private GnssNavigationTransportMultiplexer getGnssNavigationTransportMultiplexer() {
+ synchronized (mLock) {
+ if (mGnssNavigationTransportMultiplexer == null) {
+ mGnssNavigationTransportMultiplexer = new GnssNavigationTransportMultiplexer();
+ }
+ return mGnssNavigationTransportMultiplexer;
+ }
+ }
+
+ private GnssAntennaInfoTransportMultiplexer getGnssAntennaInfoTransportMultiplexer() {
+ synchronized (mLock) {
+ if (mGnssAntennaInfoTransportMultiplexer == null) {
+ mGnssAntennaInfoTransportMultiplexer = new GnssAntennaInfoTransportMultiplexer();
+ }
+ return mGnssAntennaInfoTransportMultiplexer;
+ }
+ }
+
/**
* @hide
*/
@@ -1162,32 +1196,22 @@ public class LocationManager {
@Nullable LocationRequest locationRequest,
@NonNull @CallbackExecutor Executor executor,
@NonNull LocationListener listener) {
- synchronized (mListeners) {
- LocationListenerTransport transport = mListeners.get(listener);
- if (transport != null) {
- transport.unregister();
- } else {
- transport = new LocationListenerTransport(listener);
- mListeners.put(listener, transport);
- }
- transport.register(executor);
+ if (locationRequest == null) {
+ locationRequest = new LocationRequest();
+ }
- boolean registered = false;
- try {
- mService.requestLocationUpdates(locationRequest, transport, null,
- mContext.getPackageName(), mContext.getAttributionTag(),
- transport.getListenerId());
- registered = true;
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- } finally {
- if (!registered) {
- // allow gc after exception
- transport.unregister();
- mListeners.remove(listener);
- }
+ String provider = locationRequest.getProvider();
+
+ LocationListenerTransportManager manager;
+ synchronized (sProviderLocationListeners) {
+ manager = sProviderLocationListeners.get(provider);
+ if (manager == null) {
+ manager = new LocationListenerTransportManager(mService);
+ sProviderLocationListeners.put(provider, manager);
}
}
+
+ manager.addListener(mContext, locationRequest, executor, listener);
}
/**
@@ -1220,8 +1244,8 @@ public class LocationManager {
}
try {
- mService.requestLocationUpdates(locationRequest, null, pendingIntent,
- mContext.getPackageName(), mContext.getAttributionTag(), null);
+ mService.registerLocationPendingIntent(locationRequest, pendingIntent,
+ mContext.getPackageName(), mContext.getAttributionTag());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1270,18 +1294,24 @@ public class LocationManager {
public void removeUpdates(@NonNull LocationListener listener) {
Preconditions.checkArgument(listener != null, "invalid null listener");
- synchronized (mListeners) {
- LocationListenerTransport transport = mListeners.remove(listener);
- if (transport == null) {
- return;
+ RuntimeException exception = null;
+ synchronized (sProviderLocationListeners) {
+ for (int i = 0; i < sProviderLocationListeners.size(); i++) {
+ LocationListenerTransportManager manager = sProviderLocationListeners.valueAt(i);
+ try {
+ manager.removeListener(listener);
+ } catch (RuntimeException e) {
+ if (exception == null) {
+ exception = e;
+ } else {
+ exception.addSuppressed(e);
+ }
+ }
}
- transport.unregister();
+ }
- try {
- mService.removeUpdates(transport, null);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ if (exception != null) {
+ throw exception;
}
}
@@ -1297,7 +1327,7 @@ public class LocationManager {
Preconditions.checkArgument(pendingIntent != null, "invalid null pending intent");
try {
- mService.removeUpdates(null, pendingIntent);
+ mService.unregisterLocationPendingIntent(pendingIntent);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1804,8 +1834,9 @@ public class LocationManager {
"GpsStatus APIs not supported, please use GnssStatus APIs instead");
}
- GnssStatus gnssStatus = mGnssStatusListenerManager.getGnssStatus();
- int ttff = mGnssStatusListenerManager.getTtff();
+ GnssStatusTransportMultiplexer multiplexer = getGnssStatusTransportMultiplexer();
+ GnssStatus gnssStatus = multiplexer.getGnssStatus();
+ int ttff = multiplexer.getTtff();
if (gnssStatus != null) {
if (status == null) {
status = GpsStatus.create(gnssStatus, ttff);
@@ -1834,7 +1865,7 @@ public class LocationManager {
"GpsStatus APIs not supported, please use GnssStatus APIs instead");
}
- mGnssStatusListenerManager.addListener(listener, DIRECT_EXECUTOR);
+ getGnssStatusTransportMultiplexer().addListener(listener, DIRECT_EXECUTOR);
return true;
}
@@ -1853,11 +1884,12 @@ public class LocationManager {
"GpsStatus APIs not supported, please use GnssStatus APIs instead");
}
- mGnssStatusListenerManager.removeListener(listener);
+ getGnssStatusTransportMultiplexer().removeListener(listener);
}
/**
- * Registers a GNSS status callback.
+ * Registers a GNSS status callback. This method must be called from a {@link Looper} thread,
+ * and callbacks will occur on that looper.
*
* @param callback GNSS status callback object to register
* @return true if the listener was successfully added
@@ -1890,8 +1922,7 @@ public class LocationManager {
handler = new Handler();
}
- mGnssStatusListenerManager.addListener(callback, new HandlerExecutor(handler));
- return true;
+ return registerGnssStatusCallback(new HandlerExecutor(handler), callback);
}
/**
@@ -1909,7 +1940,7 @@ public class LocationManager {
public boolean registerGnssStatusCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull GnssStatus.Callback callback) {
- mGnssStatusListenerManager.addListener(callback, executor);
+ getGnssStatusTransportMultiplexer().addListener(callback, executor);
return true;
}
@@ -1919,7 +1950,7 @@ public class LocationManager {
* @param callback GNSS status callback object to remove
*/
public void unregisterGnssStatusCallback(@NonNull GnssStatus.Callback callback) {
- mGnssStatusListenerManager.removeListener(callback);
+ getGnssStatusTransportMultiplexer().removeListener(callback);
}
/**
@@ -1973,8 +2004,7 @@ public class LocationManager {
handler = new Handler();
}
- mGnssStatusListenerManager.addListener(listener, new HandlerExecutor(handler));
- return true;
+ return addNmeaListener(new HandlerExecutor(handler), listener);
}
/**
@@ -1992,7 +2022,7 @@ public class LocationManager {
public boolean addNmeaListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull OnNmeaMessageListener listener) {
- mGnssStatusListenerManager.addListener(listener, executor);
+ getGnssStatusTransportMultiplexer().addListener(listener, executor);
return true;
}
@@ -2002,7 +2032,7 @@ public class LocationManager {
* @param listener a {@link OnNmeaMessageListener} object to remove
*/
public void removeNmeaListener(@NonNull OnNmeaMessageListener listener) {
- mGnssStatusListenerManager.removeListener(listener);
+ getGnssStatusTransportMultiplexer().removeListener(listener);
}
/**
@@ -2107,7 +2137,7 @@ public class LocationManager {
@NonNull @CallbackExecutor Executor executor,
@NonNull GnssMeasurementsEvent.Callback callback) {
Preconditions.checkArgument(request != null, "invalid null request");
- mGnssMeasurementsListenerManager.addListener(request, callback, executor);
+ getGnssMeasurementsTransportMultiplexer().addListener(request, callback, executor);
return true;
}
@@ -2141,7 +2171,7 @@ public class LocationManager {
*/
public void unregisterGnssMeasurementsCallback(
@NonNull GnssMeasurementsEvent.Callback callback) {
- mGnssMeasurementsListenerManager.removeListener(callback);
+ getGnssMeasurementsTransportMultiplexer().removeListener(callback);
}
/**
@@ -2160,7 +2190,7 @@ public class LocationManager {
public boolean registerAntennaInfoListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull GnssAntennaInfo.Listener listener) {
- mGnssAntennaInfoListenerManager.addListener(listener, executor);
+ getGnssAntennaInfoTransportMultiplexer().addListener(listener, executor);
return true;
}
@@ -2170,7 +2200,7 @@ public class LocationManager {
* @param listener a {@link GnssAntennaInfo.Listener} object to remove.
*/
public void unregisterAntennaInfoListener(@NonNull GnssAntennaInfo.Listener listener) {
- mGnssAntennaInfoListenerManager.removeListener(listener);
+ getGnssAntennaInfoTransportMultiplexer().removeListener(listener);
}
/**
@@ -2229,8 +2259,7 @@ public class LocationManager {
handler = new Handler();
}
- mGnssNavigationListenerTransport.addListener(callback, new HandlerExecutor(handler));
- return true;
+ return registerGnssNavigationMessageCallback(new HandlerExecutor(handler), callback);
}
/**
@@ -2248,7 +2277,7 @@ public class LocationManager {
public boolean registerGnssNavigationMessageCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull GnssNavigationMessage.Callback callback) {
- mGnssNavigationListenerTransport.addListener(callback, executor);
+ getGnssNavigationTransportMultiplexer().addListener(callback, executor);
return true;
}
@@ -2259,7 +2288,7 @@ public class LocationManager {
*/
public void unregisterGnssNavigationMessageCallback(
@NonNull GnssNavigationMessage.Callback callback) {
- mGnssNavigationListenerTransport.removeListener(callback);
+ getGnssNavigationTransportMultiplexer().removeListener(callback);
}
/**
@@ -2311,7 +2340,7 @@ public class LocationManager {
BatchedLocationCallbackTransport transport = new BatchedLocationCallbackTransport(callback,
handler);
- synchronized (mBatchedLocationCallbackLock) {
+ synchronized (mLock) {
try {
mService.setGnssBatchingCallback(transport, mContext.getPackageName(),
mContext.getAttributionTag());
@@ -2355,7 +2384,7 @@ public class LocationManager {
@RequiresPermission(Manifest.permission.LOCATION_HARDWARE)
public boolean unregisterGnssBatchedLocationCallback(
@NonNull BatchedLocationCallback callback) {
- synchronized (mBatchedLocationCallbackLock) {
+ synchronized (mLock) {
if (callback == mBatchedLocationCallbackTransport.getCallback()) {
try {
mBatchedLocationCallbackTransport = null;
@@ -2512,185 +2541,6 @@ public class LocationManager {
}
}
- private class LocationListenerTransport extends ILocationListener.Stub {
-
- private final LocationListener mListener;
- @Nullable private volatile Executor mExecutor = null;
-
- LocationListenerTransport(@NonNull LocationListener listener) {
- Preconditions.checkArgument(listener != null, "invalid null listener");
- mListener = listener;
- }
-
- public LocationListener getKey() {
- return mListener;
- }
-
- public String getListenerId() {
- return AppOpsManager.toReceiverId(mListener);
- }
-
- public void register(@NonNull Executor executor) {
- Preconditions.checkArgument(executor != null, "invalid null executor");
- mExecutor = executor;
- }
-
- public void unregister() {
- mExecutor = null;
- }
-
- @Override
- public void onLocationChanged(Location location) {
- Executor currentExecutor = mExecutor;
- if (currentExecutor == null) {
- return;
- }
-
- PooledRunnable runnable =
- obtainRunnable(LocationListenerTransport::acceptLocation, this, currentExecutor,
- location).recycleOnUse();
- try {
- currentExecutor.execute(runnable);
- } catch (RejectedExecutionException e) {
- runnable.recycle();
- locationCallbackFinished();
- throw e;
- }
- }
-
- private void acceptLocation(Executor currentExecutor, Location location) {
- try {
- if (currentExecutor != mExecutor) {
- return;
- }
-
- // we may be under the binder identity if a direct executor is used
- long identity = Binder.clearCallingIdentity();
- try {
- mListener.onLocationChanged(location);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- } finally {
- locationCallbackFinished();
- }
- }
-
- @Override
- public void onProviderEnabled(String provider) {
- Executor currentExecutor = mExecutor;
- if (currentExecutor == null) {
- return;
- }
-
- PooledRunnable runnable =
- obtainRunnable(LocationListenerTransport::acceptProviderChange, this,
- currentExecutor, provider, true).recycleOnUse();
- try {
- currentExecutor.execute(runnable);
- } catch (RejectedExecutionException e) {
- runnable.recycle();
- locationCallbackFinished();
- throw e;
- }
- }
-
- @Override
- public void onProviderDisabled(String provider) {
- Executor currentExecutor = mExecutor;
- if (currentExecutor == null) {
- return;
- }
-
- PooledRunnable runnable =
- obtainRunnable(LocationListenerTransport::acceptProviderChange, this,
- currentExecutor, provider, false).recycleOnUse();
- try {
- currentExecutor.execute(runnable);
- } catch (RejectedExecutionException e) {
- runnable.recycle();
- locationCallbackFinished();
- throw e;
- }
- }
-
- private void acceptProviderChange(Executor currentExecutor, String provider,
- boolean enabled) {
- try {
- if (currentExecutor != mExecutor) {
- return;
- }
-
- // we may be under the binder identity if a direct executor is used
- long identity = Binder.clearCallingIdentity();
- try {
- if (enabled) {
- mListener.onProviderEnabled(provider);
- } else {
- mListener.onProviderDisabled(provider);
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- } finally {
- locationCallbackFinished();
- }
- }
-
- @Override
- public void onRemoved() {
- // TODO: onRemoved is necessary to GC hanging listeners, but introduces some interesting
- // broken edge cases. luckily these edge cases are quite unlikely. consider the
- // following interleaving for instance:
- // 1) client adds single shot location request (A)
- // 2) client gets removal callback, and schedules it for execution
- // 3) client replaces single shot request with a different location request (B)
- // 4) prior removal callback is executed, removing location request (B) incorrectly
- // what's needed is a way to identify which listener a callback belongs to. currently
- // we reuse the same transport object for the same listeners (so that we don't leak
- // transport objects on the server side). there seem to be two solutions:
- // 1) when reregistering a request, first unregister the current transport, then
- // register with a new transport object (never reuse transport objects) - the
- // downside is that this breaks the server's knowledge that the request is the
- // same object, and thus breaks optimizations such as reusing the same transport
- // state.
- // 2) pass some other type of marker in addition to the transport (for example an
- // incrementing integer representing the transport "version"), and pass this
- // marker back into callbacks so that each callback knows which transport
- // "version" it belongs to and can not execute itself if the version does not
- // match.
- // (1) seems like the preferred solution as it's simpler to implement and the above
- // mentioned server optimizations are not terribly important (they can be bypassed by
- // clients that use a new listener every time anyways).
-
- Executor currentExecutor = mExecutor;
- if (currentExecutor == null) {
- // we've already been unregistered, no work to do anyways
- return;
- }
-
- // must be executed on the same executor so callback execution cannot be reordered
- currentExecutor.execute(() -> {
- if (currentExecutor != mExecutor) {
- return;
- }
-
- unregister();
- synchronized (mListeners) {
- mListeners.remove(mListener, this);
- }
- });
- }
-
- private void locationCallbackFinished() {
- try {
- mService.locationCallbackFinished(this);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- }
-
private static class NmeaAdapter extends GnssStatus.Callback implements OnNmeaMessageListener {
private final OnNmeaMessageListener mListener;
diff --git a/location/java/android/location/util/LocationListenerTransportManager.java b/location/java/android/location/util/LocationListenerTransportManager.java
new file mode 100644
index 000000000000..f16f7e148830
--- /dev/null
+++ b/location/java/android/location/util/LocationListenerTransportManager.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * 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 android.location.util;
+
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.location.ILocationListener;
+import android.location.ILocationManager;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationRequest;
+import android.os.RemoteException;
+
+import com.android.internal.listeners.ListenerTransportManager;
+import com.android.internal.listeners.RequestListenerTransport;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Utility class for managing location listeners.
+ *
+ * @hide
+ */
+public class LocationListenerTransportManager extends
+ ListenerTransportManager<LocationListenerTransportManager.LocationListenerTransport> {
+
+ protected class LocationListenerTransport extends
+ RequestListenerTransport<LocationRequest, LocationListener> {
+
+ private final ILocationListener mBinderTransport;
+
+ private final String mPackageName;
+ @Nullable private final String mAttributionTag;
+ private final String mListenerId;
+
+ LocationListenerTransport(Context context, LocationRequest locationRequest,
+ Executor executor, LocationListener locationListener) {
+ super(locationRequest, executor, locationListener);
+
+ mBinderTransport = new LocationListenerBinder(this);
+
+ mPackageName = context.getPackageName();
+ mAttributionTag = context.getAttributionTag();
+ mListenerId = AppOpsManager.toReceiverId(locationListener);
+ }
+
+ ILocationListener getTransport() {
+ return mBinderTransport;
+ }
+
+ String getPackageName() {
+ return mPackageName;
+ }
+
+ String getAttributionTag() {
+ return mAttributionTag;
+ }
+
+ String getListenerId() {
+ return mListenerId;
+ }
+
+ public void onLocationChanged(Location location) {
+ execute(listener -> {
+ listener.onLocationChanged(location);
+ try {
+ mService.locationCallbackFinished(mBinderTransport);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ });
+ }
+
+ public void onProviderEnabled(String provider) {
+ execute(listener -> {
+ listener.onProviderEnabled(provider);
+ try {
+ mService.locationCallbackFinished(mBinderTransport);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ });
+ }
+
+ public void onProviderDisabled(String provider) {
+ execute(listener -> {
+ listener.onProviderDisabled(provider);
+ try {
+ mService.locationCallbackFinished(mBinderTransport);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ });
+ }
+
+ public void onRemoved() {
+ // must be executed on the same executor so callbacks cannot be reordered
+ execute(listener -> removeTransport(listener, this));
+ }
+ }
+
+ final ILocationManager mService;
+
+ public LocationListenerTransportManager(ILocationManager service) {
+ mService = service;
+ }
+
+ /** Adds the given listener. */
+ public void addListener(Context context, LocationRequest locationRequest, Executor executor,
+ LocationListener listener) {
+ registerListener(listener,
+ new LocationListenerTransport(context, locationRequest, executor, listener));
+ }
+
+ /** Removes the given listener. */
+ public void removeListener(LocationListener listener) {
+ unregisterListener(listener);
+ }
+
+ @Override
+ protected void registerWithServer(LocationListenerTransport transport) throws RemoteException {
+ mService.registerLocationListener(transport.getRequest(), transport.getTransport(),
+ transport.getPackageName(), transport.getAttributionTag(),
+ transport.getListenerId());
+ }
+
+ @Override
+ protected void unregisterWithServer(LocationListenerTransport transport)
+ throws RemoteException {
+ mService.unregisterLocationListener(transport.getTransport());
+ }
+
+ private static class LocationListenerBinder extends ILocationListener.Stub {
+
+ private final LocationListenerTransport mListener;
+
+ LocationListenerBinder(LocationListenerTransport listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void onLocationChanged(Location location) {
+ mListener.onLocationChanged(location);
+ }
+
+ @Override
+ public void onProviderEnabled(String provider) {
+ mListener.onProviderEnabled(provider);
+ }
+
+ @Override
+ public void onProviderDisabled(String provider) {
+ mListener.onProviderDisabled(provider);
+ }
+
+ @Override
+ public void onRemoved() {
+ mListener.onRemoved();
+ }
+ }
+}