diff --git a/core/java/com/android/internal/listeners/ListenerTransport.java b/core/java/com/android/internal/listeners/ListenerTransport.java index 9d6210e5dcc83..1a6870c138f48 100644 --- a/core/java/com/android/internal/listeners/ListenerTransport.java +++ b/core/java/com/android/internal/listeners/ListenerTransport.java @@ -16,54 +16,43 @@ package com.android.internal.listeners; - -import android.annotation.NonNull; import android.annotation.Nullable; -import com.android.internal.util.Preconditions; - import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Consumer; /** - * A listener registration object which holds data associated with a listener, such the executor - * the listener should run on. + * A listener transport object which can run listener operations on an executor. * * @param listener type */ -public class ListenerTransport { - - private final Executor mExecutor; - - private volatile @Nullable TListener mListener; - - protected ListenerTransport(@NonNull Executor executor, @NonNull TListener listener) { - Preconditions.checkArgument(executor != null, "invalid null executor"); - Preconditions.checkArgument(listener != null, "invalid null listener/callback"); - mExecutor = executor; - mListener = listener; - } +public interface ListenerTransport { /** - * Prevents any listener invocations that happen-after this call. + * Should return a valid listener until {@link #unregister()} is invoked, and must return + * null after that. Recommended (but not required) that this is implemented via a volatile + * variable. */ - public final void unregister() { - mListener = null; - } + @Nullable TListener getListener(); + + /** + * Must be implemented so that {@link #getListener()} returns null after this is invoked. + */ + void unregister(); /** * Executes the given operation for the listener. */ - public final void execute(@NonNull Consumer operation) { + default void execute(Executor executor, Consumer operation) { Objects.requireNonNull(operation); - if (mListener == null) { + if (getListener() == null) { return; } - mExecutor.execute(() -> { - TListener listener = mListener; + executor.execute(() -> { + TListener listener = getListener(); if (listener == null) { return; } @@ -71,15 +60,4 @@ public class ListenerTransport { operation.accept(listener); }); } - - @Override - public final boolean equals(Object obj) { - // intentionally bound to reference equality so removal works as expected - return this == obj; - } - - @Override - public final int hashCode() { - return super.hashCode(); - } } diff --git a/core/java/com/android/internal/listeners/ListenerTransportManager.java b/core/java/com/android/internal/listeners/ListenerTransportManager.java new file mode 100644 index 0000000000000..0d5d1b7b53ffc --- /dev/null +++ b/core/java/com/android/internal/listeners/ListenerTransportManager.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.listeners; + +import android.os.RemoteException; + +import com.android.internal.annotations.GuardedBy; + +import java.lang.ref.WeakReference; +import java.util.Map; +import java.util.WeakHashMap; + +/** + * A listener transport manager which handles mappings between the client facing listener and system + * server facing transport. Supports transports which may be removed either from the client side or + * from the system server side without leaking memory. + * + * @param > transport type + */ +public abstract class ListenerTransportManager> { + + @GuardedBy("mRegistrations") + private final Map> mRegistrations; + + protected ListenerTransportManager() { + // using weakhashmap means that the transport may be GCed if the server drops its reference, + // and thus the listener may be GCed as well if the client drops that reference. if the + // server will never drop a reference without warning (ie, transport removal may only be + // initiated from the client side), then arraymap or similar may be used without fear of + // memory leaks. + mRegistrations = new WeakHashMap<>(); + } + + /** + * Adds a new transport with the given listener key. + */ + public final void addListener(Object key, TTransport transport) { + try { + synchronized (mRegistrations) { + // ordering of operations is important so that if an error occurs at any point we + // are left in a reasonable state + registerTransport(transport); + WeakReference oldTransportRef = mRegistrations.put(key, + new WeakReference<>(transport)); + if (oldTransportRef != null) { + TTransport oldTransport = oldTransportRef.get(); + if (oldTransport != null) { + oldTransport.unregister(); + unregisterTransport(oldTransport); + } + } + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Removes the transport with the given listener key. + */ + public final void removeListener(Object key) { + try { + synchronized (mRegistrations) { + // ordering of operations is important so that if an error occurs at any point we + // are left in a reasonable state + WeakReference transportRef = mRegistrations.remove(key); + if (transportRef != null) { + TTransport transport = transportRef.get(); + if (transport != null) { + transport.unregister(); + unregisterTransport(transport); + } + } + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + protected abstract void registerTransport(TTransport transport) throws RemoteException; + + protected abstract void unregisterTransport(TTransport transport) throws RemoteException; +} diff --git a/core/java/com/android/internal/listeners/ListenerTransportMultiplexer.java b/core/java/com/android/internal/listeners/ListenerTransportMultiplexer.java deleted file mode 100644 index fc1d69f570ad6..0000000000000 --- a/core/java/com/android/internal/listeners/ListenerTransportMultiplexer.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.listeners; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.Build; -import android.os.RemoteException; -import android.util.ArrayMap; -import android.util.IndentingPrintWriter; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.Preconditions; - -import java.io.FileDescriptor; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Objects; -import java.util.concurrent.Executor; -import java.util.function.Consumer; - -/** - * A listener multiplexer designed for use by client-side code. This class ensures that listeners - * are never invoked while a lock is held. This class is only useful for multiplexing listeners - - * if all client listeners can be combined into a single server request, and all server results will - * be delivered to all clients. - * - * By default, the multiplexer will replace requests on the server simply by registering the new - * request and trusting the server to know this is replacing the old request. If the server needs to - * have the old request unregistered first, subclasses should override - * {@link #reregisterWithServer(Object, Object)}. - * - * @param listener request type, may be Void - * @param listener type - */ -public abstract class ListenerTransportMultiplexer { - - private final Object mLock = new Object(); - - @GuardedBy("mLock") - private ArrayMap> mRegistrations = - new ArrayMap<>(); - - @GuardedBy("mLock") - private boolean mServiceRegistered = false; - - @GuardedBy("mLock") - private TRequest mCurrentRequest; - - /** - * Should be implemented to register the given merged request with the server. - * - * @see #reregisterWithServer(Object, Object) - */ - protected abstract void registerWithServer(TRequest mergedRequest) throws RemoteException; - - /** - * Invoked when the server already has a request registered, and it is being replaced with a new - * request. The default implementation simply registers the new request, trusting the server to - * overwrite the old request. - */ - protected void reregisterWithServer(TRequest oldMergedRequest, TRequest mergedRequest) - throws RemoteException { - registerWithServer(mergedRequest); - } - - /** - * Should be implemented to unregister from the server. - */ - protected abstract void unregisterWithServer() throws RemoteException; - - /** - * Called in order to generate a merged request from the given requests. The list of requests - * will never be empty. - */ - protected @Nullable TRequest mergeRequests(Collection requests) { - if (Build.IS_DEBUGGABLE) { - for (TRequest request : requests) { - // if using non-null requests then implementations must override this method - Preconditions.checkState(request == null); - } - } - - return null; - } - - /** - * Adds a new listener with no request, using the listener as the key. - */ - public void addListener(@NonNull TListener listener, @NonNull Executor executor) { - addListener(listener, null, listener, executor); - } - - /** - * Adds a new listener with the given request, using the listener as the key. - */ - public void addListener(@Nullable TRequest request, @NonNull TListener listener, - @NonNull Executor executor) { - addListener(listener, request, listener, executor); - } - - /** - * Adds a new listener with the given request using a custom key. - */ - public void addListener(@NonNull Object key, @Nullable TRequest request, - @NonNull TListener listener, @NonNull Executor executor) { - Objects.requireNonNull(key); - RequestListenerTransport registration = - new RequestListenerTransport<>(request, executor, listener); - - synchronized (mLock) { - ArrayMap> newRegistrations = - new ArrayMap<>(mRegistrations.size() + 1); - newRegistrations.putAll(mRegistrations); - RequestListenerTransport old = newRegistrations.put(key, - registration); - mRegistrations = newRegistrations; - - if (old != null) { - old.unregister(); - } - - updateService(); - } - } - - /** - * Removes the listener with the given key. - */ - public void removeListener(@NonNull Object key) { - Objects.requireNonNull(key); - - synchronized (mLock) { - if (!mRegistrations.containsKey(key)) { - return; - } - - ArrayMap> newRegistrations = - new ArrayMap<>(mRegistrations); - RequestListenerTransport old = newRegistrations.remove(key); - mRegistrations = newRegistrations; - - if (old != null) { - old.unregister(); - updateService(); - } - } - } - - private void updateService() { - synchronized (mLock) { - if (mRegistrations.isEmpty()) { - mCurrentRequest = null; - if (mServiceRegistered) { - try { - mServiceRegistered = false; - unregisterWithServer(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - return; - } - - ArrayList requests = new ArrayList<>(mRegistrations.size()); - for (int i = 0; i < mRegistrations.size(); i++) { - requests.add(mRegistrations.valueAt(i).getRequest()); - } - - TRequest merged = mergeRequests(requests); - if (!mServiceRegistered || !Objects.equals(merged, mCurrentRequest)) { - TRequest old = mCurrentRequest; - mCurrentRequest = null; - try { - if (mServiceRegistered) { - // if a remote exception is thrown the service should not be registered - mServiceRegistered = false; - reregisterWithServer(old, merged); - } else { - registerWithServer(merged); - } - mCurrentRequest = merged; - mServiceRegistered = true; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - } - } - - protected final void deliverToListeners(Consumer operation) { - ArrayMap> registrations; - synchronized (mLock) { - registrations = mRegistrations; - } - - try { - for (int i = 0; i < registrations.size(); i++) { - registrations.valueAt(i).execute(operation); - } - } finally { - onOperationFinished(operation); - } - } - - /** - * Invoked when an operation is finished. This method will always be called once for every call - * to {@link #deliverToListeners(Consumer)}, regardless of whether the operation encountered any - * error or failed to execute in any way for any listeners. - */ - protected void onOperationFinished(@NonNull Consumer operation) {} - - /** - * Dumps debug information. - */ - public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) { - ArrayMap> registrations; - synchronized (mLock) { - registrations = mRegistrations; - - ipw.print("service: "); - if (mServiceRegistered) { - if (mCurrentRequest == null) { - ipw.print("request registered"); - } else { - ipw.print("request registered - " + mCurrentRequest); - } - } else { - ipw.print("unregistered"); - } - ipw.println(); - } - - if (!registrations.isEmpty()) { - ipw.println("listeners:"); - - ipw.increaseIndent(); - for (int i = 0; i < registrations.size(); i++) { - ipw.print(registrations.valueAt(i)); - } - ipw.decreaseIndent(); - } - } -} diff --git a/core/java/com/android/internal/listeners/RequestListenerTransport.java b/core/java/com/android/internal/listeners/RequestListenerTransport.java deleted file mode 100644 index 178de0647fd18..0000000000000 --- a/core/java/com/android/internal/listeners/RequestListenerTransport.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.listeners; - -import android.annotation.Nullable; - -import java.util.concurrent.Executor; - -/** - * A listener transport with an associated request. - * - * @param request type - * @param listener type - */ -public class RequestListenerTransport extends ListenerTransport { - - private final @Nullable TRequest mRequest; - - protected RequestListenerTransport(@Nullable TRequest request, Executor executor, - TListener listener) { - super(executor, listener); - mRequest = request; - } - - /** - * Returns the request associated with this transport. - */ - public final @Nullable TRequest getRequest() { - return mRequest; - } -} diff --git a/core/tests/coretests/src/com/android/internal/listeners/ListenerTransportMultiplexerTest.java b/core/tests/coretests/src/com/android/internal/listeners/ListenerTransportMultiplexerTest.java deleted file mode 100644 index 127ecfb9dc391..0000000000000 --- a/core/tests/coretests/src/com/android/internal/listeners/ListenerTransportMultiplexerTest.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.listeners; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import android.os.Handler; -import android.os.Looper; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import junit.framework.TestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.Collection; -import java.util.Comparator; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicReference; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class ListenerTransportMultiplexerTest extends TestCase { - - TestMultiplexer mMultiplexer; - - @Before - public void setUp() { - mMultiplexer = new TestMultiplexer(); - } - - @Test - public void testAdd() { - Runnable runnable = mock(Runnable.class); - - mMultiplexer.addListener(0, runnable, Runnable::run); - assertThat(mMultiplexer.mRegistered).isTrue(); - assertThat(mMultiplexer.mMergedRequest).isEqualTo(0); - - mMultiplexer.notifyListeners(); - verify(runnable, times(1)).run(); - } - - @Test - public void testAdd_Multiple() { - Runnable runnable1 = mock(Runnable.class); - Runnable runnable2 = mock(Runnable.class); - - mMultiplexer.addListener(0, runnable1, Runnable::run); - mMultiplexer.addListener(0, runnable2, Runnable::run); - - mMultiplexer.notifyListeners(); - verify(runnable1).run(); - verify(runnable2).run(); - } - - @Test - public void testRemove() { - Runnable runnable = mock(Runnable.class); - - mMultiplexer.addListener(0, runnable, Runnable::run); - mMultiplexer.removeListener(runnable); - assertThat(mMultiplexer.mRegistered).isFalse(); - - mMultiplexer.notifyListeners(); - verify(runnable, never()).run(); - } - - @Test - public void testRemove_Multiple() { - Runnable runnable1 = mock(Runnable.class); - Runnable runnable2 = mock(Runnable.class); - - mMultiplexer.addListener(0, runnable1, Runnable::run); - mMultiplexer.addListener(1, runnable2, Runnable::run); - mMultiplexer.removeListener(runnable1); - - mMultiplexer.notifyListeners(); - verify(runnable1, never()).run(); - verify(runnable2).run(); - } - - @Test - public void testMergeMultiple() { - Runnable runnable1 = mock(Runnable.class); - Runnable runnable2 = mock(Runnable.class); - Runnable runnable3 = mock(Runnable.class); - - mMultiplexer.addListener(0, runnable1, Runnable::run); - mMultiplexer.addListener(1, runnable2, Runnable::run); - assertThat(mMultiplexer.mMergedRequest).isEqualTo(1); - - mMultiplexer.notifyListeners(); - verify(runnable1, times(1)).run(); - verify(runnable2, times(1)).run(); - verify(runnable3, times(0)).run(); - - mMultiplexer.addListener(0, runnable3, Runnable::run); - assertThat(mMultiplexer.mMergedRequest).isEqualTo(1); - - mMultiplexer.notifyListeners(); - verify(runnable1, times(2)).run(); - verify(runnable2, times(2)).run(); - verify(runnable3, times(1)).run(); - - mMultiplexer.removeListener(runnable2); - assertThat(mMultiplexer.mMergedRequest).isEqualTo(0); - - mMultiplexer.notifyListeners(); - verify(runnable1, times(3)).run(); - verify(runnable2, times(2)).run(); - verify(runnable3, times(2)).run(); - - mMultiplexer.removeListener(runnable1); - mMultiplexer.removeListener(runnable3); - mMultiplexer.notifyListeners(); - verify(runnable1, times(3)).run(); - verify(runnable2, times(2)).run(); - verify(runnable3, times(2)).run(); - } - - @Test(timeout = 5000) - public void testReentrancy() { - AtomicReference runnable = new AtomicReference<>(); - runnable.set(() -> mMultiplexer.removeListener(runnable.get())); - - mMultiplexer.addListener(0, runnable.get(), command -> { - CountDownLatch latch = new CountDownLatch(1); - new Handler(Looper.getMainLooper()).post(() -> { - command.run(); - latch.countDown(); - }); - try { - latch.await(); - } catch (InterruptedException e) { - throw new AssertionError(e); - } - }); - - mMultiplexer.notifyListeners(); - assertThat(mMultiplexer.mRegistered).isFalse(); - } - - private static class TestMultiplexer extends ListenerTransportMultiplexer { - - boolean mRegistered; - int mMergedRequest; - - TestMultiplexer() { - } - - public void notifyListeners() { - deliverToListeners(Runnable::run); - } - - @Override - protected void registerWithServer(Integer mergedRequest) { - mRegistered = true; - mMergedRequest = mergedRequest; - } - - @Override - protected void unregisterWithServer() { - mRegistered = false; - } - - @Override - protected Integer mergeRequests(Collection requests) { - return requests.stream().max(Comparator.naturalOrder()).get(); - } - } -} diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index db9003e48a180..bcfd307f3baea 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.ACCESS_COARSE_LOCATION; import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static android.Manifest.permission.LOCATION_HARDWARE; import static android.Manifest.permission.WRITE_SECURE_SETTINGS; +import static android.location.GpsStatus.GPS_EVENT_STARTED; import static android.location.LocationRequest.createFromDeprecatedCriteria; import static android.location.LocationRequest.createFromDeprecatedProvider; @@ -43,7 +44,6 @@ import android.app.PropertyInvalidatedCache; import android.compat.Compatibility; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; -import android.compat.annotation.UnsupportedAppUsage; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -60,16 +60,17 @@ import android.os.IRemoteCallback; import android.os.Looper; import android.os.Process; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.UserHandle; import com.android.internal.annotations.GuardedBy; import com.android.internal.listeners.ListenerExecutor; -import com.android.internal.listeners.ListenerTransportMultiplexer; +import com.android.internal.listeners.ListenerTransport; +import com.android.internal.listeners.ListenerTransportManager; import com.android.internal.util.Preconditions; import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -398,20 +399,40 @@ public class LocationManager { private static final long MAX_SINGLE_LOCATION_TIMEOUT_MS = 30 * 1000; + private static final String CACHE_KEY_LOCATION_ENABLED_PROPERTY = + "cache_key.location_enabled"; + + private static ILocationManager getService() throws RemoteException { + try { + return ILocationManager.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.LOCATION_SERVICE)); + } catch (ServiceManager.ServiceNotFoundException e) { + throw new RemoteException(e); + } + } + @GuardedBy("sLocationListeners") private static final WeakHashMap> sLocationListeners = new WeakHashMap<>(); - final Context mContext; + // allows lazy instantiation since most processes do not use GNSS APIs + private static class GnssLazyLoader { + static final GnssStatusTransportManager sGnssStatusListeners = + new GnssStatusTransportManager(); + static final GnssNmeaTransportManager sGnssNmeaListeners = + new GnssNmeaTransportManager(); + static final GnssMeasurementsTransportManager sGnssMeasurementsListeners = + new GnssMeasurementsTransportManager(); + static final GnssAntennaTransportManager sGnssAntennaInfoListeners = + new GnssAntennaTransportManager(); + static final GnssNavigationTransportManager sGnssNavigationListeners = + new GnssNavigationTransportManager(); + } - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "{@link " - + "LocationManager}") - final ILocationManager mService; + private final Context mContext; + private final ILocationManager mService; - private final Object mLock = new Object(); - - @GuardedBy("mLock") - private PropertyInvalidatedCache mLocationEnabledCache = + private volatile PropertyInvalidatedCache mLocationEnabledCache = new PropertyInvalidatedCache( 4, CACHE_KEY_LOCATION_ENABLED_PROPERTY) { @@ -425,68 +446,12 @@ public class LocationManager { } }; - @GuardedBy("mLock") - @Nullable private GnssStatusTransportMultiplexer mGnssStatusTransportMultiplexer; - @GuardedBy("mLock") - @Nullable private GnssNmeaTransportMultiplexer mGnssNmeaTransportMultiplexer; - @GuardedBy("mLock") - @Nullable private GnssMeasurementsTransportMultiplexer mGnssMeasurementsTransportMultiplexer; - @GuardedBy("mLock") - @Nullable private GnssNavigationTransportMultiplexer mGnssNavigationTransportMultiplexer; - @GuardedBy("mLock") - @Nullable private GnssAntennaInfoTransportMultiplexer mGnssAntennaInfoTransportMultiplexer; - /** * @hide */ public LocationManager(@NonNull Context context, @NonNull ILocationManager service) { - mService = service; - mContext = context; - } - - private GnssStatusTransportMultiplexer getGnssStatusTransportMultiplexer() { - synchronized (mLock) { - if (mGnssStatusTransportMultiplexer == null) { - mGnssStatusTransportMultiplexer = new GnssStatusTransportMultiplexer(); - } - return mGnssStatusTransportMultiplexer; - } - } - - private GnssNmeaTransportMultiplexer getGnssNmeaTransportMultiplexer() { - synchronized (mLock) { - if (mGnssNmeaTransportMultiplexer == null) { - mGnssNmeaTransportMultiplexer = new GnssNmeaTransportMultiplexer(); - } - return mGnssNmeaTransportMultiplexer; - } - } - - private GnssMeasurementsTransportMultiplexer getGnssMeasurementsTransportMultiplexer() { - synchronized (mLock) { - if (mGnssMeasurementsTransportMultiplexer == null) { - mGnssMeasurementsTransportMultiplexer = new GnssMeasurementsTransportMultiplexer(); - } - return mGnssMeasurementsTransportMultiplexer; - } - } - - private GnssNavigationTransportMultiplexer getGnssNavigationTransportMultiplexer() { - synchronized (mLock) { - if (mGnssNavigationTransportMultiplexer == null) { - mGnssNavigationTransportMultiplexer = new GnssNavigationTransportMultiplexer(); - } - return mGnssNavigationTransportMultiplexer; - } - } - - private GnssAntennaInfoTransportMultiplexer getGnssAntennaInfoTransportMultiplexer() { - synchronized (mLock) { - if (mGnssAntennaInfoTransportMultiplexer == null) { - mGnssAntennaInfoTransportMultiplexer = new GnssAntennaInfoTransportMultiplexer(); - } - return mGnssAntennaInfoTransportMultiplexer; - } + mContext = Objects.requireNonNull(context); + mService = Objects.requireNonNull(service); } /** @@ -627,10 +592,9 @@ public class LocationManager { */ @SystemApi public boolean isLocationEnabledForUser(@NonNull UserHandle userHandle) { - synchronized (mLock) { - if (mLocationEnabledCache != null) { - return mLocationEnabledCache.query(userHandle.getIdentifier()); - } + PropertyInvalidatedCache cache = mLocationEnabledCache; + if (cache != null) { + return cache.query(userHandle.getIdentifier()); } // fallback if cache is disabled @@ -2265,9 +2229,8 @@ public class LocationManager { "GpsStatus APIs not supported, please use GnssStatus APIs instead"); } - GnssStatusTransportMultiplexer multiplexer = getGnssStatusTransportMultiplexer(); - GnssStatus gnssStatus = multiplexer.getGnssStatus(); - int ttff = multiplexer.getTtff(); + GnssStatus gnssStatus = GpsStatusTransport.sGnssStatus; + int ttff = GpsStatusTransport.sTtff; if (gnssStatus != null) { if (status == null) { status = GpsStatus.create(gnssStatus, ttff); @@ -2300,8 +2263,8 @@ public class LocationManager { "GpsStatus APIs not supported, please use GnssStatus APIs instead"); } - getGnssStatusTransportMultiplexer().addListener(listener, - new HandlerExecutor(new Handler())); + GnssLazyLoader.sGnssStatusListeners.addListener(listener, + new GpsStatusTransport(new HandlerExecutor(new Handler()), mContext, listener)); return true; } @@ -2320,7 +2283,7 @@ public class LocationManager { "GpsStatus APIs not supported, please use GnssStatus APIs instead"); } - getGnssStatusTransportMultiplexer().removeListener(listener); + GnssLazyLoader.sGnssStatusListeners.removeListener(listener); } /** @@ -2383,7 +2346,8 @@ public class LocationManager { public boolean registerGnssStatusCallback( @NonNull @CallbackExecutor Executor executor, @NonNull GnssStatus.Callback callback) { - getGnssStatusTransportMultiplexer().addListener(callback, executor); + GnssLazyLoader.sGnssStatusListeners.addListener(callback, + new GnssStatusTransport(executor, mContext, callback)); return true; } @@ -2393,7 +2357,7 @@ public class LocationManager { * @param callback GNSS status callback object to remove */ public void unregisterGnssStatusCallback(@NonNull GnssStatus.Callback callback) { - getGnssStatusTransportMultiplexer().removeListener(callback); + GnssLazyLoader.sGnssStatusListeners.removeListener(callback); } /** @@ -2473,7 +2437,8 @@ public class LocationManager { public boolean addNmeaListener( @NonNull @CallbackExecutor Executor executor, @NonNull OnNmeaMessageListener listener) { - getGnssNmeaTransportMultiplexer().addListener(listener, executor); + GnssLazyLoader.sGnssNmeaListeners.addListener(listener, + new GnssNmeaTransport(executor, mContext, listener)); return true; } @@ -2483,7 +2448,7 @@ public class LocationManager { * @param listener a {@link OnNmeaMessageListener} object to remove */ public void removeNmeaListener(@NonNull OnNmeaMessageListener listener) { - getGnssNmeaTransportMultiplexer().removeListener(listener); + GnssLazyLoader.sGnssNmeaListeners.removeListener(listener); } /** @@ -2599,10 +2564,8 @@ public class LocationManager { @NonNull GnssRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull GnssMeasurementsEvent.Callback callback) { - Preconditions.checkArgument(request != null, "invalid null request"); - getGnssMeasurementsTransportMultiplexer().addListener(request.toGnssMeasurementRequest(), - callback, executor); - return true; + return registerGnssMeasurementsCallback(request.toGnssMeasurementRequest(), executor, + callback); } /** @@ -2624,8 +2587,8 @@ public class LocationManager { @NonNull GnssMeasurementRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull GnssMeasurementsEvent.Callback callback) { - Preconditions.checkArgument(request != null, "invalid null request"); - getGnssMeasurementsTransportMultiplexer().addListener(request, callback, executor); + GnssLazyLoader.sGnssMeasurementsListeners.addListener(callback, + new GnssMeasurementsTransport(executor, mContext, request, callback)); return true; } @@ -2657,7 +2620,7 @@ public class LocationManager { */ public void unregisterGnssMeasurementsCallback( @NonNull GnssMeasurementsEvent.Callback callback) { - getGnssMeasurementsTransportMultiplexer().removeListener(callback); + GnssLazyLoader.sGnssMeasurementsListeners.removeListener(callback); } /** @@ -2682,7 +2645,8 @@ public class LocationManager { public boolean registerAntennaInfoListener( @NonNull @CallbackExecutor Executor executor, @NonNull GnssAntennaInfo.Listener listener) { - getGnssAntennaInfoTransportMultiplexer().addListener(listener, executor); + GnssLazyLoader.sGnssAntennaInfoListeners.addListener(listener, + new GnssAntennaInfoTransport(executor, mContext, listener)); return true; } @@ -2695,7 +2659,7 @@ public class LocationManager { */ @Deprecated public void unregisterAntennaInfoListener(@NonNull GnssAntennaInfo.Listener listener) { - getGnssAntennaInfoTransportMultiplexer().removeListener(listener); + GnssLazyLoader.sGnssAntennaInfoListeners.removeListener(listener); } /** @@ -2785,7 +2749,8 @@ public class LocationManager { public boolean registerGnssNavigationMessageCallback( @NonNull @CallbackExecutor Executor executor, @NonNull GnssNavigationMessage.Callback callback) { - getGnssNavigationTransportMultiplexer().addListener(callback, executor); + GnssLazyLoader.sGnssNavigationListeners.addListener(callback, + new GnssNavigationTransport(executor, mContext, callback)); return true; } @@ -2796,7 +2761,7 @@ public class LocationManager { */ public void unregisterGnssNavigationMessageCallback( @NonNull GnssNavigationMessage.Callback callback) { - getGnssNavigationTransportMultiplexer().removeListener(callback); + GnssLazyLoader.sGnssNavigationListeners.removeListener(callback); } /** @@ -2905,6 +2870,89 @@ public class LocationManager { } } + private static class GnssStatusTransportManager extends + ListenerTransportManager { + + @Override + protected void registerTransport(GnssStatusTransport transport) + throws RemoteException { + getService().registerGnssStatusCallback(transport, transport.getPackage(), + transport.getAttributionTag()); + } + + @Override + protected void unregisterTransport(GnssStatusTransport transport) + throws RemoteException { + getService().unregisterGnssStatusCallback(transport); + } + } + + private static class GnssNmeaTransportManager extends + ListenerTransportManager { + + @Override + protected void registerTransport(GnssNmeaTransport transport) + throws RemoteException { + getService().registerGnssNmeaCallback(transport, transport.getPackage(), + transport.getAttributionTag()); + } + + @Override + protected void unregisterTransport(GnssNmeaTransport transport) + throws RemoteException { + getService().unregisterGnssNmeaCallback(transport); + } + } + + private static class GnssMeasurementsTransportManager extends + ListenerTransportManager { + + @Override + protected void registerTransport(GnssMeasurementsTransport transport) + throws RemoteException { + getService().addGnssMeasurementsListener(transport.getRequest(), transport, + transport.getPackage(), transport.getAttributionTag()); + } + + @Override + protected void unregisterTransport(GnssMeasurementsTransport transport) + throws RemoteException { + getService().removeGnssMeasurementsListener(transport); + } + } + + private static class GnssAntennaTransportManager extends + ListenerTransportManager { + + @Override + protected void registerTransport(GnssAntennaInfoTransport transport) { + transport.getContext().registerReceiver(transport, + new IntentFilter(ACTION_GNSS_ANTENNA_INFOS_CHANGED)); + } + + @Override + protected void unregisterTransport(GnssAntennaInfoTransport transport) { + transport.getContext().unregisterReceiver(transport); + } + } + + private static class GnssNavigationTransportManager extends + ListenerTransportManager { + + @Override + protected void registerTransport(GnssNavigationTransport transport) + throws RemoteException { + getService().addGnssNavigationMessageListener(transport, + transport.getPackage(), transport.getAttributionTag()); + } + + @Override + protected void unregisterTransport(GnssNavigationTransport transport) + throws RemoteException { + getService().removeGnssNavigationMessageListener(transport); + } + } + private static class GetCurrentLocationTransport extends ILocationCallback.Stub implements ListenerExecutor, CancellationSignal.OnCancelListener { @@ -3019,7 +3067,7 @@ public class LocationManager { @Override public void onStarted() { - mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_STARTED); + mGpsListener.onGpsStatusChanged(GPS_EVENT_STARTED); } @Override @@ -3038,273 +3086,269 @@ public class LocationManager { } } - private class GnssStatusTransportMultiplexer extends - ListenerTransportMultiplexer { + private static class GnssStatusTransport extends IGnssStatusListener.Stub implements + ListenerTransport { - private @Nullable IGnssStatusListener mListenerTransport; + private final Executor mExecutor; + private final String mPackageName; + private final String mAttributionTag; - volatile @Nullable GnssStatus mGnssStatus; - volatile int mTtff; + private volatile @Nullable GnssStatus.Callback mListener; - GnssStatusTransportMultiplexer() {} - - public GnssStatus getGnssStatus() { - return mGnssStatus; + GnssStatusTransport(Executor executor, Context context, GnssStatus.Callback listener) { + Preconditions.checkArgument(executor != null, "invalid null executor"); + Preconditions.checkArgument(listener != null, "invalid null callback"); + mExecutor = executor; + mPackageName = context.getPackageName(); + mAttributionTag = context.getAttributionTag(); + mListener = listener; } - public int getTtff() { - return mTtff; + public String getPackage() { + return mPackageName; } - public void addListener(@NonNull GpsStatus.Listener listener, @NonNull Executor executor) { - addListener(listener, null, new GpsAdapter(listener), executor); + public String getAttributionTag() { + return mAttributionTag; } @Override - protected void registerWithServer(Void ignored) throws RemoteException { - IGnssStatusListener transport = mListenerTransport; - if (transport == null) { - transport = new GnssStatusListener(); - } - - // if a remote exception is thrown the transport should not be set - mListenerTransport = null; - mService.registerGnssStatusCallback(transport, mContext.getPackageName(), - mContext.getAttributionTag()); - mListenerTransport = transport; + public void unregister() { + mListener = null; } @Override - protected void unregisterWithServer() throws RemoteException { - if (mListenerTransport != null) { - IGnssStatusListener transport = mListenerTransport; - mListenerTransport = null; - mService.unregisterGnssStatusCallback(transport); - } + public @Nullable GnssStatus.Callback getListener() { + return mListener; } - private class GnssStatusListener extends IGnssStatusListener.Stub { + @Override + public void onGnssStarted() { + execute(mExecutor, GnssStatus.Callback::onStarted); + } - GnssStatusListener() {} + @Override + public void onGnssStopped() { + execute(mExecutor, GnssStatus.Callback::onStopped); + } - @Override - public void onGnssStarted() { - deliverToListeners(GnssStatus.Callback::onStarted); - } + @Override + public void onFirstFix(int ttff) { + execute(mExecutor, listener -> listener.onFirstFix(ttff)); - @Override - public void onGnssStopped() { - deliverToListeners(GnssStatus.Callback::onStopped); - } + } - @Override - public void onFirstFix(int ttff) { - mTtff = ttff; - deliverToListeners(callback -> callback.onFirstFix(ttff)); - } + @Override + public void onSvStatusChanged(GnssStatus gnssStatus) { + execute(mExecutor, listener -> listener.onSatelliteStatusChanged(gnssStatus)); + } + } - @Override - public void onSvStatusChanged(GnssStatus gnssStatus) { - mGnssStatus = gnssStatus; - deliverToListeners(callback -> callback.onSatelliteStatusChanged(gnssStatus)); + private static class GpsStatusTransport extends GnssStatusTransport { + + static volatile int sTtff; + static volatile GnssStatus sGnssStatus; + + GpsStatusTransport(Executor executor, Context context, GpsStatus.Listener listener) { + super(executor, context, new GpsAdapter(listener)); + } + + @Override + public void onFirstFix(int ttff) { + sTtff = ttff; + super.onFirstFix(ttff); + } + + @Override + public void onSvStatusChanged(GnssStatus gnssStatus) { + sGnssStatus = gnssStatus; + super.onSvStatusChanged(gnssStatus); + } + } + + private static class GnssNmeaTransport extends IGnssNmeaListener.Stub implements + ListenerTransport { + + private final Executor mExecutor; + private final String mPackageName; + private final String mAttributionTag; + + private volatile @Nullable OnNmeaMessageListener mListener; + + GnssNmeaTransport(Executor executor, Context context, OnNmeaMessageListener listener) { + Preconditions.checkArgument(executor != null, "invalid null executor"); + Preconditions.checkArgument(listener != null, "invalid null listener"); + mExecutor = executor; + mPackageName = context.getPackageName(); + mAttributionTag = context.getAttributionTag(); + mListener = listener; + } + + public String getPackage() { + return mPackageName; + } + + public String getAttributionTag() { + return mAttributionTag; + } + + @Override + public void unregister() { + mListener = null; + } + + @Override + public @Nullable OnNmeaMessageListener getListener() { + return mListener; + } + + @Override + public void onNmeaReceived(long timestamp, String nmea) { + execute(mExecutor, callback -> callback.onNmeaMessage(nmea, timestamp)); + } + } + + private static class GnssMeasurementsTransport extends IGnssMeasurementsListener.Stub implements + ListenerTransport { + + private final Executor mExecutor; + private final String mPackageName; + private final String mAttributionTag; + private final GnssMeasurementRequest mRequest; + + private volatile @Nullable GnssMeasurementsEvent.Callback mListener; + + GnssMeasurementsTransport(Executor executor, Context context, + GnssMeasurementRequest request, GnssMeasurementsEvent.Callback listener) { + Preconditions.checkArgument(executor != null, "invalid null executor"); + Preconditions.checkArgument(listener != null, "invalid null callback"); + Preconditions.checkArgument(request != null, "invalid null request"); + mExecutor = executor; + mPackageName = context.getPackageName(); + mAttributionTag = context.getAttributionTag(); + mRequest = request; + mListener = listener; + } + + public String getPackage() { + return mPackageName; + } + + public String getAttributionTag() { + return mAttributionTag; + } + + public GnssMeasurementRequest getRequest() { + return mRequest; + } + + @Override + public void unregister() { + mListener = null; + } + + @Override + public @Nullable GnssMeasurementsEvent.Callback getListener() { + return mListener; + } + + @Override + public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) { + execute(mExecutor, callback -> callback.onGnssMeasurementsReceived(event)); + } + + @Override + public void onStatusChanged(int status) { + execute(mExecutor, callback -> callback.onStatusChanged(status)); + } + } + + private static class GnssAntennaInfoTransport extends BroadcastReceiver implements + ListenerTransport { + + private final Executor mExecutor; + private final Context mContext; + + private volatile @Nullable GnssAntennaInfo.Listener mListener; + + GnssAntennaInfoTransport(Executor executor, Context context, + GnssAntennaInfo.Listener listener) { + Preconditions.checkArgument(executor != null, "invalid null executor"); + Preconditions.checkArgument(listener != null, "invalid null listener"); + mExecutor = executor; + mContext = context; + mListener = listener; + } + + public Context getContext() { + return mContext; + } + + @Override + public void unregister() { + mListener = null; + } + + @Override + public @Nullable GnssAntennaInfo.Listener getListener() { + return mListener; + } + + @Override + public void onReceive(Context context, Intent intent) { + ArrayList infos = intent.getParcelableArrayListExtra( + EXTRA_GNSS_ANTENNA_INFOS); + if (infos != null) { + execute(mExecutor, callback -> callback.onGnssAntennaInfoReceived(infos)); } } } - private class GnssNmeaTransportMultiplexer extends - ListenerTransportMultiplexer { + private static class GnssNavigationTransport extends IGnssNavigationMessageListener.Stub + implements ListenerTransport { - private @Nullable IGnssNmeaListener mListenerTransport; + private final Executor mExecutor; + private final String mPackageName; + private final String mAttributionTag; - GnssNmeaTransportMultiplexer() {} + private volatile @Nullable GnssNavigationMessage.Callback mListener; - public void addListener(@NonNull OnNmeaMessageListener listener, - @NonNull Executor executor) { - addListener(listener, null, listener, executor); + GnssNavigationTransport(Executor executor, Context context, + GnssNavigationMessage.Callback listener) { + Preconditions.checkArgument(executor != null, "invalid null executor"); + Preconditions.checkArgument(listener != null, "invalid null callback"); + mExecutor = executor; + mPackageName = context.getPackageName(); + mAttributionTag = context.getAttributionTag(); + mListener = listener; + } + + public String getPackage() { + return mPackageName; + } + + public String getAttributionTag() { + return mAttributionTag; } @Override - protected void registerWithServer(Void ignored) throws RemoteException { - IGnssNmeaListener transport = mListenerTransport; - if (transport == null) { - transport = new GnssNmeaListener(); - } - - // if a remote exception is thrown the transport should not be set - mListenerTransport = null; - mService.registerGnssNmeaCallback(transport, mContext.getPackageName(), - mContext.getAttributionTag()); - mListenerTransport = transport; + public void unregister() { + mListener = null; } @Override - protected void unregisterWithServer() throws RemoteException { - if (mListenerTransport != null) { - IGnssNmeaListener transport = mListenerTransport; - mListenerTransport = null; - mService.unregisterGnssNmeaCallback(transport); - } - } - - private class GnssNmeaListener extends IGnssNmeaListener.Stub { - - GnssNmeaListener() {} - - @Override - public void onNmeaReceived(long timestamp, String nmea) { - deliverToListeners(callback -> callback.onNmeaMessage(nmea, timestamp)); - } - } - } - - private class GnssMeasurementsTransportMultiplexer extends - ListenerTransportMultiplexer { - - private @Nullable IGnssMeasurementsListener mListenerTransport; - - GnssMeasurementsTransportMultiplexer() {} - - @Override - protected void registerWithServer(GnssMeasurementRequest request) throws RemoteException { - IGnssMeasurementsListener transport = mListenerTransport; - if (transport == null) { - transport = new GnssMeasurementsListener(); - } - - // if a remote exception is thrown the transport should not be set - mListenerTransport = null; - mService.addGnssMeasurementsListener(request, transport, mContext.getPackageName(), - mContext.getAttributionTag()); - mListenerTransport = transport; + public @Nullable GnssNavigationMessage.Callback getListener() { + return mListener; } @Override - protected void unregisterWithServer() throws RemoteException { - if (mListenerTransport != null) { - IGnssMeasurementsListener transport = mListenerTransport; - mListenerTransport = null; - mService.removeGnssMeasurementsListener(transport); - } + public void onGnssNavigationMessageReceived(GnssNavigationMessage event) { + execute(mExecutor, listener -> listener.onGnssNavigationMessageReceived(event)); } @Override - protected GnssMeasurementRequest mergeRequests( - Collection requests) { - GnssMeasurementRequest.Builder builder = new GnssMeasurementRequest.Builder(); - for (GnssMeasurementRequest request : requests) { - if (request.isFullTracking()) { - builder.setFullTracking(true); - } - if (request.isCorrelationVectorOutputsEnabled()) { - builder.setCorrelationVectorOutputsEnabled(true); - } - } - - return builder.build(); - } - - private class GnssMeasurementsListener extends IGnssMeasurementsListener.Stub { - - GnssMeasurementsListener() {} - - @Override - public void onGnssMeasurementsReceived(final GnssMeasurementsEvent event) { - deliverToListeners(callback -> callback.onGnssMeasurementsReceived(event)); - } - - @Override - public void onStatusChanged(int status) { - deliverToListeners(callback -> callback.onStatusChanged(status)); - } - } - } - - private class GnssNavigationTransportMultiplexer extends - ListenerTransportMultiplexer { - - @Nullable - private IGnssNavigationMessageListener mListenerTransport; - - GnssNavigationTransportMultiplexer() {} - - @Override - protected void registerWithServer(Void ignored) throws RemoteException { - IGnssNavigationMessageListener transport = mListenerTransport; - if (transport == null) { - transport = new GnssNavigationMessageListener(); - } - - // if a remote exception is thrown the transport should not be set - mListenerTransport = null; - mService.addGnssNavigationMessageListener(transport, mContext.getPackageName(), - mContext.getAttributionTag()); - mListenerTransport = transport; - } - - @Override - protected void unregisterWithServer() throws RemoteException { - if (mListenerTransport != null) { - IGnssNavigationMessageListener transport = mListenerTransport; - mListenerTransport = null; - mService.removeGnssNavigationMessageListener(transport); - } - } - - private class GnssNavigationMessageListener extends IGnssNavigationMessageListener.Stub { - - GnssNavigationMessageListener() {} - - @Override - public void onGnssNavigationMessageReceived(GnssNavigationMessage event) { - deliverToListeners(listener -> listener.onGnssNavigationMessageReceived(event)); - } - - @Override - public void onStatusChanged(int status) { - deliverToListeners(listener -> listener.onStatusChanged(status)); - } - } - } - - private class GnssAntennaInfoTransportMultiplexer extends - ListenerTransportMultiplexer { - - private @Nullable BroadcastReceiver mListenerTransport; - - GnssAntennaInfoTransportMultiplexer() {} - - @Override - protected void registerWithServer(Void ignored) { - if (mListenerTransport == null) { - // if an exception is thrown the transport should not be set - BroadcastReceiver transport = new GnssAntennaInfoReceiver(); - mContext.registerReceiver(transport, - new IntentFilter(ACTION_GNSS_ANTENNA_INFOS_CHANGED)); - mListenerTransport = transport; - } - } - - @Override - protected void unregisterWithServer() { - if (mListenerTransport != null) { - BroadcastReceiver transport = mListenerTransport; - mListenerTransport = null; - mContext.unregisterReceiver(transport); - } - } - - private class GnssAntennaInfoReceiver extends BroadcastReceiver { - - GnssAntennaInfoReceiver() {} - - @Override - public void onReceive(Context context, Intent intent) { - ArrayList infos = intent.getParcelableArrayListExtra( - EXTRA_GNSS_ANTENNA_INFOS); - if (infos != null) { - deliverToListeners(callback -> callback.onGnssAntennaInfoReceived(infos)); - } - } + public void onStatusChanged(int status) { + execute(mExecutor, listener -> listener.onStatusChanged(status)); } } @@ -3334,12 +3378,6 @@ public class LocationManager { } } - /** - * @hide - */ - private static final String CACHE_KEY_LOCATION_ENABLED_PROPERTY = - "cache_key.location_enabled"; - /** * @hide */ @@ -3351,8 +3389,6 @@ public class LocationManager { * @hide */ public void disableLocalLocationEnabledCaches() { - synchronized (mLock) { - mLocationEnabledCache = null; - } + mLocationEnabledCache = null; } }