Add Executor APIs to LocationManager

Add executor support for all LM APIs and consolidate various GNSS
listeners. Also fixes some edge cases around multithreaded listener
callback delivery.

Bug: 136212299
Test: Manual
Change-Id: I2e863e41de846e739654362e859b58a5ed45c673
This commit is contained in:
Soonil Nagarkar
2019-09-23 11:58:52 -07:00
parent df801fcf87
commit 4551405b93
10 changed files with 914 additions and 868 deletions

View File

@@ -23089,8 +23089,9 @@ package android.location {
public class LocationManager {
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean addGpsStatusListener(android.location.GpsStatus.Listener);
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean addNmeaListener(@NonNull android.location.OnNmeaMessageListener);
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean addNmeaListener(@NonNull android.location.OnNmeaMessageListener);
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean addNmeaListener(@NonNull android.location.OnNmeaMessageListener, @Nullable android.os.Handler);
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean addNmeaListener(@NonNull java.util.concurrent.Executor, @NonNull android.location.OnNmeaMessageListener);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void addProximityAlert(double, double, float, long, @NonNull android.app.PendingIntent);
method public void addTestProvider(@NonNull String, boolean, boolean, boolean, boolean, boolean, boolean, boolean, int, int);
method @Deprecated public void clearTestProviderEnabled(@NonNull String);
@@ -23107,12 +23108,15 @@ package android.location {
method @NonNull public java.util.List<java.lang.String> getProviders(@NonNull android.location.Criteria, boolean);
method public boolean isLocationEnabled();
method public boolean isProviderEnabled(@NonNull String);
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssMeasurementsCallback(@NonNull android.location.GnssMeasurementsEvent.Callback);
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssMeasurementsCallback(@NonNull android.location.GnssMeasurementsEvent.Callback);
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssMeasurementsCallback(@NonNull android.location.GnssMeasurementsEvent.Callback, @Nullable android.os.Handler);
method public boolean registerGnssNavigationMessageCallback(@NonNull android.location.GnssNavigationMessage.Callback);
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssMeasurementsCallback(@NonNull java.util.concurrent.Executor, @NonNull android.location.GnssMeasurementsEvent.Callback);
method @Deprecated public boolean registerGnssNavigationMessageCallback(@NonNull android.location.GnssNavigationMessage.Callback);
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssNavigationMessageCallback(@NonNull android.location.GnssNavigationMessage.Callback, @Nullable android.os.Handler);
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssStatusCallback(@NonNull android.location.GnssStatus.Callback);
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssNavigationMessageCallback(@NonNull java.util.concurrent.Executor, @NonNull android.location.GnssNavigationMessage.Callback);
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssStatusCallback(@NonNull android.location.GnssStatus.Callback);
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssStatusCallback(@NonNull android.location.GnssStatus.Callback, @Nullable android.os.Handler);
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssStatusCallback(@NonNull java.util.concurrent.Executor, @NonNull android.location.GnssStatus.Callback);
method @Deprecated public void removeGpsStatusListener(android.location.GpsStatus.Listener);
method public void removeNmeaListener(@NonNull android.location.OnNmeaMessageListener);
method @RequiresPermission(anyOf={"android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"}, apis="..22") public void removeProximityAlert(@NonNull android.app.PendingIntent);
@@ -23121,7 +23125,9 @@ package android.location {
method public void removeUpdates(@NonNull android.app.PendingIntent);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull String, long, float, @NonNull android.location.LocationListener);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull String, long, float, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull String, long, float, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(long, float, @NonNull android.location.Criteria, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(long, float, @NonNull android.location.Criteria, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull String, long, float, @NonNull android.app.PendingIntent);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(long, float, @NonNull android.location.Criteria, @NonNull android.app.PendingIntent);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestSingleUpdate(@NonNull String, @NonNull android.location.LocationListener, @Nullable android.os.Looper);

View File

@@ -3430,6 +3430,7 @@ package android.location {
method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public boolean isProviderPackage(@NonNull String);
method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean registerGnssBatchedLocationCallback(long, boolean, @NonNull android.location.BatchedLocationCallback, @Nullable android.os.Handler);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull android.location.LocationRequest, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull android.location.LocationRequest, @NonNull android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void setExtraLocationControllerPackage(@Nullable String);
method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void setExtraLocationControllerPackageEnabled(boolean);

View File

@@ -1085,6 +1085,7 @@ package android.location {
method @NonNull public String[] getIgnoreSettingsWhitelist();
method @NonNull public java.util.List<android.location.LocationRequest> getTestProviderCurrentRequests(String);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull android.location.LocationRequest, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull android.location.LocationRequest, @NonNull android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setLocationEnabledForUser(boolean, @NonNull android.os.UserHandle);
}

View File

@@ -218,7 +218,6 @@ Landroid/location/ILocationManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
Landroid/location/ILocationManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/location/ILocationManager;
Landroid/location/ILocationManager$Stub;->TRANSACTION_getAllProviders:I
Landroid/location/INetInitiatedListener$Stub;-><init>()V
Landroid/location/LocationManager$ListenerTransport;-><init>(Landroid/location/LocationManager;Landroid/location/LocationListener;Landroid/os/Looper;)V
Landroid/Manifest$permission;->CAPTURE_SECURE_VIDEO_OUTPUT:Ljava/lang/String;
Landroid/Manifest$permission;->CAPTURE_VIDEO_OUTPUT:Ljava/lang/String;
Landroid/Manifest$permission;->READ_FRAME_BUFFER:Ljava/lang/String;

View File

@@ -0,0 +1,139 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.location;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.RemoteException;
import android.util.ArrayMap;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
* A base class to manage listeners that have a 1:N -> source:listener relationship.
*
* @hide
*/
abstract class AbstractListenerManager<T> {
private static class Registration<T> {
private final Executor mExecutor;
@Nullable private volatile T mListener;
private Registration(Executor executor, T listener) {
Preconditions.checkArgument(listener != null);
Preconditions.checkArgument(executor != null);
mExecutor = executor;
mListener = listener;
}
private void unregister() {
mListener = null;
}
private void execute(Consumer<T> operation) {
mExecutor.execute(() -> {
T listener = mListener;
if (listener == null) {
return;
}
// we may be under the binder identity if a direct executor is used
long identity = Binder.clearCallingIdentity();
try {
operation.accept(listener);
} finally {
Binder.restoreCallingIdentity(identity);
}
});
}
}
@GuardedBy("mListeners")
private final ArrayMap<Object, Registration<T>> mListeners = new ArrayMap<>();
public boolean addListener(@NonNull T listener, @NonNull Handler handler)
throws RemoteException {
return addInternal(listener, handler);
}
public boolean addListener(@NonNull T listener, @NonNull Executor executor)
throws RemoteException {
return addInternal(listener, executor);
}
protected final boolean addInternal(Object listener, Handler handler) throws RemoteException {
return addInternal(listener, new HandlerExecutor(handler));
}
protected final boolean addInternal(Object listener, Executor executor) throws RemoteException {
return addInternal(listener, new Registration<>(executor, convertKey(listener)));
}
private boolean addInternal(Object key, Registration<T> registration) throws RemoteException {
Preconditions.checkNotNull(key);
Preconditions.checkNotNull(registration);
synchronized (mListeners) {
if (mListeners.isEmpty() && !registerService()) {
return false;
}
Registration<T> oldRegistration = mListeners.put(key, registration);
if (oldRegistration != null) {
oldRegistration.unregister();
}
return true;
}
}
public void removeListener(Object listener) throws RemoteException {
synchronized (mListeners) {
Registration<T> oldRegistration = mListeners.remove(listener);
if (oldRegistration == null) {
return;
}
oldRegistration.unregister();
if (mListeners.isEmpty()) {
unregisterService();
}
}
}
@SuppressWarnings("unchecked")
protected T convertKey(@NonNull Object listener) {
return (T) listener;
}
protected abstract boolean registerService() throws RemoteException;
protected abstract void unregisterService() throws RemoteException;
protected void execute(Consumer<T> operation) {
synchronized (mListeners) {
for (Registration<T> registration : mListeners.values()) {
registration.execute(operation);
}
}
}
}

View File

@@ -1,66 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package android.location;
import android.content.Context;
import android.os.RemoteException;
import java.util.List;
/**
* A handler class to manage transport callbacks for {@link BatchedLocationCallback}.
*
* @hide
*/
class BatchedLocationCallbackTransport
extends LocalListenerHelper<BatchedLocationCallback> {
private final ILocationManager mLocationManager;
private final IBatchedLocationCallback mCallbackTransport = new CallbackTransport();
public BatchedLocationCallbackTransport(Context context, ILocationManager locationManager) {
super(context, "BatchedLocationCallbackTransport");
mLocationManager = locationManager;
}
@Override
protected boolean registerWithServer() throws RemoteException {
return mLocationManager.addGnssBatchingCallback(
mCallbackTransport,
getContext().getPackageName());
}
@Override
protected void unregisterFromServer() throws RemoteException {
mLocationManager.removeGnssBatchingCallback();
}
private class CallbackTransport extends IBatchedLocationCallback.Stub {
@Override
public void onLocationBatch(final List<Location> locations) {
ListenerOperation<BatchedLocationCallback> operation =
new ListenerOperation<BatchedLocationCallback>() {
@Override
public void execute(BatchedLocationCallback callback)
throws RemoteException {
callback.onLocationBatch(locations);
}
};
foreach(operation);
}
}
}

View File

@@ -1,97 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package android.location;
import android.content.Context;
import android.os.RemoteException;
import com.android.internal.util.Preconditions;
/**
* A handler class to manage transport callbacks for {@link GnssMeasurementsEvent.Callback}.
*
* @hide
*/
class GnssMeasurementCallbackTransport
extends LocalListenerHelper<GnssMeasurementsEvent.Callback> {
private static final String TAG = "GnssMeasCbTransport";
private final ILocationManager mLocationManager;
private final IGnssMeasurementsListener mListenerTransport = new ListenerTransport();
public GnssMeasurementCallbackTransport(Context context, ILocationManager locationManager) {
super(context, TAG);
mLocationManager = locationManager;
}
@Override
protected boolean registerWithServer() throws RemoteException {
return mLocationManager.addGnssMeasurementsListener(
mListenerTransport,
getContext().getPackageName());
}
@Override
protected void unregisterFromServer() throws RemoteException {
mLocationManager.removeGnssMeasurementsListener(mListenerTransport);
}
/**
* Injects GNSS measurement corrections into the GNSS chipset.
*
* @param measurementCorrections a {@link GnssMeasurementCorrections} object with the GNSS
* measurement corrections to be injected into the GNSS chipset.
*/
protected void injectGnssMeasurementCorrections(
GnssMeasurementCorrections measurementCorrections) throws RemoteException {
Preconditions.checkNotNull(measurementCorrections);
mLocationManager.injectGnssMeasurementCorrections(
measurementCorrections, getContext().getPackageName());
}
protected long getGnssCapabilities() throws RemoteException {
return mLocationManager.getGnssCapabilities(getContext().getPackageName());
}
private class ListenerTransport extends IGnssMeasurementsListener.Stub {
@Override
public void onGnssMeasurementsReceived(final GnssMeasurementsEvent event) {
ListenerOperation<GnssMeasurementsEvent.Callback> operation =
new ListenerOperation<GnssMeasurementsEvent.Callback>() {
@Override
public void execute(GnssMeasurementsEvent.Callback callback)
throws RemoteException {
callback.onGnssMeasurementsReceived(event);
}
};
foreach(operation);
}
@Override
public void onStatusChanged(final int status) {
ListenerOperation<GnssMeasurementsEvent.Callback> operation =
new ListenerOperation<GnssMeasurementsEvent.Callback>() {
@Override
public void execute(GnssMeasurementsEvent.Callback callback)
throws RemoteException {
callback.onStatusChanged(status);
}
};
foreach(operation);
}
}
}

View File

@@ -1,79 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package android.location;
import android.content.Context;
import android.os.RemoteException;
/**
* A handler class to manage transport callback for {@link GnssNavigationMessage.Callback}.
*
* @hide
*/
class GnssNavigationMessageCallbackTransport
extends LocalListenerHelper<GnssNavigationMessage.Callback> {
private final ILocationManager mLocationManager;
private final IGnssNavigationMessageListener mListenerTransport = new ListenerTransport();
public GnssNavigationMessageCallbackTransport(
Context context,
ILocationManager locationManager) {
super(context, "GnssNavigationMessageCallbackTransport");
mLocationManager = locationManager;
}
@Override
protected boolean registerWithServer() throws RemoteException {
return mLocationManager.addGnssNavigationMessageListener(
mListenerTransport,
getContext().getPackageName());
}
@Override
protected void unregisterFromServer() throws RemoteException {
mLocationManager.removeGnssNavigationMessageListener(mListenerTransport);
}
private class ListenerTransport extends IGnssNavigationMessageListener.Stub {
@Override
public void onGnssNavigationMessageReceived(final GnssNavigationMessage event) {
ListenerOperation<GnssNavigationMessage.Callback> operation =
new ListenerOperation<GnssNavigationMessage.Callback>() {
@Override
public void execute(GnssNavigationMessage.Callback callback)
throws RemoteException {
callback.onGnssNavigationMessageReceived(event);
}
};
foreach(operation);
}
@Override
public void onStatusChanged(final int status) {
ListenerOperation<GnssNavigationMessage.Callback> operation =
new ListenerOperation<GnssNavigationMessage.Callback>() {
@Override
public void execute(GnssNavigationMessage.Callback callback)
throws RemoteException {
callback.onStatusChanged(status);
}
};
foreach(operation);
}
}
}

View File

@@ -1,134 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package android.location;
import android.annotation.NonNull;
import android.content.Context;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* A base handler class to manage transport and local listeners.
*
* @hide
*/
abstract class LocalListenerHelper<TListener> {
private final HashMap<TListener, Handler> mListeners = new HashMap<>();
private final String mTag;
private final Context mContext;
protected LocalListenerHelper(Context context, String name) {
Preconditions.checkNotNull(name);
mContext = context;
mTag = name;
}
/**
* Adds a {@param listener} to the list of listeners on which callbacks will be executed. The
* execution will happen on the {@param handler} thread or alternatively in the callback thread
* if a {@code null} handler value is passed.
*/
public boolean add(@NonNull TListener listener, Handler handler) {
Preconditions.checkNotNull(listener);
synchronized (mListeners) {
// we need to register with the service first, because we need to find out if the
// service will actually support the request before we attempt anything
if (mListeners.isEmpty()) {
boolean registeredWithService;
try {
registeredWithService = registerWithServer();
} catch (RemoteException e) {
Log.e(mTag, "Error handling first listener.", e);
return false;
}
if (!registeredWithService) {
Log.e(mTag, "Unable to register listener transport.");
return false;
}
}
if (mListeners.containsKey(listener)) {
return true;
}
mListeners.put(listener, handler);
return true;
}
}
public void remove(@NonNull TListener listener) {
Preconditions.checkNotNull(listener);
synchronized (mListeners) {
boolean removed = mListeners.containsKey(listener);
mListeners.remove(listener);
boolean isLastRemoved = removed && mListeners.isEmpty();
if (isLastRemoved) {
try {
unregisterFromServer();
} catch (RemoteException e) {
Log.v(mTag, "Error handling last listener removal", e);
}
}
}
}
protected abstract boolean registerWithServer() throws RemoteException;
protected abstract void unregisterFromServer() throws RemoteException;
protected interface ListenerOperation<TListener> {
void execute(TListener listener) throws RemoteException;
}
protected Context getContext() {
return mContext;
}
private void executeOperation(ListenerOperation<TListener> operation, TListener listener) {
try {
operation.execute(listener);
} catch (RemoteException e) {
Log.e(mTag, "Error in monitored listener.", e);
// don't return, give a fair chance to all listeners to receive the event
}
}
protected void foreach(final ListenerOperation<TListener> operation) {
Collection<Map.Entry<TListener, Handler>> listeners;
synchronized (mListeners) {
listeners = new ArrayList<>(mListeners.entrySet());
}
for (final Map.Entry<TListener, Handler> listener : listeners) {
if (listener.getValue() == null) {
executeOperation(operation, listener.getKey());
} else {
listener.getValue().post(new Runnable() {
@Override
public void run() {
executeOperation(operation, listener.getKey());
}
});
}
}
}
}

File diff suppressed because it is too large Load Diff