| /* |
| * 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); |
| } |
| } |