Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8058d919 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Refactor GNSS listeners"

parents 5304effe 6257ba86
Loading
Loading
Loading
Loading
+0 −220
Original line number Diff line number Diff line
/*
 * 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);
            }
        }
    }

    @GuardedBy("mListeners")
    private final ArrayMap<Object, Registration<TRequest, TListener>> mListeners =
            new ArrayMap<>();

    @GuardedBy("mListeners")
    @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 (mListeners) {
            boolean initialRequest = mListeners.isEmpty();

            Registration<TRequest, TListener> oldRegistration = mListeners.put(key, registration);
            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 (mListeners) {
            Registration<TRequest, TListener> oldRegistration = mListeners.remove(listener);
            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) {
        synchronized (mListeners) {
            for (Registration<TRequest, TListener> registration : mListeners.values()) {
                registration.execute(operation);
            }
        }
    }

    @GuardedBy("mListeners")
    @SuppressWarnings("unchecked")
    @Nullable
    private TRequest mergeRequests() {
        Preconditions.checkState(Thread.holdsLock(mListeners));

        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);
    }
}
+3 −1
Original line number Diff line number Diff line
@@ -70,7 +70,9 @@ public final class GnssRequest implements Parcelable {
    public String toString() {
        StringBuilder s = new StringBuilder();
        s.append("GnssRequest[");
        s.append("FullTracking=").append(mFullTracking);
        if (mFullTracking) {
            s.append("FullTracking");
        }
        s.append(']');
        return s.toString();
    }
+13 −18
Original line number Diff line number Diff line
@@ -57,10 +57,6 @@ interface ILocationManager
            in PendingIntent intent, String packageName, String featureId);
    void removeGeofence(in Geofence fence, in PendingIntent intent, String packageName);

    boolean registerGnssStatusCallback(IGnssStatusListener callback, String packageName,
            String featureId);
    void unregisterGnssStatusCallback(IGnssStatusListener callback);

    boolean geocoderIsPresent();
    String getFromLocation(double latitude, double longitude, int maxResults,
        in GeocoderParams params, out List<Address> addrs);
@@ -69,31 +65,30 @@ interface ILocationManager
        double upperRightLatitude, double upperRightLongitude, int maxResults,
        in GeocoderParams params, out List<Address> addrs);

    boolean addGnssMeasurementsListener(in GnssRequest request,
            in IGnssMeasurementsListener listener,
            String packageName, String featureId);
    void injectGnssMeasurementCorrections(in GnssMeasurementCorrections corrections,
            in String packageName);
    long getGnssCapabilities();
    int getGnssYearOfHardware();
    String getGnssHardwareModelName();

    void registerGnssStatusCallback(in IGnssStatusListener callback, String packageName, String featureId);
    void unregisterGnssStatusCallback(in IGnssStatusListener callback);

    void addGnssMeasurementsListener(in GnssRequest request, in IGnssMeasurementsListener listener, String packageName, String featureId);
    void removeGnssMeasurementsListener(in IGnssMeasurementsListener listener);

    boolean addGnssAntennaInfoListener(in IGnssAntennaInfoListener listener,
             String packageName, String featureId);
    void addGnssAntennaInfoListener(in IGnssAntennaInfoListener listener, String packageName, String featureId);
    void removeGnssAntennaInfoListener(in IGnssAntennaInfoListener listener);

    boolean addGnssNavigationMessageListener(in IGnssNavigationMessageListener listener,
             String packageName, String featureId);
    void addGnssNavigationMessageListener(in IGnssNavigationMessageListener listener, String packageName, String featureId);
    void removeGnssNavigationMessageListener(in IGnssNavigationMessageListener listener);

    int getGnssYearOfHardware();
    String getGnssHardwareModelName();
    void injectGnssMeasurementCorrections(in GnssMeasurementCorrections corrections, String packageName);

    int getGnssBatchSize(String packageName);
    boolean addGnssBatchingCallback(in IBatchedLocationCallback callback, String packageName, String featureId);
    void addGnssBatchingCallback(in IBatchedLocationCallback callback, String packageName, String featureId);
    void removeGnssBatchingCallback();
    boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName, String featureId);
    void startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName, String featureId);
    void flushGnssBatch(String packageName);
    boolean stopGnssBatch();
    void stopGnssBatch();
    void injectLocation(in Location location);

    List<String> getAllProviders();
+181 −219

File changed.

Preview size limit exceeded, changes collapsed.

+411 −0
Original line number Diff line number Diff line
/*
 * 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 android.location.util.listeners;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Binder;
import android.os.Build;
import android.util.ArrayMap;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * A base class to manage listeners multiplexed from some source.
 *
 * @param <TKey>           key type
 * @param <TRequest>       request type
 * @param <TListener>      listener type
 * @param <TRegistration>  registration type
 * @param <TMergedRequest> merged request type
 * @hide
 */
