Reduce LocationManager memory usage

Move GNSS listener overhead into statics so it's shared per process, and
do not instantiate by default.

Previously GNSS requests were multiplexed by LocationManager instance,
this is now removed. This could mean additional binder overhead, but
it's pretty unlikely that this feature was in active use anywhere, as in
any large application that has multiple gnss requests, they are usually
coming from different contexts (and thus LocationManager instances) and
would not have seen the benefit of multiplexing anyways.

Bug: 179261871
Test: atest CtsLocationFineTestCases
Change-Id: I8b7a41c13dfe219c326b1e519483d89d2b721ce5
This commit is contained in:
Soonil Nagarkar
2021-02-02 15:29:23 -08:00
parent 9ee2e0d368
commit 204b133883
6 changed files with 480 additions and 866 deletions

View File

@@ -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 <TListener> listener type
*/
public class ListenerTransport<TListener> {
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<TListener> {
/**
* 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<TListener> operation) {
default void execute(Executor executor, Consumer<TListener> 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<TListener> {
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();
}
}

View File

@@ -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 <TTransport>> transport type
*/
public abstract class ListenerTransportManager<TTransport extends ListenerTransport<?>> {
@GuardedBy("mRegistrations")
private final Map<Object, WeakReference<TTransport>> 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<TTransport> 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<TTransport> 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;
}

View File

@@ -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 <TRequest> listener request type, may be Void
* @param <TListener> listener type
*/
public abstract class ListenerTransportMultiplexer<TRequest, TListener> {
private final Object mLock = new Object();
@GuardedBy("mLock")
private ArrayMap<Object, RequestListenerTransport<TRequest, TListener>> 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<TRequest> 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<TRequest, TListener> registration =
new RequestListenerTransport<>(request, executor, listener);
synchronized (mLock) {
ArrayMap<Object, RequestListenerTransport<TRequest, TListener>> newRegistrations =
new ArrayMap<>(mRegistrations.size() + 1);
newRegistrations.putAll(mRegistrations);
RequestListenerTransport<TRequest, TListener> 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<Object, RequestListenerTransport<TRequest, TListener>> newRegistrations =
new ArrayMap<>(mRegistrations);
RequestListenerTransport<TRequest, TListener> 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<TRequest> 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<TListener> operation) {
ArrayMap<Object, RequestListenerTransport<TRequest, TListener>> 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<TListener> operation) {}
/**
* Dumps debug information.
*/
public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
ArrayMap<Object, RequestListenerTransport<TRequest, TListener>> 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();
}
}
}

View File

@@ -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 <TRequest> request type
* @param <TListener> listener type
*/
public class RequestListenerTransport<TRequest, TListener> extends ListenerTransport<TListener> {
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;
}
}

View File

@@ -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> 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<Integer, Runnable> {
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<Integer> requests) {
return requests.stream().max(Comparator.naturalOrder()).get();
}
}
}

View File

@@ -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<LocationListener, WeakReference<LocationListenerTransport>>
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<Integer, Boolean> mLocationEnabledCache =
private volatile PropertyInvalidatedCache<Integer, Boolean> mLocationEnabledCache =
new PropertyInvalidatedCache<Integer, Boolean>(
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<Integer, Boolean> cache = mLocationEnabledCache;
if (cache != null) {
return cache.query(userHandle.getIdentifier());
}
// fallback if cache is disabled
@@ -2264,9 +2228,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);
@@ -2299,8 +2262,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;
}
@@ -2319,7 +2282,7 @@ public class LocationManager {
"GpsStatus APIs not supported, please use GnssStatus APIs instead");
}
getGnssStatusTransportMultiplexer().removeListener(listener);
GnssLazyLoader.sGnssStatusListeners.removeListener(listener);
}
/**
@@ -2382,7 +2345,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;
}
@@ -2392,7 +2356,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);
}
/**
@@ -2472,7 +2436,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;
}
@@ -2482,7 +2447,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);
}
/**
@@ -2598,10 +2563,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);
}
/**
@@ -2623,8 +2586,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;
}
@@ -2656,7 +2619,7 @@ public class LocationManager {
*/
public void unregisterGnssMeasurementsCallback(
@NonNull GnssMeasurementsEvent.Callback callback) {
getGnssMeasurementsTransportMultiplexer().removeListener(callback);
GnssLazyLoader.sGnssMeasurementsListeners.removeListener(callback);
}
/**
@@ -2681,7 +2644,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;
}
@@ -2694,7 +2658,7 @@ public class LocationManager {
*/
@Deprecated
public void unregisterAntennaInfoListener(@NonNull GnssAntennaInfo.Listener listener) {
getGnssAntennaInfoTransportMultiplexer().removeListener(listener);
GnssLazyLoader.sGnssAntennaInfoListeners.removeListener(listener);
}
/**
@@ -2784,7 +2748,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;
}
@@ -2795,7 +2760,7 @@ public class LocationManager {
*/
public void unregisterGnssNavigationMessageCallback(
@NonNull GnssNavigationMessage.Callback callback) {
getGnssNavigationTransportMultiplexer().removeListener(callback);
GnssLazyLoader.sGnssNavigationListeners.removeListener(callback);
}
/**
@@ -2904,6 +2869,89 @@ public class LocationManager {
}
}
private static class GnssStatusTransportManager extends
ListenerTransportManager<GnssStatusTransport> {
@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<GnssNmeaTransport> {
@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<GnssMeasurementsTransport> {
@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<GnssAntennaInfoTransport> {
@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<GnssNavigationTransport> {
@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 {
@@ -3018,7 +3066,7 @@ public class LocationManager {
@Override
public void onStarted() {
mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_STARTED);
mGpsListener.onGpsStatusChanged(GPS_EVENT_STARTED);
}
@Override
@@ -3037,273 +3085,269 @@ public class LocationManager {
}
}
private class GnssStatusTransportMultiplexer extends
ListenerTransportMultiplexer<Void, GnssStatus.Callback> {
private static class GnssStatusTransport extends IGnssStatusListener.Stub implements
ListenerTransport<GnssStatus.Callback> {
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<OnNmeaMessageListener> {
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<GnssMeasurementsEvent.Callback> {
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<GnssAntennaInfo.Listener> {
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<GnssAntennaInfo> infos = intent.getParcelableArrayListExtra(
EXTRA_GNSS_ANTENNA_INFOS);
if (infos != null) {
execute(mExecutor, callback -> callback.onGnssAntennaInfoReceived(infos));
}
}
}
private class GnssNmeaTransportMultiplexer extends
ListenerTransportMultiplexer<Void, OnNmeaMessageListener> {
private static class GnssNavigationTransport extends IGnssNavigationMessageListener.Stub
implements ListenerTransport<GnssNavigationMessage.Callback> {
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<GnssMeasurementRequest, GnssMeasurementsEvent.Callback> {
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<GnssMeasurementRequest> 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<Void, GnssNavigationMessage.Callback> {
@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<Void, GnssAntennaInfo.Listener> {
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<GnssAntennaInfo> 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));
}
}
@@ -3333,12 +3377,6 @@ public class LocationManager {
}
}
/**
* @hide
*/
private static final String CACHE_KEY_LOCATION_ENABLED_PROPERTY =
"cache_key.location_enabled";
/**
* @hide
*/
@@ -3350,8 +3388,6 @@ public class LocationManager {
* @hide
*/
public void disableLocalLocationEnabledCaches() {
synchronized (mLock) {
mLocationEnabledCache = null;
}
mLocationEnabledCache = null;
}
}