blob: 36b86899f2d8fcfa126eae519b25f43b2304e7e0 [file] [log] [blame]
/*
* 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 static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;
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.Objects;
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<TRequest, TListener> {
private static class Registration<TRequest, TListener> {
private final Executor mExecutor;
@Nullable private TRequest mRequest;
@Nullable private volatile TListener mListener;
private Registration(@Nullable TRequest request, Executor executor, TListener listener) {
Preconditions.checkArgument(listener != null, "invalid null listener/callback");
Preconditions.checkArgument(executor != null, "invalid null executor");
mExecutor = executor;
mListener = listener;
mRequest = request;
}
@Nullable
public TRequest getRequest() {
return mRequest;
}
private void unregister() {
mRequest = null;
mListener = null;
}
private void execute(Consumer<TListener> operation) {
mExecutor.execute(
obtainRunnable(Registration<TRequest, TListener>::accept, this, operation)
.recycleOnUse());
}
private void accept(Consumer<TListener> operation) {
TListener 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);
}
}
}
private final Object mLock = new Object();
@GuardedBy("mLock")
private volatile ArrayMap<Object, Registration<TRequest, TListener>> mListeners =
new ArrayMap<>();
@GuardedBy("mLock")
@Nullable
private TRequest mMergedRequest;
public boolean addListener(@NonNull TListener listener, @NonNull Handler handler)
throws RemoteException {
return addInternal(/* request= */ null, listener, handler);
}
public boolean addListener(@NonNull TListener listener, @NonNull Executor executor)
throws RemoteException {
return addInternal(/* request= */ null, listener, executor);
}
public boolean addListener(@Nullable TRequest request, @NonNull TListener listener,
@NonNull Handler handler) throws RemoteException {
return addInternal(request, listener, handler);
}
public boolean addListener(@Nullable TRequest request, @NonNull TListener listener,
@NonNull Executor executor) throws RemoteException {
return addInternal(request, listener, executor);
}
protected final boolean addInternal(@Nullable TRequest request, @NonNull Object listener,
@NonNull Handler handler) throws RemoteException {
return addInternal(request, listener, new HandlerExecutor(handler));
}
protected final boolean addInternal(@Nullable TRequest request, @NonNull Object listener,
@NonNull Executor executor)
throws RemoteException {
Preconditions.checkArgument(listener != null, "invalid null listener/callback");
return addInternal(listener, new Registration<>(request, executor, convertKey(listener)));
}
private boolean addInternal(Object key, Registration<TRequest, TListener> registration)
throws RemoteException {
Preconditions.checkNotNull(registration);
synchronized (mLock) {
boolean initialRequest = mListeners.isEmpty();
ArrayMap<Object, Registration<TRequest, TListener>> newListeners = new ArrayMap<>(
mListeners.size() + 1);
newListeners.putAll(mListeners);
Registration<TRequest, TListener> oldRegistration = newListeners.put(key,
registration);
mListeners = newListeners;
if (oldRegistration != null) {
oldRegistration.unregister();
}
TRequest merged = mergeRequests();
if (initialRequest || !Objects.equals(merged, mMergedRequest)) {
mMergedRequest = merged;
if (!initialRequest) {
unregisterService();
}
registerService(mMergedRequest);
}
return true;
}
}
public void removeListener(Object listener) throws RemoteException {
synchronized (mLock) {
ArrayMap<Object, Registration<TRequest, TListener>> newListeners = new ArrayMap<>(
mListeners);
Registration<TRequest, TListener> oldRegistration = newListeners.remove(listener);
mListeners = newListeners;
if (oldRegistration == null) {
return;
}
oldRegistration.unregister();
boolean lastRequest = mListeners.isEmpty();
TRequest merged = lastRequest ? null : mergeRequests();
boolean newRequest = !lastRequest && !Objects.equals(merged, mMergedRequest);
if (lastRequest || newRequest) {
unregisterService();
mMergedRequest = merged;
if (newRequest) {
registerService(mMergedRequest);
}
}
}
}
@SuppressWarnings("unchecked")
protected TListener convertKey(@NonNull Object listener) {
return (TListener) listener;
}
protected abstract boolean registerService(TRequest request) throws RemoteException;
protected abstract void unregisterService() throws RemoteException;
@Nullable
protected TRequest merge(@NonNull TRequest[] requests) {
for (TRequest request : requests) {
Preconditions.checkArgument(request == null,
"merge() has to be overridden for non-null requests.");
}
return null;
}
protected void execute(Consumer<TListener> operation) {
for (Registration<TRequest, TListener> registration : mListeners.values()) {
registration.execute(operation);
}
}
@GuardedBy("mLock")
@SuppressWarnings("unchecked")
@Nullable
private TRequest mergeRequests() {
Preconditions.checkState(Thread.holdsLock(mLock));
if (mListeners.isEmpty()) {
return null;
}
if (mListeners.size() == 1) {
return mListeners.valueAt(0).getRequest();
}
TRequest[] requests = (TRequest[]) new Object[mListeners.size()];
for (int index = 0; index < mListeners.size(); index++) {
requests[index] = mListeners.valueAt(index).getRequest();
}
return merge(requests);
}
}