public abstract class AbstractListenerManager<TKey, TRequest, TListener,
        TRegistration extends AbstractListenerManager.Registration<TRequest, TListener>,
        TMergedRequest> {

    /**
     * A listener registration object which holds data associated with the listener.
     *
     * @param <TRequest>  request type
     * @param <TListener> listener type
     */
    public static class Registration<TRequest, TListener> {

        private final Executor mExecutor;
        private volatile @Nullable TRequest mRequest;
        private volatile @Nullable TListener mListener;

        protected Registration(@Nullable TRequest request, @NonNull Executor executor,
                @NonNull TListener listener) {
            Preconditions.checkArgument(executor != null, "invalid null executor");
            Preconditions.checkArgument(listener != null, "invalid null listener/callback");
            mExecutor = executor;
            mRequest = request;
            mListener = listener;
        }

        /**
         * Returns the request associated with this listener, or null if one wasn't supplied.
         */
        public @Nullable TRequest getRequest() {
            return mRequest;
        }

        /**
         * Returns the listener, or null if this registration is no longer registered.
         */
        protected @Nullable TListener getListener() {
            return mListener;
        }

        boolean register() {
            Preconditions.checkState(mListener != null);
            return onRegister();
        }

        protected final void unregister() {
            if (mListener != null) {
                onUnregister();
                mRequest = null;
                mListener = null;
            }
        }

        /**
         * May be overridden by subclasses. Invoked when registration is occurring. If this returns
         * true, then registration will complete successfully. If this returns false, registration
         * will fail.
         */
        protected boolean onRegister() {
            return true;
        }

        /**
         * May be overridden by subclasses. Invoked before unregistration occurs.
         */
        protected void onUnregister() {}

        void execute(Consumer<TListener> operation) {
            mExecutor.execute(() -> {
                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);
                }
            });
        }

        @Override
        public String toString() {
            if (mRequest == null) {
                return "[]";
            } else {
                return mRequest.toString();
            }
        }
    }

    @GuardedBy("mRegistrations")
    private final ArrayMap<TKey, TRegistration> mRegistrations = new ArrayMap<>();

    @GuardedBy("mRegistrations")
    private final ReentrancyGuard mReentrancyGuard = new ReentrancyGuard();

    @GuardedBy("mRegistrations")
    private boolean mServiceRegistered = false;

    @GuardedBy("mRegistrations")
    private TMergedRequest mCurrentRequest;

    /**
     * Adds a new registration with the given key. If the registration succeeds,
     * {@link #onRegistrationAdded(Object, Registration)} will be invoked.
     */
    protected final void addRegistration(@NonNull TKey key, @NonNull TRegistration registration) {
        synchronized (mRegistrations) {
            // this class does not support adding listeners reentrantly
            Preconditions.checkState(!mReentrancyGuard.isReentrant());

            if (!registration.register()) {
                registration.unregister();
                return;
            }

            TRegistration old = mRegistrations.put(Objects.requireNonNull(key), registration);
            if (old != null) {
                onRegistrationRemoved(key, old);
                old.unregister();
            }

            onRegistrationAdded(key, registration);
            updateService();
        }
    }

    /**
     * Removes the given registration with the given key. If unregistration occurs,
     * {@link #onRegistrationRemoved(Object, Registration)} will be called.
     */
    protected final void removeRegistration(@NonNull TKey key,
            @NonNull TRegistration registration) {
        synchronized (mRegistrations) {
            if (mRegistrations.remove(key, registration)) {
                unregisterRegistration(key, registration);
            }
        }
    }

    /**
     * Removes the registration with the given key. If unregistration occurs,
     * {@link #onRegistrationRemoved(Object, Registration)} will be called.
     */
    protected final void removeRegistration(@NonNull TKey key) {
        synchronized (mRegistrations) {
            TRegistration registration = mRegistrations.remove(key);
            if (registration != null) {
                unregisterRegistration(key, registration);
            }
        }
    }

    @GuardedBy("mRegistrations")
    private void unregisterRegistration(TKey key, TRegistration registration) {
        if (Build.IS_DEBUGGABLE) {
            Preconditions.checkState(Thread.holdsLock(mRegistrations));
        }

        // this class does not support removing listeners reentrantly
        Preconditions.checkState(!mReentrancyGuard.isReentrant());

        onRegistrationRemoved(key, registration);
        registration.unregister();
        updateService();
    }

    @GuardedBy("mRegistrations")
    private void updateService() {
        if (Build.IS_DEBUGGABLE) {
            Preconditions.checkState(Thread.holdsLock(mRegistrations));
        }

        ArrayList<TRegistration> actives = new ArrayList<>(mRegistrations.size());
        for (int i = 0; i < mRegistrations.size(); i++) {
            TRegistration registration = mRegistrations.valueAt(i);
            if (isActive(registration)) {
                actives.add(registration);
            }
        }

        if (actives.isEmpty()) {
            if (mServiceRegistered) {
                unregisterService();
                mServiceRegistered = false;
            }
            mCurrentRequest = null;
            return;
        }

        TMergedRequest merged = mergeRequests(actives);
        if (!mServiceRegistered || !Objects.equals(merged, mCurrentRequest)) {
            if (mServiceRegistered) {
                unregisterService();
            }
            mCurrentRequest = merged;
            mServiceRegistered = registerService(mCurrentRequest);
        }
    }

    /**
     * Defines whether a registration is currently active or not. Only active registrations will be
     * considered within {@link #mergeRequests(List)} to calculate the merged request, and listener
     * invocations will only be delivered to active requests. If a registration's active state
     * changes, {@link #updateRegistrations(Function)} should be invoked with a function that
     * returns true for any registrations that have changed their active state in order to inform
     * this manager of the active status change.
     */
    protected boolean isActive(@NonNull TRegistration registration) {
        return true;
    }

    /**
     * Performs some function on all (not just active) registrations. The function should return
     * true if the active state of the registration has changed, or if the change to the
     * registration may have changed the result of {@link #mergeRequests(List)}.
     */
    protected final void updateRegistrations(@NonNull Function<TRegistration, Boolean> function) {
        synchronized (mRegistrations) {
            boolean changed = false;
            try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
                for (int i = 0; i < mRegistrations.size(); i++) {
                    changed |= function.apply(mRegistrations.valueAt(i));
                }
            }
            if (changed) {
                updateService();
            }
        }
    }

    /**
     * Called in order to generate a merged request from all active requests. The list of
     * registrations will never be empty.
     */
    @Nullable
    protected TMergedRequest mergeRequests(@NonNull List<TRegistration> registrations) {
        if (Build.IS_DEBUGGABLE) {
            for (TRegistration registration : registrations) {
                // if using non-null requests then implementations must override this method
                Preconditions.checkState(registration.getRequest() == null);
            }
        }

        return null;
    }

    /**
     * Should be implemented to register the service with the given request, and should return true
     * if registration succeeds.
     */
    protected abstract boolean registerService(@Nullable TMergedRequest mergedRequest);

    /**
     * Should be implemented to unregister the service.
     */
    protected abstract void unregisterService();

    /**
     * Invoked when a registration is added.
     */
    protected void onRegistrationAdded(@NonNull TKey key, @NonNull TRegistration registration) {}

    /**
     * Invoked when a registration is removed.
     */
    protected void onRegistrationRemoved(@NonNull TKey key, @NonNull TRegistration registration) {}

    /**
     * Executes the given delivery operation for all active listeners.
     */
    protected final void deliverToListeners(@NonNull Consumer<TListener> operation) {
        deliverToListeners(operation, registration -> true);
    }

    /**
     * Executes the given delivery operation for all active listeners which pass the given
     * predicate.
     */
    protected final void deliverToListeners(@NonNull Consumer<TListener> operation,
            @NonNull Predicate<TRegistration> deliveryPredicate) {
        synchronized (mRegistrations) {
            try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
                for (int i = 0; i < mRegistrations.size(); i++) {
                    TRegistration registration = mRegistrations.valueAt(i);
                    if (isActive(registration) && deliveryPredicate.test(registration)) {
                        registration.execute(operation);
                    }
                }
            }
        }
    }

    /**
     * Dumps debug information.
     */
    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");

        synchronized (mRegistrations) {
            ipw.print("service: ");
            ipw.println(serviceStateToString());

            if (!mRegistrations.isEmpty()) {
                ipw.println("listeners:");

                ipw.increaseIndent();
                for (int i = 0; i < mRegistrations.size(); i++) {
                    TRegistration registration = mRegistrations.valueAt(i);
                    ipw.print(registration);
                    if (!isActive(registration)) {
                        ipw.println(" (inactive)");
                    } else {
                        ipw.println();
                    }
                }
                ipw.decreaseIndent();
            }
        }
    }

    /**
     * May be override to provide additional details on service state when dumping the manager
     * state.
     */
    protected String serviceStateToString() {
        if (mServiceRegistered) {
            if (mCurrentRequest == null) {
                return "registered";
            } else {
                return "registered with " + mCurrentRequest;
            }
        } else {
            return "unregistered";
        }
    }

    // this class does not have an idempotent close(), always use via try-with-resources
    private static class ReentrancyGuard implements AutoCloseable {

        private int mGuard = 0;

        ReentrancyGuard() {
        }

        public boolean isReentrant() {
            return mGuard != 0;
        }

        public ReentrancyGuard acquire() {
            ++mGuard;
            return this;
        }

        @Override
        public void close() {
            --mGuard;
        }
    }
}
Loading