diff options
-rw-r--r-- | api/current.txt | 14 | ||||
-rw-r--r-- | api/system-current.txt | 1 | ||||
-rw-r--r-- | api/test-current.txt | 1 | ||||
-rw-r--r-- | config/hiddenapi-greylist.txt | 1 | ||||
-rw-r--r-- | location/java/android/location/AbstractListenerManager.java | 139 | ||||
-rw-r--r-- | location/java/android/location/BatchedLocationCallbackTransport.java | 66 | ||||
-rw-r--r-- | location/java/android/location/GnssMeasurementCallbackTransport.java | 97 | ||||
-rw-r--r-- | location/java/android/location/GnssNavigationMessageCallbackTransport.java | 79 | ||||
-rw-r--r-- | location/java/android/location/LocalListenerHelper.java | 134 | ||||
-rw-r--r-- | location/java/android/location/LocationManager.java | 1250 |
10 files changed, 914 insertions, 868 deletions
diff --git a/api/current.txt b/api/current.txt index b4d110e13261..ad0a5e5ed1f0 100644 --- a/api/current.txt +++ b/api/current.txt @@ -23089,8 +23089,9 @@ package android.location { 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 @@ package android.location { 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 @@ package android.location { 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 283433e519ba..18c1eceda71b 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -3430,6 +3430,7 @@ package android.location { 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 34312f69116a..70f84a1d7365 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1085,6 +1085,7 @@ package android.location { 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 a6e1f0ab0cde..a16399c25363 100644 --- a/config/hiddenapi-greylist.txt +++ b/config/hiddenapi-greylist.txt @@ -218,7 +218,6 @@ Landroid/location/ILocationManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V 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 000000000000..c41023e31065 --- /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 e00f855e9302..000000000000 --- 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 8cb8c0b78da1..000000000000 --- 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 1eafd02e52be..000000000000 --- 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 592d01d2fed6..000000000000 --- 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 5be4770440dc..7cf5bb80b654 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -22,6 +22,7 @@ import static android.Manifest.permission.LOCATION_HARDWARE; 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.TestApi; 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.provider.Settings; 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 class LocationManager { 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 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; - - private LocationListener mListener; - private final Handler mListenerHandler; - - ListenerTransport(LocationListener listener, Looper looper) { - mListener = listener; - - 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); - } + private final Context mContext; - @Override - public void onProviderDisabled(String provider) { - Message msg = Message.obtain(); - msg.what = TYPE_PROVIDER_DISABLED; - msg.obj = provider; - sendCallbackMessage(msg); - } + @UnsupportedAppUsage + private final ILocationManager mService; - private void sendCallbackMessage(Message msg) { - if (!mListenerHandler.sendMessage(msg)) { - locationCallbackFinished(); - } - } + @GuardedBy("mListeners") + private final ArrayMap<LocationListener, LocationListenerTransport> mListeners = + new ArrayMap<>(); - 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(); - } + @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(); - 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 @@ public class LocationManager { } } - /** - * @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 @@ public class LocationManager { LocationRequest request = LocationRequest.createFromDeprecatedProvider( provider, minTime, minDistance, false); - requestLocationUpdates(request, listener, null, null); + requestLocationUpdates(request, listener, null); } /** @@ -556,7 +443,36 @@ public class LocationManager { 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 @@ public class LocationManager { 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 @@ public class LocationManager { LocationRequest request = LocationRequest.createFromDeprecatedProvider( provider, minTime, minDistance, false); - requestLocationUpdates(request, null, null, intent); + requestLocationUpdates(request, intent); } /** @@ -724,7 +666,7 @@ public class LocationManager { LocationRequest request = LocationRequest.createFromDeprecatedCriteria( criteria, minTime, minDistance, false); - requestLocationUpdates(request, null, null, intent); + requestLocationUpdates(request, intent); } /** @@ -754,7 +696,7 @@ public class LocationManager { LocationRequest request = LocationRequest.createFromDeprecatedProvider( provider, 0, 0, true); - requestLocationUpdates(request, listener, looper, null); + requestLocationUpdates(request, listener, looper); } /** @@ -787,7 +729,7 @@ public class LocationManager { LocationRequest request = LocationRequest.createFromDeprecatedCriteria( criteria, 0, 0, true); - requestLocationUpdates(request, listener, looper, null); + requestLocationUpdates(request, listener, looper); } /** @@ -810,7 +752,7 @@ public class LocationManager { LocationRequest request = LocationRequest.createFromDeprecatedProvider( provider, 0, 0, true); - requestLocationUpdates(request, null, null, intent); + requestLocationUpdates(request, intent); } /** @@ -834,7 +776,7 @@ public class LocationManager { LocationRequest request = LocationRequest.createFromDeprecatedCriteria( criteria, 0, 0, true); - requestLocationUpdates(request, null, null, intent); + requestLocationUpdates(request, intent); } /** @@ -881,7 +823,7 @@ public class LocationManager { * * <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 @@ public class LocationManager { @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 @@ public class LocationManager { * <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 @@ public class LocationManager { @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 @@ public class LocationManager { } } - 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. - * - * <p>Following this call, updates will no longer - * occur for this listener. + * Removes location updates for the specified LocationListener. 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 @@ public class LocationManager { } } - // --- 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 @@ public class LocationManager { * 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 @@ public class LocationManager { * 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 @@ public class LocationManager { * @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 @@ public class LocationManager { * 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 @@ public class LocationManager { * 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 class LocationManager { */ 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 @@ public class LocationManager { } /** + * 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 @@ public class LocationManager { * 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 @@ public class LocationManager { @RequiresPermission(ACCESS_FINE_LOCATION) public void injectGnssMeasurementCorrections( @NonNull GnssMeasurementCorrections measurementCorrections) { + Preconditions.checkArgument(measurementCorrections != null); + try { + 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 { - mGnssMeasurementCallbackTransport.injectGnssMeasurementCorrections( - measurementCorrections); + mGnssMeasurementsListenerManager.removeListener(callback); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1996,7 +1864,7 @@ public class LocationManager { @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 @@ public class LocationManager { } /** - * 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 @@ public class LocationManager { * * @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 @@ public class LocationManager { * 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 class LocationManager { */ public void unregisterGnssNavigationMessageCallback( @NonNull GnssNavigationMessage.Callback callback) { - mGnssNavigationMessageCallbackTransport.remove(callback); + try { + mGnssNavigationMessageListenerTransport.removeListener(callback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -2111,8 +1989,10 @@ public class LocationManager { } // 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 @@ public class LocationManager { @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 @@ public class LocationManager { @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 @@ public class LocationManager { } } + 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)); + } + } + } } |