Add Executor APIs to LocationManager
Add executor support for all LM APIs and consolidate various GNSS
listeners. Also fixes some edge cases around multithreaded listener
callback delivery.
Bug: 136212299
Test: Manual
Change-Id: I2e863e41de846e739654362e859b58a5ed45c673
diff --git a/api/current.txt b/api/current.txt
index b4d110e..ad0a5e5 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -23089,8 +23089,9 @@
public class LocationManager {
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean addGpsStatusListener(android.location.GpsStatus.Listener);
- method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean addNmeaListener(@NonNull android.location.OnNmeaMessageListener);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean addNmeaListener(@NonNull android.location.OnNmeaMessageListener);
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean addNmeaListener(@NonNull android.location.OnNmeaMessageListener, @Nullable android.os.Handler);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean addNmeaListener(@NonNull java.util.concurrent.Executor, @NonNull android.location.OnNmeaMessageListener);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void addProximityAlert(double, double, float, long, @NonNull android.app.PendingIntent);
method public void addTestProvider(@NonNull String, boolean, boolean, boolean, boolean, boolean, boolean, boolean, int, int);
method @Deprecated public void clearTestProviderEnabled(@NonNull String);
@@ -23107,12 +23108,15 @@
method @NonNull public java.util.List<java.lang.String> getProviders(@NonNull android.location.Criteria, boolean);
method public boolean isLocationEnabled();
method public boolean isProviderEnabled(@NonNull String);
- method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssMeasurementsCallback(@NonNull android.location.GnssMeasurementsEvent.Callback);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssMeasurementsCallback(@NonNull android.location.GnssMeasurementsEvent.Callback);
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssMeasurementsCallback(@NonNull android.location.GnssMeasurementsEvent.Callback, @Nullable android.os.Handler);
- method public boolean registerGnssNavigationMessageCallback(@NonNull android.location.GnssNavigationMessage.Callback);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssMeasurementsCallback(@NonNull java.util.concurrent.Executor, @NonNull android.location.GnssMeasurementsEvent.Callback);
+ method @Deprecated public boolean registerGnssNavigationMessageCallback(@NonNull android.location.GnssNavigationMessage.Callback);
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssNavigationMessageCallback(@NonNull android.location.GnssNavigationMessage.Callback, @Nullable android.os.Handler);
- method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssStatusCallback(@NonNull android.location.GnssStatus.Callback);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssNavigationMessageCallback(@NonNull java.util.concurrent.Executor, @NonNull android.location.GnssNavigationMessage.Callback);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssStatusCallback(@NonNull android.location.GnssStatus.Callback);
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssStatusCallback(@NonNull android.location.GnssStatus.Callback, @Nullable android.os.Handler);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssStatusCallback(@NonNull java.util.concurrent.Executor, @NonNull android.location.GnssStatus.Callback);
method @Deprecated public void removeGpsStatusListener(android.location.GpsStatus.Listener);
method public void removeNmeaListener(@NonNull android.location.OnNmeaMessageListener);
method @RequiresPermission(anyOf={"android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"}, apis="..22") public void removeProximityAlert(@NonNull android.app.PendingIntent);
@@ -23121,7 +23125,9 @@
method public void removeUpdates(@NonNull android.app.PendingIntent);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull String, long, float, @NonNull android.location.LocationListener);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull String, long, float, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
+ method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull String, long, float, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(long, float, @NonNull android.location.Criteria, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
+ method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(long, float, @NonNull android.location.Criteria, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull String, long, float, @NonNull android.app.PendingIntent);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(long, float, @NonNull android.location.Criteria, @NonNull android.app.PendingIntent);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestSingleUpdate(@NonNull String, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
diff --git a/api/system-current.txt b/api/system-current.txt
index 283433e..18c1ece 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3430,6 +3430,7 @@
method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public boolean isProviderPackage(@NonNull String);
method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean registerGnssBatchedLocationCallback(long, boolean, @NonNull android.location.BatchedLocationCallback, @Nullable android.os.Handler);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull android.location.LocationRequest, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
+ method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull android.location.LocationRequest, @NonNull android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void setExtraLocationControllerPackage(@Nullable String);
method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void setExtraLocationControllerPackageEnabled(boolean);
diff --git a/api/test-current.txt b/api/test-current.txt
index 34312f6..70f84a1 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1085,6 +1085,7 @@
method @NonNull public String[] getIgnoreSettingsWhitelist();
method @NonNull public java.util.List<android.location.LocationRequest> getTestProviderCurrentRequests(String);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull android.location.LocationRequest, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
+ method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull android.location.LocationRequest, @NonNull android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setLocationEnabledForUser(boolean, @NonNull android.os.UserHandle);
}
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index a6e1f0a..a16399c 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -218,7 +218,6 @@
Landroid/location/ILocationManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/location/ILocationManager;
Landroid/location/ILocationManager$Stub;->TRANSACTION_getAllProviders:I
Landroid/location/INetInitiatedListener$Stub;-><init>()V
-Landroid/location/LocationManager$ListenerTransport;-><init>(Landroid/location/LocationManager;Landroid/location/LocationListener;Landroid/os/Looper;)V
Landroid/Manifest$permission;->CAPTURE_SECURE_VIDEO_OUTPUT:Ljava/lang/String;
Landroid/Manifest$permission;->CAPTURE_VIDEO_OUTPUT:Ljava/lang/String;
Landroid/Manifest$permission;->READ_FRAME_BUFFER:Ljava/lang/String;
diff --git a/location/java/android/location/AbstractListenerManager.java b/location/java/android/location/AbstractListenerManager.java
new file mode 100644
index 0000000..c41023e
--- /dev/null
+++ b/location/java/android/location/AbstractListenerManager.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2014 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * A base class to manage listeners that have a 1:N -> source:listener relationship.
+ *
+ * @hide
+ */
+abstract class AbstractListenerManager<T> {
+
+ private static class Registration<T> {
+ private final Executor mExecutor;
+ @Nullable private volatile T mListener;
+
+ private Registration(Executor executor, T listener) {
+ Preconditions.checkArgument(listener != null);
+ Preconditions.checkArgument(executor != null);
+ mExecutor = executor;
+ mListener = listener;
+ }
+
+ private void unregister() {
+ mListener = null;
+ }
+
+ private void execute(Consumer<T> operation) {
+ mExecutor.execute(() -> {
+ T listener = mListener;
+ if (listener == null) {
+ return;
+ }
+
+ // we may be under the binder identity if a direct executor is used
+ long identity = Binder.clearCallingIdentity();
+ try {
+ operation.accept(listener);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ });
+ }
+ }
+
+ @GuardedBy("mListeners")
+ private final ArrayMap<Object, Registration<T>> mListeners = new ArrayMap<>();
+
+ public boolean addListener(@NonNull T listener, @NonNull Handler handler)
+ throws RemoteException {
+ return addInternal(listener, handler);
+ }
+
+ public boolean addListener(@NonNull T listener, @NonNull Executor executor)
+ throws RemoteException {
+ return addInternal(listener, executor);
+ }
+
+ protected final boolean addInternal(Object listener, Handler handler) throws RemoteException {
+ return addInternal(listener, new HandlerExecutor(handler));
+ }
+
+ protected final boolean addInternal(Object listener, Executor executor) throws RemoteException {
+ return addInternal(listener, new Registration<>(executor, convertKey(listener)));
+ }
+
+ private boolean addInternal(Object key, Registration<T> registration) throws RemoteException {
+ Preconditions.checkNotNull(key);
+ Preconditions.checkNotNull(registration);
+
+ synchronized (mListeners) {
+ if (mListeners.isEmpty() && !registerService()) {
+ return false;
+ }
+ Registration<T> oldRegistration = mListeners.put(key, registration);
+ if (oldRegistration != null) {
+ oldRegistration.unregister();
+ }
+ return true;
+ }
+ }
+
+ public void removeListener(Object listener) throws RemoteException {
+ synchronized (mListeners) {
+ Registration<T> oldRegistration = mListeners.remove(listener);
+ if (oldRegistration == null) {
+ return;
+ }
+ oldRegistration.unregister();
+
+ if (mListeners.isEmpty()) {
+ unregisterService();
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ protected T convertKey(@NonNull Object listener) {
+ return (T) listener;
+ }
+
+ protected abstract boolean registerService() throws RemoteException;
+ protected abstract void unregisterService() throws RemoteException;
+
+ protected void execute(Consumer<T> operation) {
+ synchronized (mListeners) {
+ for (Registration<T> registration : mListeners.values()) {
+ registration.execute(operation);
+ }
+ }
+ }
+}
diff --git a/location/java/android/location/BatchedLocationCallbackTransport.java b/location/java/android/location/BatchedLocationCallbackTransport.java
deleted file mode 100644
index e00f855..0000000
--- a/location/java/android/location/BatchedLocationCallbackTransport.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2017 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;
-
-import android.content.Context;
-import android.os.RemoteException;
-
-import java.util.List;
-
-/**
- * A handler class to manage transport callbacks for {@link BatchedLocationCallback}.
- *
- * @hide
- */
-class BatchedLocationCallbackTransport
- extends LocalListenerHelper<BatchedLocationCallback> {
- private final ILocationManager mLocationManager;
-
- private final IBatchedLocationCallback mCallbackTransport = new CallbackTransport();
-
- public BatchedLocationCallbackTransport(Context context, ILocationManager locationManager) {
- super(context, "BatchedLocationCallbackTransport");
- mLocationManager = locationManager;
- }
-
- @Override
- protected boolean registerWithServer() throws RemoteException {
- return mLocationManager.addGnssBatchingCallback(
- mCallbackTransport,
- getContext().getPackageName());
- }
-
- @Override
- protected void unregisterFromServer() throws RemoteException {
- mLocationManager.removeGnssBatchingCallback();
- }
-
- private class CallbackTransport extends IBatchedLocationCallback.Stub {
- @Override
- public void onLocationBatch(final List<Location> locations) {
- ListenerOperation<BatchedLocationCallback> operation =
- new ListenerOperation<BatchedLocationCallback>() {
- @Override
- public void execute(BatchedLocationCallback callback)
- throws RemoteException {
- callback.onLocationBatch(locations);
- }
- };
- foreach(operation);
- }
- }
-}
diff --git a/location/java/android/location/GnssMeasurementCallbackTransport.java b/location/java/android/location/GnssMeasurementCallbackTransport.java
deleted file mode 100644
index 8cb8c0b..0000000
--- a/location/java/android/location/GnssMeasurementCallbackTransport.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2014 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;
-
-import android.content.Context;
-import android.os.RemoteException;
-
-import com.android.internal.util.Preconditions;
-
-/**
- * A handler class to manage transport callbacks for {@link GnssMeasurementsEvent.Callback}.
- *
- * @hide
- */
-class GnssMeasurementCallbackTransport
- extends LocalListenerHelper<GnssMeasurementsEvent.Callback> {
- private static final String TAG = "GnssMeasCbTransport";
- private final ILocationManager mLocationManager;
-
- private final IGnssMeasurementsListener mListenerTransport = new ListenerTransport();
-
- public GnssMeasurementCallbackTransport(Context context, ILocationManager locationManager) {
- super(context, TAG);
- mLocationManager = locationManager;
- }
-
- @Override
- protected boolean registerWithServer() throws RemoteException {
- return mLocationManager.addGnssMeasurementsListener(
- mListenerTransport,
- getContext().getPackageName());
- }
-
- @Override
- protected void unregisterFromServer() throws RemoteException {
- mLocationManager.removeGnssMeasurementsListener(mListenerTransport);
- }
-
- /**
- * Injects GNSS measurement corrections into the GNSS chipset.
- *
- * @param measurementCorrections a {@link GnssMeasurementCorrections} object with the GNSS
- * measurement corrections to be injected into the GNSS chipset.
- */
- protected void injectGnssMeasurementCorrections(
- GnssMeasurementCorrections measurementCorrections) throws RemoteException {
- Preconditions.checkNotNull(measurementCorrections);
- mLocationManager.injectGnssMeasurementCorrections(
- measurementCorrections, getContext().getPackageName());
- }
-
- protected long getGnssCapabilities() throws RemoteException {
- return mLocationManager.getGnssCapabilities(getContext().getPackageName());
- }
-
- private class ListenerTransport extends IGnssMeasurementsListener.Stub {
- @Override
- public void onGnssMeasurementsReceived(final GnssMeasurementsEvent event) {
- ListenerOperation<GnssMeasurementsEvent.Callback> operation =
- new ListenerOperation<GnssMeasurementsEvent.Callback>() {
- @Override
- public void execute(GnssMeasurementsEvent.Callback callback)
- throws RemoteException {
- callback.onGnssMeasurementsReceived(event);
- }
- };
- foreach(operation);
- }
-
- @Override
- public void onStatusChanged(final int status) {
- ListenerOperation<GnssMeasurementsEvent.Callback> operation =
- new ListenerOperation<GnssMeasurementsEvent.Callback>() {
- @Override
- public void execute(GnssMeasurementsEvent.Callback callback)
- throws RemoteException {
- callback.onStatusChanged(status);
- }
- };
- foreach(operation);
- }
- }
-}
diff --git a/location/java/android/location/GnssNavigationMessageCallbackTransport.java b/location/java/android/location/GnssNavigationMessageCallbackTransport.java
deleted file mode 100644
index 1eafd02..0000000
--- a/location/java/android/location/GnssNavigationMessageCallbackTransport.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2014 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;
-
-import android.content.Context;
-import android.os.RemoteException;
-
-/**
- * A handler class to manage transport callback for {@link GnssNavigationMessage.Callback}.
- *
- * @hide
- */
-class GnssNavigationMessageCallbackTransport
- extends LocalListenerHelper<GnssNavigationMessage.Callback> {
- private final ILocationManager mLocationManager;
-
- private final IGnssNavigationMessageListener mListenerTransport = new ListenerTransport();
-
- public GnssNavigationMessageCallbackTransport(
- Context context,
- ILocationManager locationManager) {
- super(context, "GnssNavigationMessageCallbackTransport");
- mLocationManager = locationManager;
- }
-
- @Override
- protected boolean registerWithServer() throws RemoteException {
- return mLocationManager.addGnssNavigationMessageListener(
- mListenerTransport,
- getContext().getPackageName());
- }
-
- @Override
- protected void unregisterFromServer() throws RemoteException {
- mLocationManager.removeGnssNavigationMessageListener(mListenerTransport);
- }
-
- private class ListenerTransport extends IGnssNavigationMessageListener.Stub {
- @Override
- public void onGnssNavigationMessageReceived(final GnssNavigationMessage event) {
- ListenerOperation<GnssNavigationMessage.Callback> operation =
- new ListenerOperation<GnssNavigationMessage.Callback>() {
- @Override
- public void execute(GnssNavigationMessage.Callback callback)
- throws RemoteException {
- callback.onGnssNavigationMessageReceived(event);
- }
- };
- foreach(operation);
- }
-
- @Override
- public void onStatusChanged(final int status) {
- ListenerOperation<GnssNavigationMessage.Callback> operation =
- new ListenerOperation<GnssNavigationMessage.Callback>() {
- @Override
- public void execute(GnssNavigationMessage.Callback callback)
- throws RemoteException {
- callback.onStatusChanged(status);
- }
- };
- foreach(operation);
- }
- }
-}
diff --git a/location/java/android/location/LocalListenerHelper.java b/location/java/android/location/LocalListenerHelper.java
deleted file mode 100644
index 592d01d..0000000
--- a/location/java/android/location/LocalListenerHelper.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2014 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;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.internal.util.Preconditions;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * A base handler class to manage transport and local listeners.
- *
- * @hide
- */
-abstract class LocalListenerHelper<TListener> {
- private final HashMap<TListener, Handler> mListeners = new HashMap<>();
-
- private final String mTag;
- private final Context mContext;
-
- protected LocalListenerHelper(Context context, String name) {
- Preconditions.checkNotNull(name);
- mContext = context;
- mTag = name;
- }
-
- /**
- * Adds a {@param listener} to the list of listeners on which callbacks will be executed. The
- * execution will happen on the {@param handler} thread or alternatively in the callback thread
- * if a {@code null} handler value is passed.
- */
- public boolean add(@NonNull TListener listener, Handler handler) {
- Preconditions.checkNotNull(listener);
- synchronized (mListeners) {
- // we need to register with the service first, because we need to find out if the
- // service will actually support the request before we attempt anything
- if (mListeners.isEmpty()) {
- boolean registeredWithService;
- try {
- registeredWithService = registerWithServer();
- } catch (RemoteException e) {
- Log.e(mTag, "Error handling first listener.", e);
- return false;
- }
- if (!registeredWithService) {
- Log.e(mTag, "Unable to register listener transport.");
- return false;
- }
- }
- if (mListeners.containsKey(listener)) {
- return true;
- }
- mListeners.put(listener, handler);
- return true;
- }
- }
-
- public void remove(@NonNull TListener listener) {
- Preconditions.checkNotNull(listener);
- synchronized (mListeners) {
- boolean removed = mListeners.containsKey(listener);
- mListeners.remove(listener);
- boolean isLastRemoved = removed && mListeners.isEmpty();
- if (isLastRemoved) {
- try {
- unregisterFromServer();
- } catch (RemoteException e) {
- Log.v(mTag, "Error handling last listener removal", e);
- }
- }
- }
- }
-
- protected abstract boolean registerWithServer() throws RemoteException;
- protected abstract void unregisterFromServer() throws RemoteException;
-
- protected interface ListenerOperation<TListener> {
- void execute(TListener listener) throws RemoteException;
- }
-
- protected Context getContext() {
- return mContext;
- }
-
- private void executeOperation(ListenerOperation<TListener> operation, TListener listener) {
- try {
- operation.execute(listener);
- } catch (RemoteException e) {
- Log.e(mTag, "Error in monitored listener.", e);
- // don't return, give a fair chance to all listeners to receive the event
- }
- }
-
- protected void foreach(final ListenerOperation<TListener> operation) {
- Collection<Map.Entry<TListener, Handler>> listeners;
- synchronized (mListeners) {
- listeners = new ArrayList<>(mListeners.entrySet());
- }
- for (final Map.Entry<TListener, Handler> listener : listeners) {
- if (listener.getValue() == null) {
- executeOperation(operation, listener.getKey());
- } else {
- listener.getValue().post(new Runnable() {
- @Override
- public void run() {
- executeOperation(operation, listener.getKey());
- }
- });
- }
- }
- }
-}
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 5be4770..7cf5bb8 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -22,6 +22,7 @@
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import android.Manifest;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
@@ -33,13 +34,13 @@
import android.annotation.UnsupportedAppUsage;
import android.app.PendingIntent;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.PackageManager;
+import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.Looper;
-import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -47,47 +48,32 @@
import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.location.ProviderProperties;
import com.android.internal.util.Preconditions;
-import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
/**
- * This class provides access to the system location services. These
- * services allow applications to obtain periodic updates of the
- * device's geographical location, or to fire an application-specified
- * {@link Intent} when the device enters the proximity of a given
- * geographical location.
+ * This class provides access to the system location services. These services allow applications to
+ * obtain periodic updates of the device's geographical location, or to be notified when the device
+ * enters the proximity of a given geographical location.
*
- * <p class="note">Unless noted, all Location API methods require
- * the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} or
- * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permissions.
- * If your application only has the coarse permission then it will not have
- * access to the GPS or passive location providers. Other providers will still
- * return location results, but the update rate will be throttled and the exact
- * location will be obfuscated to a coarse level of accuracy.
+ * <p class="note">Unless noted, all Location API methods require the {@link
+ * android.Manifest.permission#ACCESS_COARSE_LOCATION} or {@link
+ * android.Manifest.permission#ACCESS_FINE_LOCATION} permissions. If your application only has the
+ * coarse permission then it will not have access to fine location providers. Other providers will
+ * still return location results, but the exact location will be obfuscated to a coarse level of
+ * accuracy.
*/
+@SuppressWarnings({"deprecation", "DeprecatedIsStillUsed"})
@SystemService(Context.LOCATION_SERVICE)
@RequiresFeature(PackageManager.FEATURE_LOCATION)
public class LocationManager {
- private static final String TAG = "LocationManager";
- private final Context mContext;
- @UnsupportedAppUsage
- private final ILocationManager mService;
- private final GnssMeasurementCallbackTransport mGnssMeasurementCallbackTransport;
- private final GnssNavigationMessageCallbackTransport mGnssNavigationMessageCallbackTransport;
- private final BatchedLocationCallbackTransport mBatchedLocationCallbackTransport;
- private final ArrayMap<GnssStatus.Callback, GnssStatusListenerTransport> mGnssStatusListeners =
- new ArrayMap<>();
- private final ArrayMap<OnNmeaMessageListener, GnssStatusListenerTransport> mGnssNmeaListeners =
- new ArrayMap<>();
- private final ArrayMap<GpsStatus.Listener, GnssStatusListenerTransport> mGpsStatusListeners =
- new ArrayMap<>();
- // volatile + GnssStatus final-fields pattern to avoid a partially published object
- private volatile GnssStatus mGnssStatus;
- private int mTimeToFirstFix;
+ private static final String TAG = "LocationManager";
/**
* Name of the network location provider.
@@ -238,112 +224,31 @@
public static final String METADATA_SETTINGS_FOOTER_STRING =
"com.android.settings.location.FOOTER_STRING";
- // Map from LocationListeners to their associated ListenerTransport objects
- private final ArrayMap<LocationListener, ListenerTransport> mListeners = new ArrayMap<>();
+ private final Context mContext;
- private class ListenerTransport extends ILocationListener.Stub {
- private static final int TYPE_LOCATION_CHANGED = 1;
- private static final int TYPE_STATUS_CHANGED = 2;
- private static final int TYPE_PROVIDER_ENABLED = 3;
- private static final int TYPE_PROVIDER_DISABLED = 4;
+ @UnsupportedAppUsage
+ private final ILocationManager mService;
- private LocationListener mListener;
- private final Handler mListenerHandler;
+ @GuardedBy("mListeners")
+ private final ArrayMap<LocationListener, LocationListenerTransport> mListeners =
+ new ArrayMap<>();
- ListenerTransport(LocationListener listener, Looper looper) {
- mListener = listener;
+ @GuardedBy("mBatchedLocationCallbackManager")
+ private final BatchedLocationCallbackManager mBatchedLocationCallbackManager =
+ new BatchedLocationCallbackManager();
+ private final GnssStatusListenerManager
+ mGnssStatusListenerManager = new GnssStatusListenerManager();
+ private final GnssMeasurementsListenerManager mGnssMeasurementsListenerManager =
+ new GnssMeasurementsListenerManager();
+ private final GnssNavigationMessageListenerManager mGnssNavigationMessageListenerTransport =
+ new GnssNavigationMessageListenerManager();
- if (looper == null) {
- mListenerHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- _handleMessage(msg);
- }
- };
- } else {
- mListenerHandler = new Handler(looper) {
- @Override
- public void handleMessage(Message msg) {
- _handleMessage(msg);
- }
- };
- }
- }
-
- @Override
- public void onLocationChanged(Location location) {
- Message msg = Message.obtain();
- msg.what = TYPE_LOCATION_CHANGED;
- msg.obj = location;
- sendCallbackMessage(msg);
- }
-
- @Override
- public void onStatusChanged(String provider, int status, Bundle extras) {
- Message msg = Message.obtain();
- msg.what = TYPE_STATUS_CHANGED;
- Bundle b = new Bundle();
- b.putString("provider", provider);
- b.putInt("status", status);
- if (extras != null) {
- b.putBundle("extras", extras);
- }
- msg.obj = b;
- sendCallbackMessage(msg);
- }
-
- @Override
- public void onProviderEnabled(String provider) {
- Message msg = Message.obtain();
- msg.what = TYPE_PROVIDER_ENABLED;
- msg.obj = provider;
- sendCallbackMessage(msg);
- }
-
- @Override
- public void onProviderDisabled(String provider) {
- Message msg = Message.obtain();
- msg.what = TYPE_PROVIDER_DISABLED;
- msg.obj = provider;
- sendCallbackMessage(msg);
- }
-
- private void sendCallbackMessage(Message msg) {
- if (!mListenerHandler.sendMessage(msg)) {
- locationCallbackFinished();
- }
- }
-
- private void _handleMessage(Message msg) {
- switch (msg.what) {
- case TYPE_LOCATION_CHANGED:
- Location location = new Location((Location) msg.obj);
- mListener.onLocationChanged(location);
- break;
- case TYPE_STATUS_CHANGED:
- Bundle b = (Bundle) msg.obj;
- String provider = b.getString("provider");
- int status = b.getInt("status");
- Bundle extras = b.getBundle("extras");
- mListener.onStatusChanged(provider, status, extras);
- break;
- case TYPE_PROVIDER_ENABLED:
- mListener.onProviderEnabled((String) msg.obj);
- break;
- case TYPE_PROVIDER_DISABLED:
- mListener.onProviderDisabled((String) msg.obj);
- break;
- }
- locationCallbackFinished();
- }
-
- private void locationCallbackFinished() {
- try {
- mService.locationCallbackFinished(this);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
+ /**
+ * @hide
+ */
+ public LocationManager(@NonNull Context context, @NonNull ILocationManager service) {
+ mService = service;
+ mContext = context;
}
/**
@@ -370,24 +275,6 @@
}
}
- /**
- * @hide - hide this constructor because it has a parameter
- * of type ILocationManager, which is a system private class. The
- * right way to create an instance of this class is using the
- * factory Context.getSystemService.
- */
- public LocationManager(@NonNull Context context, @NonNull ILocationManager service) {
- mService = service;
- mContext = context;
- mGnssMeasurementCallbackTransport =
- new GnssMeasurementCallbackTransport(mContext, mService);
- mGnssNavigationMessageCallbackTransport =
- new GnssNavigationMessageCallbackTransport(mContext, mService);
- mBatchedLocationCallbackTransport =
- new BatchedLocationCallbackTransport(mContext, mService);
-
- }
-
private LocationProvider createProvider(String name, ProviderProperties properties) {
return new LocationProvider(name, properties);
}
@@ -524,7 +411,7 @@
LocationRequest request = LocationRequest.createFromDeprecatedProvider(
provider, minTime, minDistance, false);
- requestLocationUpdates(request, listener, null, null);
+ requestLocationUpdates(request, listener, null);
}
/**
@@ -556,7 +443,36 @@
LocationRequest request = LocationRequest.createFromDeprecatedProvider(
provider, minTime, minDistance, false);
- requestLocationUpdates(request, listener, looper, null);
+ requestLocationUpdates(request, listener, looper);
+ }
+
+ /**
+ * Register for location updates from the given provider with the given arguments. {@link
+ * LocationListener} callbacks will take place on the given {@link Executor}. Only one request
+ * can be registered for each unique listener, so any subsequent requests with the same listener
+ * will overwrite all associated arguments.
+ *
+ * <p>See {@link #requestLocationUpdates(String, long, float, LocationListener, Looper)} for
+ * more information.
+ *
+ * @param provider the name of the provider used for location updates
+ * @param minTimeMs minimum time interval between location updates, in milliseconds
+ * @param minDistanceM minimum distance between location updates, in meters
+ * @param executor all listener updates will take place on this {@link Executor}
+ * @param listener a {@link LocationListener} that will be called when updates are available
+ * @throws IllegalArgumentException if provider, listener, or looper is null or nonexistant
+ * @throws SecurityException if no suitable permission is present
+ */
+ @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+ public void requestLocationUpdates(
+ @NonNull String provider,
+ long minTimeMs,
+ float minDistanceM,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull LocationListener listener) {
+ LocationRequest request = LocationRequest.createFromDeprecatedProvider(
+ provider, minTimeMs, minDistanceM, false);
+ requestLocationUpdates(request, executor, listener);
}
/**
@@ -589,7 +505,33 @@
LocationRequest request = LocationRequest.createFromDeprecatedCriteria(
criteria, minTime, minDistance, false);
- requestLocationUpdates(request, listener, looper, null);
+ requestLocationUpdates(request, listener, looper);
+ }
+
+ /**
+ * Uses the given {@link Criteria} to select a single provider to use for location updates.
+ *
+ * <p>See {@link #requestLocationUpdates(String, long, float, Executor, LocationListener)} for
+ * more information.
+ *
+ * @param minTimeMs minimum time interval between location updates, in milliseconds
+ * @param minDistanceM minimum distance between location updates, in meters
+ * @param criteria the {@link Criteria} used to select a provider for location updates
+ * @param executor all listener updates will take place on this {@link Executor}
+ * @param listener a {@link LocationListener} that will be called when updates are available
+ * @throws IllegalArgumentException if criteria, listener, or looper is null
+ * @throws SecurityException if no suitable permission is present
+ */
+ @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+ public void requestLocationUpdates(
+ long minTimeMs,
+ float minDistanceM,
+ @NonNull Criteria criteria,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull LocationListener listener) {
+ LocationRequest request = LocationRequest.createFromDeprecatedCriteria(
+ criteria, minTimeMs, minDistanceM, false);
+ requestLocationUpdates(request, executor, listener);
}
/**
@@ -617,7 +559,7 @@
LocationRequest request = LocationRequest.createFromDeprecatedProvider(
provider, minTime, minDistance, false);
- requestLocationUpdates(request, null, null, intent);
+ requestLocationUpdates(request, intent);
}
/**
@@ -724,7 +666,7 @@
LocationRequest request = LocationRequest.createFromDeprecatedCriteria(
criteria, minTime, minDistance, false);
- requestLocationUpdates(request, null, null, intent);
+ requestLocationUpdates(request, intent);
}
/**
@@ -754,7 +696,7 @@
LocationRequest request = LocationRequest.createFromDeprecatedProvider(
provider, 0, 0, true);
- requestLocationUpdates(request, listener, looper, null);
+ requestLocationUpdates(request, listener, looper);
}
/**
@@ -787,7 +729,7 @@
LocationRequest request = LocationRequest.createFromDeprecatedCriteria(
criteria, 0, 0, true);
- requestLocationUpdates(request, listener, looper, null);
+ requestLocationUpdates(request, listener, looper);
}
/**
@@ -810,7 +752,7 @@
LocationRequest request = LocationRequest.createFromDeprecatedProvider(
provider, 0, 0, true);
- requestLocationUpdates(request, null, null, intent);
+ requestLocationUpdates(request, intent);
}
/**
@@ -834,7 +776,7 @@
LocationRequest request = LocationRequest.createFromDeprecatedCriteria(
criteria, 0, 0, true);
- requestLocationUpdates(request, null, null, intent);
+ requestLocationUpdates(request, intent);
}
/**
@@ -881,7 +823,7 @@
*
* <p> To unregister for Location updates, use: {@link #removeUpdates(LocationListener)}.
*
- * @param request quality of service required, null for default low power
+ * @param locationRequest quality of service required, null for default low power
* @param listener a {@link LocationListener} whose
* {@link LocationListener#onLocationChanged} method will be called when
* the location update is available
@@ -898,13 +840,37 @@
@TestApi
@RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
public void requestLocationUpdates(
- @NonNull LocationRequest request,
+ @NonNull LocationRequest locationRequest,
@NonNull LocationListener listener,
@Nullable Looper looper) {
- checkListener(listener);
- requestLocationUpdates(request, listener, looper, null);
+ requestLocationUpdates(locationRequest,
+ new LocationListenerTransport(looper == null ? new Handler() : new Handler(looper),
+ listener));
}
+ /**
+ * Register for location updates with the given {@link LocationRequest}.
+ *
+ * <p>See {@link #requestLocationUpdates(String, long, float, Executor, LocationListener)} for
+ * more information.
+ *
+ * @param locationRequest the {@link LocationRequest} being made
+ * @param executor all listener updates will take place on this {@link Executor}
+ * @param listener a {@link LocationListener} that will be called when updates are
+ * available
+ * @throws IllegalArgumentException if locationRequest, listener, or executor is null
+ * @throws SecurityException if no suitable permission is present
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+ public void requestLocationUpdates(
+ @NonNull LocationRequest locationRequest,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull LocationListener listener) {
+ requestLocationUpdates(locationRequest, new LocationListenerTransport(executor, listener));
+ }
/**
* Register for fused location updates using a LocationRequest and a pending intent.
@@ -918,8 +884,8 @@
* <p> See {@link #requestLocationUpdates(LocationRequest, LocationListener, Looper)}
* for more detail.
*
- * @param request quality of service required, null for default low power
- * @param intent a {@link PendingIntent} to be sent for the location update
+ * @param locationRequest quality of service required, null for default low power
+ * @param pendingIntent a {@link PendingIntent} to be sent for the location update
*
* @throws IllegalArgumentException if intent is null
* @throws SecurityException if no suitable permission is present
@@ -930,9 +896,38 @@
@TestApi
@RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
public void requestLocationUpdates(
- @NonNull LocationRequest request, @NonNull PendingIntent intent) {
- checkPendingIntent(intent);
- requestLocationUpdates(request, null, null, intent);
+ @NonNull LocationRequest locationRequest,
+ @NonNull PendingIntent pendingIntent) {
+ Preconditions.checkArgument(locationRequest != null, "invalid null location request");
+ Preconditions.checkArgument(pendingIntent != null, "invalid null pending intent");
+ if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN) {
+ Preconditions.checkArgument(pendingIntent.isTargetedToPackage(),
+ "pending intent must be targeted to package");
+ }
+
+ try {
+ mService.requestLocationUpdates(locationRequest, null, pendingIntent,
+ mContext.getPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private void requestLocationUpdates(LocationRequest request,
+ LocationListenerTransport transport) {
+ synchronized (mListeners) {
+ LocationListenerTransport oldTransport = mListeners.put(transport.getKey(), transport);
+ if (oldTransport != null) {
+ oldTransport.unregisterListener();
+ }
+
+ try {
+ mService.requestLocationUpdates(request, transport, null,
+ mContext.getPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
/**
@@ -963,74 +958,43 @@
}
}
- private ListenerTransport wrapListener(LocationListener listener, Looper looper) {
- if (listener == null) return null;
- synchronized (mListeners) {
- ListenerTransport transport = mListeners.get(listener);
- if (transport == null) {
- transport = new ListenerTransport(listener, looper);
- }
- mListeners.put(listener, transport);
- return transport;
- }
- }
-
- @UnsupportedAppUsage
- private void requestLocationUpdates(LocationRequest request, LocationListener listener,
- Looper looper, PendingIntent intent) {
-
- String packageName = mContext.getPackageName();
-
- // wrap the listener class
- ListenerTransport transport = wrapListener(listener, looper);
-
- try {
- mService.requestLocationUpdates(request, transport, intent, packageName);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
/**
- * Removes all location updates for the specified LocationListener.
+ * Removes location updates for the specified LocationListener. Following this call, updates
+ * will no longer occur for this listener.
*
- * <p>Following this call, updates will no longer
- * occur for this listener.
- *
- * @param listener listener object that no longer needs location updates
+ * @param listener listener that no longer needs location updates
* @throws IllegalArgumentException if listener is null
*/
public void removeUpdates(@NonNull LocationListener listener) {
- checkListener(listener);
- String packageName = mContext.getPackageName();
+ Preconditions.checkArgument(listener != null, "invalid null listener");
- ListenerTransport transport;
synchronized (mListeners) {
- transport = mListeners.remove(listener);
- }
- if (transport == null) return;
+ LocationListenerTransport transport = mListeners.remove(listener);
+ if (transport == null) {
+ return;
+ }
+ transport.unregisterListener();
- try {
- mService.removeUpdates(transport, null, packageName);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ try {
+ mService.removeUpdates(transport, null, mContext.getPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
/**
- * Removes all location updates for the specified pending intent.
+ * Removes all location updates for the specified pending intent. Following this call, updates
+ * will no longer occur for this pending intent.
*
- * <p>Following this call, updates will no longer for this pending intent.
- *
- * @param intent pending intent object that no longer needs location updates
- * @throws IllegalArgumentException if intent is null
+ * @param pendingIntent pending intent that no longer needs location updates
+ * @throws IllegalArgumentException if pendingIntent is null
*/
- public void removeUpdates(@NonNull PendingIntent intent) {
- checkPendingIntent(intent);
- String packageName = mContext.getPackageName();
+ public void removeUpdates(@NonNull PendingIntent pendingIntent) {
+ Preconditions.checkArgument(pendingIntent != null, "invalid null pending intent");
try {
- mService.removeUpdates(null, intent, packageName);
+ mService.removeUpdates(null, pendingIntent, mContext.getPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1574,199 +1538,36 @@
}
}
- // --- GPS-specific support ---
-
- // This class is used to send Gnss status events to the client's specific thread.
- private class GnssStatusListenerTransport extends IGnssStatusListener.Stub {
-
- private final GnssStatus.Callback mGnssCallback;
- private final OnNmeaMessageListener mGnssNmeaListener;
-
- private class GnssHandler extends Handler {
- GnssHandler(Handler handler) {
- super(handler != null ? handler.getLooper() : Looper.myLooper());
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case NMEA_RECEIVED:
- synchronized (mNmeaBuffer) {
- for (Nmea nmea : mNmeaBuffer) {
- mGnssNmeaListener.onNmeaMessage(nmea.mNmea, nmea.mTimestamp);
- }
- mNmeaBuffer.clear();
- }
- break;
- case GNSS_EVENT_STARTED:
- mGnssCallback.onStarted();
- break;
- case GNSS_EVENT_STOPPED:
- mGnssCallback.onStopped();
- break;
- case GNSS_EVENT_FIRST_FIX:
- mGnssCallback.onFirstFix(mTimeToFirstFix);
- break;
- case GNSS_EVENT_SATELLITE_STATUS:
- mGnssCallback.onSatelliteStatusChanged(mGnssStatus);
- break;
- default:
- break;
- }
- }
- }
-
- private final Handler mGnssHandler;
-
- private static final int NMEA_RECEIVED = 1;
- private static final int GNSS_EVENT_STARTED = 2;
- private static final int GNSS_EVENT_STOPPED = 3;
- private static final int GNSS_EVENT_FIRST_FIX = 4;
- private static final int GNSS_EVENT_SATELLITE_STATUS = 5;
-
- private class Nmea {
- long mTimestamp;
- String mNmea;
-
- Nmea(long timestamp, String nmea) {
- mTimestamp = timestamp;
- mNmea = nmea;
- }
- }
- private final ArrayList<Nmea> mNmeaBuffer;
-
- GnssStatusListenerTransport(GnssStatus.Callback callback, Handler handler) {
- mGnssCallback = callback;
- mGnssHandler = new GnssHandler(handler);
- mGnssNmeaListener = null;
- mNmeaBuffer = null;
- }
-
- GnssStatusListenerTransport(OnNmeaMessageListener listener, Handler handler) {
- mGnssCallback = null;
- mGnssHandler = new GnssHandler(handler);
- mGnssNmeaListener = listener;
- mNmeaBuffer = new ArrayList<>();
- }
-
- GnssStatusListenerTransport(GpsStatus.Listener listener, Handler handler) {
- mGnssHandler = new GnssHandler(handler);
- mNmeaBuffer = null;
- mGnssCallback = listener != null ? new GnssStatus.Callback() {
- @Override
- public void onStarted() {
- listener.onGpsStatusChanged(GpsStatus.GPS_EVENT_STARTED);
- }
-
- @Override
- public void onStopped() {
- listener.onGpsStatusChanged(GpsStatus.GPS_EVENT_STOPPED);
- }
-
- @Override
- public void onFirstFix(int ttff) {
- listener.onGpsStatusChanged(GpsStatus.GPS_EVENT_FIRST_FIX);
- }
-
- @Override
- public void onSatelliteStatusChanged(GnssStatus status) {
- listener.onGpsStatusChanged(GpsStatus.GPS_EVENT_SATELLITE_STATUS);
- }
- } : null;
- mGnssNmeaListener = null;
- }
-
- @Override
- public void onGnssStarted() {
- if (mGnssCallback != null) {
- mGnssHandler.obtainMessage(GNSS_EVENT_STARTED).sendToTarget();
- }
- }
-
- @Override
- public void onGnssStopped() {
- if (mGnssCallback != null) {
- mGnssHandler.obtainMessage(GNSS_EVENT_STOPPED).sendToTarget();
- }
- }
-
- @Override
- public void onFirstFix(int ttff) {
- if (mGnssCallback != null) {
- mTimeToFirstFix = ttff;
- mGnssHandler.obtainMessage(GNSS_EVENT_FIRST_FIX).sendToTarget();
- }
- }
-
- @Override
- public void onSvStatusChanged(int svCount, int[] prnWithFlags,
- float[] cn0s, float[] elevations, float[] azimuths, float[] carrierFreqs) {
- if (mGnssCallback != null) {
- mGnssStatus = new GnssStatus(svCount, prnWithFlags, cn0s, elevations, azimuths,
- carrierFreqs);
-
- mGnssHandler.removeMessages(GNSS_EVENT_SATELLITE_STATUS);
- mGnssHandler.obtainMessage(GNSS_EVENT_SATELLITE_STATUS).sendToTarget();
- }
- }
-
- @Override
- public void onNmeaReceived(long timestamp, String nmea) {
- if (mGnssNmeaListener != null) {
- synchronized (mNmeaBuffer) {
- mNmeaBuffer.add(new Nmea(timestamp, nmea));
- }
-
- mGnssHandler.removeMessages(NMEA_RECEIVED);
- mGnssHandler.obtainMessage(NMEA_RECEIVED).sendToTarget();
- }
- }
- }
-
/**
* Adds a GPS status listener.
*
* @param listener GPS status listener object to register
- *
* @return true if the listener was successfully added
- *
* @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
- * @deprecated use {@link #registerGnssStatusCallback(GnssStatus.Callback)} instead.
+ * @deprecated use {@link #registerGnssStatusCallback(GnssStatus.Callback)} instead. No longer
+ * supported in apps targeting R and above.
*/
@Deprecated
@RequiresPermission(ACCESS_FINE_LOCATION)
public boolean addGpsStatusListener(GpsStatus.Listener listener) {
- boolean result;
-
- if (mGpsStatusListeners.get(listener) != null) {
- return true;
- }
try {
- GnssStatusListenerTransport transport = new GnssStatusListenerTransport(listener, null);
- result = mService.registerGnssStatusCallback(transport, mContext.getPackageName());
- if (result) {
- mGpsStatusListeners.put(listener, transport);
- }
+ return mGnssStatusListenerManager.addListener(listener, new Handler());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
-
- return result;
}
/**
* Removes a GPS status listener.
*
* @param listener GPS status listener object to remove
- * @deprecated use {@link #unregisterGnssStatusCallback(GnssStatus.Callback)} instead.
+ * @deprecated use {@link #unregisterGnssStatusCallback(GnssStatus.Callback)} instead. No longer
+ * supported in apps targeting R and above.
*/
@Deprecated
public void removeGpsStatusListener(GpsStatus.Listener listener) {
try {
- GnssStatusListenerTransport transport = mGpsStatusListeners.remove(listener);
- if (transport != null) {
- mService.unregisterGnssStatusCallback(transport);
- }
+ mGnssStatusListenerManager.removeListener(listener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1776,11 +1577,12 @@
* Registers a GNSS status callback.
*
* @param callback GNSS status callback object to register
- *
* @return true if the listener was successfully added
- *
* @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
+ * @deprecated Use {@link #registerGnssStatusCallback(GnssStatus.Callback, Handler)} or {@link
+ * #registerGnssStatusCallback(Executor, GnssStatus.Callback)} instead.
*/
+ @Deprecated
@RequiresPermission(ACCESS_FINE_LOCATION)
public boolean registerGnssStatusCallback(@NonNull GnssStatus.Callback callback) {
return registerGnssStatusCallback(callback, null);
@@ -1790,33 +1592,41 @@
* Registers a GNSS status callback.
*
* @param callback GNSS status callback object to register
- * @param handler the handler that the callback runs on.
- *
+ * @param handler a handler with a looper that the callback runs on.
* @return true if the listener was successfully added
- *
* @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
*/
@RequiresPermission(ACCESS_FINE_LOCATION)
public boolean registerGnssStatusCallback(
@NonNull GnssStatus.Callback callback, @Nullable Handler handler) {
- boolean result;
- synchronized (mGnssStatusListeners) {
- if (mGnssStatusListeners.get(callback) != null) {
- return true;
- }
- try {
- GnssStatusListenerTransport transport =
- new GnssStatusListenerTransport(callback, handler);
- result = mService.registerGnssStatusCallback(transport, mContext.getPackageName());
- if (result) {
- mGnssStatusListeners.put(callback, transport);
- }
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ if (handler == null) {
+ handler = new Handler();
}
- return result;
+ try {
+ return mGnssStatusListenerManager.addListener(callback, handler);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers a GNSS status callback.
+ *
+ * @param callback GNSS status callback object to register
+ * @param executor the executor that the callback runs on.
+ * @return true if the listener was successfully added
+ * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
+ */
+ @RequiresPermission(ACCESS_FINE_LOCATION)
+ public boolean registerGnssStatusCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull GnssStatus.Callback callback) {
+ try {
+ return mGnssStatusListenerManager.addListener(callback, executor);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -1825,15 +1635,10 @@
* @param callback GNSS status callback object to remove
*/
public void unregisterGnssStatusCallback(@NonNull GnssStatus.Callback callback) {
- synchronized (mGnssStatusListeners) {
- try {
- GnssStatusListenerTransport transport = mGnssStatusListeners.remove(callback);
- if (transport != null) {
- mService.unregisterGnssStatusCallback(transport);
- }
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ try {
+ mGnssStatusListenerManager.removeListener(callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
@@ -1868,11 +1673,12 @@
* Adds an NMEA listener.
*
* @param listener a {@link OnNmeaMessageListener} object to register
- *
* @return true if the listener was successfully added
- *
* @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
+ * @deprecated Use {@link #addNmeaListener(OnNmeaMessageListener, Handler)} or {@link
+ * #addNmeaListener(Executor, OnNmeaMessageListener)} instead.
*/
+ @Deprecated
@RequiresPermission(ACCESS_FINE_LOCATION)
public boolean addNmeaListener(@NonNull OnNmeaMessageListener listener) {
return addNmeaListener(listener, null);
@@ -1882,33 +1688,40 @@
* Adds an NMEA listener.
*
* @param listener a {@link OnNmeaMessageListener} object to register
- * @param handler the handler that the listener runs on.
- *
+ * @param handler a handler with the looper that the listener runs on.
* @return true if the listener was successfully added
- *
* @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
*/
@RequiresPermission(ACCESS_FINE_LOCATION)
public boolean addNmeaListener(
@NonNull OnNmeaMessageListener listener, @Nullable Handler handler) {
- boolean result;
-
- if (mGnssNmeaListeners.get(listener) != null) {
- // listener is already registered
- return true;
+ if (handler == null) {
+ handler = new Handler();
}
try {
- GnssStatusListenerTransport transport =
- new GnssStatusListenerTransport(listener, handler);
- result = mService.registerGnssStatusCallback(transport, mContext.getPackageName());
- if (result) {
- mGnssNmeaListeners.put(listener, transport);
- }
+ return mGnssStatusListenerManager.addListener(listener, handler);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
+ }
- return result;
+ /**
+ * Adds an NMEA listener.
+ *
+ * @param listener a {@link OnNmeaMessageListener} object to register
+ * @param executor the {@link Executor} that the listener runs on.
+ * @return true if the listener was successfully added
+ * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
+ */
+ @RequiresPermission(ACCESS_FINE_LOCATION)
+ public boolean addNmeaListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnNmeaMessageListener listener) {
+ try {
+ return mGnssStatusListenerManager.addListener(listener, executor);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -1918,10 +1731,7 @@
*/
public void removeNmeaListener(@NonNull OnNmeaMessageListener listener) {
try {
- GnssStatusListenerTransport transport = mGnssNmeaListeners.remove(listener);
- if (transport != null) {
- mService.unregisterGnssStatusCallback(transport);
- }
+ mGnssStatusListenerManager.removeListener(listener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1942,11 +1752,29 @@
}
/**
+ * No-op method to keep backward-compatibility. Don't use it. Use {@link
+ * #unregisterGnssMeasurementsCallback} instead.
+ *
+ * @hide
+ * @deprecated use {@link #unregisterGnssMeasurementsCallback(GnssMeasurementsEvent.Callback)}
+ * instead.
+ * @removed
+ */
+ @Deprecated
+ @SystemApi
+ @SuppressLint("Doclava125")
+ public void removeGpsMeasurementListener(GpsMeasurementsEvent.Listener listener) {}
+
+ /**
* Registers a GPS Measurement callback.
*
* @param callback a {@link GnssMeasurementsEvent.Callback} object to register.
* @return {@code true} if the callback was added successfully, {@code false} otherwise.
+ * @deprecated Use {@link
+ * #registerGnssMeasurementsCallback(GnssMeasurementsEvent.Callback, Handler)} or {@link
+ * #registerGnssMeasurementsCallback(Executor, GnssMeasurementsEvent.Callback)} instead.
*/
+ @Deprecated
@RequiresPermission(ACCESS_FINE_LOCATION)
public boolean registerGnssMeasurementsCallback(
@NonNull GnssMeasurementsEvent.Callback callback) {
@@ -1957,13 +1785,38 @@
* Registers a GPS Measurement callback.
*
* @param callback a {@link GnssMeasurementsEvent.Callback} object to register.
- * @param handler the handler that the callback runs on.
+ * @param handler the handler that the callback runs on.
* @return {@code true} if the callback was added successfully, {@code false} otherwise.
*/
@RequiresPermission(ACCESS_FINE_LOCATION)
public boolean registerGnssMeasurementsCallback(
@NonNull GnssMeasurementsEvent.Callback callback, @Nullable Handler handler) {
- return mGnssMeasurementCallbackTransport.add(callback, handler);
+ if (handler == null) {
+ handler = new Handler();
+ }
+ try {
+ return mGnssMeasurementsListenerManager.addListener(callback, handler);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers a GPS Measurement callback.
+ *
+ * @param callback a {@link GnssMeasurementsEvent.Callback} object to register.
+ * @param executor the executor that the callback runs on.
+ * @return {@code true} if the callback was added successfully, {@code false} otherwise.
+ */
+ @RequiresPermission(ACCESS_FINE_LOCATION)
+ public boolean registerGnssMeasurementsCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull GnssMeasurementsEvent.Callback callback) {
+ try {
+ return mGnssMeasurementsListenerManager.addListener(callback, executor);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -1977,9 +1830,24 @@
@RequiresPermission(ACCESS_FINE_LOCATION)
public void injectGnssMeasurementCorrections(
@NonNull GnssMeasurementCorrections measurementCorrections) {
+ Preconditions.checkArgument(measurementCorrections != null);
try {
- mGnssMeasurementCallbackTransport.injectGnssMeasurementCorrections(
- measurementCorrections);
+ mService.injectGnssMeasurementCorrections(
+ measurementCorrections, mContext.getPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregisters a GPS Measurement callback.
+ *
+ * @param callback a {@link GnssMeasurementsEvent.Callback} object to remove.
+ */
+ public void unregisterGnssMeasurementsCallback(
+ @NonNull GnssMeasurementsEvent.Callback callback) {
+ try {
+ mGnssMeasurementsListenerManager.removeListener(callback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1996,7 +1864,7 @@
@RequiresPermission(ACCESS_FINE_LOCATION)
public @NonNull GnssCapabilities getGnssCapabilities() {
try {
- long gnssCapabilities = mGnssMeasurementCallbackTransport.getGnssCapabilities();
+ long gnssCapabilities = mService.getGnssCapabilities(mContext.getPackageName());
if (gnssCapabilities == GnssCapabilities.INVALID_CAPABILITIES) {
gnssCapabilities = 0L;
}
@@ -2007,30 +1875,6 @@
}
/**
- * No-op method to keep backward-compatibility. Don't use it. Use {@link
- * #unregisterGnssMeasurementsCallback} instead.
- *
- * @hide
- * @deprecated use {@link #unregisterGnssMeasurementsCallback(GnssMeasurementsEvent.Callback)}
- * instead.
- * @removed
- */
- @Deprecated
- @SystemApi
- @SuppressLint("Doclava125")
- public void removeGpsMeasurementListener(GpsMeasurementsEvent.Listener listener) {}
-
- /**
- * Unregisters a GPS Measurement callback.
- *
- * @param callback a {@link GnssMeasurementsEvent.Callback} object to remove.
- */
- public void unregisterGnssMeasurementsCallback(
- @NonNull GnssMeasurementsEvent.Callback callback) {
- mGnssMeasurementCallbackTransport.remove(callback);
- }
-
- /**
* No-op method to keep backward-compatibility.
* Don't use it. Use {@link #registerGnssNavigationMessageCallback} instead.
* @hide
@@ -2063,7 +1907,11 @@
*
* @param callback a {@link GnssNavigationMessage.Callback} object to register.
* @return {@code true} if the callback was added successfully, {@code false} otherwise.
+ * @deprecated Use {@link
+ * #registerGnssNavigationMessageCallback(GnssNavigationMessage.Callback, Handler)} or {@link
+ * #registerGnssNavigationMessageCallback(Executor, GnssNavigationMessage.Callback)} instead.
*/
+ @Deprecated
public boolean registerGnssNavigationMessageCallback(
@NonNull GnssNavigationMessage.Callback callback) {
return registerGnssNavigationMessageCallback(callback, null);
@@ -2073,13 +1921,39 @@
* Registers a GNSS Navigation Message callback.
*
* @param callback a {@link GnssNavigationMessage.Callback} object to register.
- * @param handler the handler that the callback runs on.
+ * @param handler the handler that the callback runs on.
* @return {@code true} if the callback was added successfully, {@code false} otherwise.
*/
@RequiresPermission(ACCESS_FINE_LOCATION)
public boolean registerGnssNavigationMessageCallback(
@NonNull GnssNavigationMessage.Callback callback, @Nullable Handler handler) {
- return mGnssNavigationMessageCallbackTransport.add(callback, handler);
+ if (handler == null) {
+ handler = new Handler();
+ }
+
+ try {
+ return mGnssNavigationMessageListenerTransport.addListener(callback, handler);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers a GNSS Navigation Message callback.
+ *
+ * @param callback a {@link GnssNavigationMessage.Callback} object to register.
+ * @param executor the looper that the callback runs on.
+ * @return {@code true} if the callback was added successfully, {@code false} otherwise.
+ */
+ @RequiresPermission(ACCESS_FINE_LOCATION)
+ public boolean registerGnssNavigationMessageCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull GnssNavigationMessage.Callback callback) {
+ try {
+ return mGnssNavigationMessageListenerTransport.addListener(callback, executor);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -2089,7 +1963,11 @@
*/
public void unregisterGnssNavigationMessageCallback(
@NonNull GnssNavigationMessage.Callback callback) {
- mGnssNavigationMessageCallbackTransport.remove(callback);
+ try {
+ mGnssNavigationMessageListenerTransport.removeListener(callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -2111,8 +1989,10 @@
}
// When mGnssStatus is null, that means that this method is called outside
// onGpsStatusChanged(). Return an empty status to maintain backwards compatibility.
- if (mGnssStatus != null) {
- status.setStatus(mGnssStatus, mTimeToFirstFix);
+ GnssStatus gnssStatus = mGnssStatusListenerManager.getGnssStatus();
+ int ttff = mGnssStatusListenerManager.getTtff();
+ if (gnssStatus != null) {
+ status.setStatus(gnssStatus, ttff);
}
return status;
}
@@ -2192,12 +2072,20 @@
@RequiresPermission(Manifest.permission.LOCATION_HARDWARE)
public boolean registerGnssBatchedLocationCallback(long periodNanos, boolean wakeOnFifoFull,
@NonNull BatchedLocationCallback callback, @Nullable Handler handler) {
- mBatchedLocationCallbackTransport.add(callback, handler);
+ if (handler == null) {
+ handler = new Handler();
+ }
- try {
- return mService.startGnssBatch(periodNanos, wakeOnFifoFull, mContext.getPackageName());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ synchronized (mBatchedLocationCallbackManager) {
+ try {
+ if (mBatchedLocationCallbackManager.addListener(callback, handler)) {
+ return mService.startGnssBatch(periodNanos, wakeOnFifoFull,
+ mContext.getPackageName());
+ }
+ return false;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
@@ -2231,13 +2119,14 @@
@RequiresPermission(Manifest.permission.LOCATION_HARDWARE)
public boolean unregisterGnssBatchedLocationCallback(
@NonNull BatchedLocationCallback callback) {
-
- mBatchedLocationCallbackTransport.remove(callback);
-
- try {
- return mService.stopGnssBatch();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ synchronized (mBatchedLocationCallbackManager) {
+ try {
+ mBatchedLocationCallbackManager.removeListener(callback);
+ mService.stopGnssBatch();
+ return true;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
@@ -2429,4 +2318,391 @@
}
}
+ private class LocationListenerTransport extends ILocationListener.Stub {
+
+ private final Executor mExecutor;
+ @Nullable private volatile LocationListener mListener;
+
+ private LocationListenerTransport(@NonNull Handler handler,
+ @NonNull LocationListener listener) {
+ Preconditions.checkArgument(handler != null, "invalid null handler");
+ Preconditions.checkArgument(listener != null, "invalid null listener");
+
+ mExecutor = new HandlerExecutor(handler);
+ mListener = listener;
+ }
+
+ private LocationListenerTransport(@NonNull Executor executor,
+ @NonNull LocationListener listener) {
+ Preconditions.checkArgument(executor != null, "invalid null executor");
+ Preconditions.checkArgument(listener != null, "invalid null listener");
+
+ mExecutor = executor;
+ mListener = listener;
+ }
+
+ private LocationListener getKey() {
+ return mListener;
+ }
+
+ private void unregisterListener() {
+ mListener = null;
+ }
+
+ @Override
+ public void onLocationChanged(Location location) {
+ try {
+ mExecutor.execute(() -> {
+ try {
+ LocationListener listener = mListener;
+ if (listener == null) {
+ return;
+ }
+
+ // we may be under the binder identity if a direct executor is used
+ long identity = Binder.clearCallingIdentity();
+ try {
+ listener.onLocationChanged(location);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ } finally {
+ locationCallbackFinished();
+ }
+ });
+ } catch (RejectedExecutionException e) {
+ locationCallbackFinished();
+ throw e;
+ }
+ }
+
+ @Override
+ public void onStatusChanged(String provider, int status, Bundle extras) {
+ try {
+ mExecutor.execute(() -> {
+ try {
+ LocationListener listener = mListener;
+ if (listener == null) {
+ return;
+ }
+
+ // we may be under the binder identity if a direct executor is used
+ long identity = Binder.clearCallingIdentity();
+ try {
+ listener.onStatusChanged(provider, status, extras);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ } finally {
+ locationCallbackFinished();
+ }
+ });
+ } catch (RejectedExecutionException e) {
+ locationCallbackFinished();
+ throw e;
+ }
+ }
+
+ @Override
+ public void onProviderEnabled(String provider) {
+ try {
+ mExecutor.execute(() -> {
+ try {
+ LocationListener listener = mListener;
+ if (listener == null) {
+ return;
+ }
+
+ // we may be under the binder identity if a direct executor is used
+ long identity = Binder.clearCallingIdentity();
+ try {
+ listener.onProviderEnabled(provider);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ } finally {
+ locationCallbackFinished();
+ }
+ });
+ } catch (RejectedExecutionException e) {
+ locationCallbackFinished();
+ throw e;
+ }
+ }
+
+ @Override
+ public void onProviderDisabled(String provider) {
+ try {
+ mExecutor.execute(() -> {
+ try {
+ LocationListener listener = mListener;
+ if (listener == null) {
+ return;
+ }
+
+ // we may be under the binder identity if a direct executor is used
+ long identity = Binder.clearCallingIdentity();
+ try {
+ listener.onProviderDisabled(provider);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ } finally {
+ locationCallbackFinished();
+ }
+ });
+ } catch (RejectedExecutionException e) {
+ locationCallbackFinished();
+ throw e;
+ }
+ }
+
+ 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;
+
+ private NmeaAdapter(OnNmeaMessageListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void onNmeaMessage(String message, long timestamp) {
+ mListener.onNmeaMessage(message, timestamp);
+ }
+ }
+
+ private class GnssStatusListenerManager extends
+ AbstractListenerManager<GnssStatus.Callback> {
+
+ @Nullable
+ private IGnssStatusListener mListenerTransport;
+
+ @Nullable
+ private volatile GnssStatus mGnssStatus;
+ private volatile int mTtff;
+
+ public GnssStatus getGnssStatus() {
+ return mGnssStatus;
+ }
+
+ public int getTtff() {
+ return mTtff;
+ }
+
+ public boolean addListener(@NonNull GpsStatus.Listener listener, @NonNull Handler handler)
+ throws RemoteException {
+ return addInternal(listener, handler);
+ }
+
+ public boolean addListener(@NonNull OnNmeaMessageListener listener,
+ @NonNull Handler handler)
+ throws RemoteException {
+ return addInternal(listener, handler);
+ }
+
+ public boolean addListener(@NonNull OnNmeaMessageListener listener,
+ @NonNull Executor executor)
+ throws RemoteException {
+ return addInternal(listener, executor);
+ }
+
+ @Override
+ protected GnssStatus.Callback convertKey(Object listener) {
+ if (listener instanceof GnssStatus.Callback) {
+ return (GnssStatus.Callback) listener;
+ } else if (listener instanceof GpsStatus.Listener) {
+ return new GnssStatus.Callback() {
+ private final GpsStatus.Listener mGpsListener = (GpsStatus.Listener) listener;
+
+ @Override
+ public void onStarted() {
+ mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_STARTED);
+ }
+
+ @Override
+ public void onStopped() {
+ mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_STOPPED);
+ }
+
+ @Override
+ public void onFirstFix(int ttffMillis) {
+ mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_FIRST_FIX);
+ }
+
+ @Override
+ public void onSatelliteStatusChanged(GnssStatus status) {
+ mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_SATELLITE_STATUS);
+ }
+ };
+ } else if (listener instanceof OnNmeaMessageListener) {
+ return new NmeaAdapter((OnNmeaMessageListener) listener);
+ } else {
+ throw new IllegalStateException();
+ }
+ }
+
+ @Override
+ protected boolean registerService() throws RemoteException {
+ Preconditions.checkState(mListenerTransport == null);
+
+ mListenerTransport = new GnssStatusListener();
+ return mService.registerGnssStatusCallback(mListenerTransport,
+ mContext.getPackageName());
+ }
+
+ @Override
+ protected void unregisterService() throws RemoteException {
+ Preconditions.checkState(mListenerTransport != null);
+
+ mService.unregisterGnssStatusCallback(mListenerTransport);
+ mListenerTransport = null;
+ }
+
+ private class GnssStatusListener extends IGnssStatusListener.Stub {
+ @Override
+ public void onGnssStarted() {
+ execute(GnssStatus.Callback::onStarted);
+ }
+
+ @Override
+ public void onGnssStopped() {
+ execute(GnssStatus.Callback::onStopped);
+ }
+
+ @Override
+ public void onFirstFix(int ttff) {
+ mTtff = ttff;
+ execute((callback) -> callback.onFirstFix(ttff));
+ }
+
+ @Override
+ public void onSvStatusChanged(int svCount, int[] svidWithFlags, float[] cn0s,
+ float[] elevations, float[] azimuths, float[] carrierFreqs) {
+ GnssStatus localStatus = new GnssStatus(svCount, svidWithFlags, cn0s, elevations,
+ azimuths, carrierFreqs);
+ mGnssStatus = localStatus;
+ execute((callback) -> callback.onSatelliteStatusChanged(localStatus));
+ }
+
+ @Override
+ public void onNmeaReceived(long timestamp, String nmea) {
+ execute((callback) -> {
+ if (callback instanceof NmeaAdapter) {
+ ((NmeaAdapter) callback).onNmeaMessage(nmea, timestamp);
+ }
+ });
+ }
+ }
+ }
+
+ private class GnssMeasurementsListenerManager extends
+ AbstractListenerManager<GnssMeasurementsEvent.Callback> {
+
+ @Nullable
+ private IGnssMeasurementsListener mListenerTransport;
+
+ @Override
+ protected boolean registerService() throws RemoteException {
+ Preconditions.checkState(mListenerTransport == null);
+
+ mListenerTransport = new GnssMeasurementsListener();
+ return mService.addGnssMeasurementsListener(mListenerTransport,
+ mContext.getPackageName());
+ }
+
+ @Override
+ protected void unregisterService() throws RemoteException {
+ Preconditions.checkState(mListenerTransport != null);
+
+ mService.removeGnssMeasurementsListener(mListenerTransport);
+ mListenerTransport = null;
+ }
+
+ private class GnssMeasurementsListener extends IGnssMeasurementsListener.Stub {
+ @Override
+ public void onGnssMeasurementsReceived(final GnssMeasurementsEvent event) {
+ execute((callback) -> callback.onGnssMeasurementsReceived(event));
+ }
+
+ @Override
+ public void onStatusChanged(int status) {
+ execute((callback) -> callback.onStatusChanged(status));
+ }
+ }
+ }
+
+ private class GnssNavigationMessageListenerManager extends
+ AbstractListenerManager<GnssNavigationMessage.Callback> {
+
+ @Nullable
+ private IGnssNavigationMessageListener mListenerTransport;
+
+ @Override
+ protected boolean registerService() throws RemoteException {
+ Preconditions.checkState(mListenerTransport == null);
+
+ mListenerTransport = new GnssNavigationMessageListener();
+ return mService.addGnssNavigationMessageListener(mListenerTransport,
+ mContext.getPackageName());
+ }
+
+ @Override
+ protected void unregisterService() throws RemoteException {
+ Preconditions.checkState(mListenerTransport != null);
+
+ mService.removeGnssNavigationMessageListener(mListenerTransport);
+ mListenerTransport = null;
+ }
+
+ private class GnssNavigationMessageListener extends IGnssNavigationMessageListener.Stub {
+ @Override
+ public void onGnssNavigationMessageReceived(GnssNavigationMessage event) {
+ execute((listener) -> listener.onGnssNavigationMessageReceived(event));
+ }
+
+ @Override
+ public void onStatusChanged(int status) {
+ execute((listener) -> listener.onStatusChanged(status));
+ }
+ }
+ }
+
+ private class BatchedLocationCallbackManager extends
+ AbstractListenerManager<BatchedLocationCallback> {
+
+ @Nullable
+ private IBatchedLocationCallback mListenerTransport;
+
+ @Override
+ protected boolean registerService() throws RemoteException {
+ Preconditions.checkState(mListenerTransport == null);
+
+ mListenerTransport = new BatchedLocationCallback();
+ return mService.addGnssBatchingCallback(mListenerTransport, mContext.getPackageName());
+ }
+
+ @Override
+ protected void unregisterService() throws RemoteException {
+ Preconditions.checkState(mListenerTransport != null);
+
+ mService.removeGnssBatchingCallback();
+ mListenerTransport = null;
+ }
+
+ private class BatchedLocationCallback extends IBatchedLocationCallback.Stub {
+ @Override
+ public void onLocationBatch(List<Location> locations) {
+ execute((listener) -> listener.onLocationBatch(locations));
+ }
+ }
+ }
}