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

Commit 490c46ca authored by Soonil Nagarkar's avatar Soonil Nagarkar
Browse files

Refactor and reorganize listener multiplexers

-Rename manager -> multiplexer where appropriate
-Split into client/server files/packages to minimize amount of code
going into client and keep complexity server side.
-Make GNSS listeners respect location blacklist.
-Add tests.

Test: presubmits + manual
Change-Id: I368e1c6aab1b9bcc1ff771a050431abc8e9c4c00
parent 409ef11a
Loading
Loading
Loading
Loading
+102 −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 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.concurrent.RejectedExecutionException;
import java.util.function.Consumer;

/**
 * A listener registration object which holds data associated with a listener, such the executor
 * the listener should run on.
 *
 * @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;
    }

    final boolean isRegistered() {
        return mListener != null;
    }

    /**
     * Prevents any listener invocations that happen-after this call.
     */
    public final void unregister() {
        mListener = null;
    }

    /**
     * Executes the given operation for the listener. {@link #onOperationFinished(Consumer)} will
     * always be invoked at some time after this method, regardless of if the listener invocation
     * happens or if the operation fails in some way.
     */
    public final void execute(@NonNull Consumer<TListener> operation) {
        Objects.requireNonNull(operation);
        try {
            mExecutor.execute(() -> {
                try {
                    TListener listener = mListener;
                    if (listener == null) {
                        return;
                    }

                    operation.accept(listener);
                } finally {
                    onOperationFinished(operation);
                }
            });
        } catch (RejectedExecutionException e) {
            onOperationFinished(operation);
        }
    }

    /**
     * Invoked when an operation is finished. This method will always be called once for every call
     * to {@link #execute(Consumer)}, regardless of whether the operation encountered any
     * error or failed to execute in any way. May run on any thread.
     */
    protected void onOperationFinished(@NonNull Consumer<TListener> operation) {}

    @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();
    }
}
+145 −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 com.android.internal.listeners;

import android.annotation.NonNull;
import android.os.RemoteException;
import android.util.ArrayMap;

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

import java.util.Objects;

/**
 * A listener manager which tracks listeners along with their keys. This class enforces proper use
 * of transport objects and ensure unregistration race conditions are handled properly. If listeners
 * should be multiplexed before being sent to the server, see {@link ListenerTransportMultiplexer}
 * instead.
 *
 * @param <TTransport> transport type
 */
public abstract class ListenerTransportManager<TTransport extends ListenerTransport<?>> {

    @GuardedBy("mTransports")
    private final ArrayMap<Object, TTransport> mTransports = new ArrayMap<>();

    /**
     * Should be implemented to register the transport with the server.
     *
     * @see #reregisterWithServer(ListenerTransport, ListenerTransport)
     */
    protected abstract void registerWithServer(TTransport transport) throws RemoteException;

    /**
     * Invoked when the server already has a transport registered for a key, and it is being
     * replaced with a new transport. The default implementation unregisters the old transport, then
     * registers the new transport, but this may be overridden by subclasses in order to reregister
     * more efficiently.
     */
    protected void reregisterWithServer(TTransport oldTransport, TTransport newTransport)
            throws RemoteException {
        unregisterWithServer(oldTransport);
        registerWithServer(newTransport);
    }

    /**
     * Should be implemented to unregister the transport from the server.
     */
    protected abstract void unregisterWithServer(TTransport transport) throws RemoteException;

    /**
     * Adds a new transport with the given key and makes a call to add the transport server side. If
     * a transport already exists with that key, it will be replaced by the new transport and
     * {@link #reregisterWithServer(ListenerTransport, ListenerTransport)} will be invoked to
     * replace the old transport with the new transport server side. If no transport exists with
     * that key, it will be added server side via {@link #registerWithServer(ListenerTransport)}.
     */
    public void registerListener(@NonNull Object key, @NonNull TTransport transport) {
        Objects.requireNonNull(key);
        Objects.requireNonNull(transport);

        synchronized (mTransports) {
            TTransport oldTransport = mTransports.put(key, transport);
            if (oldTransport != null) {
                oldTransport.unregister();
            }

            Preconditions.checkState(transport.isRegistered());

            boolean registered = false;
            try {
                if (oldTransport == null) {
                    registerWithServer(transport);
                } else {
                    reregisterWithServer(oldTransport, transport);
                }
                registered = true;
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            } finally {
                if (!registered) {
                    transport.unregister();
                    mTransports.remove(key);
                }
            }
        }
    }

    /**
     * Removes the transport with the given key, and makes a call to remove the transport server
     * side via {@link #unregisterWithServer(ListenerTransport)}.
     */
    public void unregisterListener(@NonNull Object key) {
        Objects.requireNonNull(key);

        synchronized (mTransports) {
            TTransport transport = mTransports.remove(key);
            if (transport == null) {
                return;
            }

            transport.unregister();
            try {
                unregisterWithServer(transport);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }

    /**
     * Removes the given transport with the given key if such a mapping exists. This only removes
     * the client registration, it does not make any calls to remove the transport server side. The
     * intended use is for when the transport is already removed server side and only client side
     * cleanup is necessary.
     */
    public void removeTransport(@NonNull Object key, @NonNull ListenerTransport<?> transport) {
        Objects.requireNonNull(key);
        Objects.requireNonNull(transport);

        synchronized (mTransports) {
            TTransport typedTransport = mTransports.get(key);
            if (typedTransport != transport) {
                return;
            }

            mTransports.remove(key);
            typedTransport.unregister();
        }
    }
}
+262 −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 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 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.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. If this is not the case, see {@link ListenerTransportManager}
 * instead.
 *
 * 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 unregisters first, then registers the new request, but
     * this may be overridden by subclasses in order to reregister more efficiently.
     */
    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, PrintWriter pw, String[] args) {
        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");

        ArrayMap<Object, RequestListenerTransport<TRequest, TListener>> registrations;
        synchronized (mLock) {
            registrations = mRegistrations;

            ipw.print("service: ");
            if (mServiceRegistered) {
                if (mCurrentRequest == null) {
                    pw.print("request registered");
                } else {
                    pw.print("request registered - " + mCurrentRequest);
                }
            } else {
                pw.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();
        }
    }
}
+45 −0
Original line number Diff line number Diff line
@@ -14,56 +14,32 @@
 * limitations under the License.
 */

package android.location.util.listeners;
package com.android.internal.listeners;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.location.util.listeners.AbstractListenerManager.Registration;

import java.util.concurrent.Executor;

/**
 * A listener manager for client side implementations, where there is no need to deal with any
 * complications other than muxing listeners. Listeners without associated requests are supported
 * without any further work, and listeners with requests must produce muxed requests of the same
 * type.
 * A listener transport with an associated request.
 *
 * @param <TRequest>  request type
 * @param <TListener> listener type
 * @hide
 */
public abstract class ClientListenerManager<TRequest, TListener> extends
        AbstractListenerManager<Object, TRequest, TListener, Registration<TRequest, TListener>,
                TRequest> {
public class RequestListenerTransport<TRequest, TListener> extends ListenerTransport<TListener> {

    /**
     * Adds a new listener with no request.
     */
    public void addListener(@NonNull TListener listener, @NonNull Executor executor) {
        addListener(listener, null, listener, executor);
    }
    private final @Nullable TRequest mRequest;

    /**
     * Adds a new listener with the given request.
     */
    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, rather than using the listener
     * as the key.
     */
    protected void addListener(@NonNull Object key, @Nullable TRequest request,
            @NonNull TListener listener, @NonNull Executor executor) {
        addRegistration(key, new Registration<>(request, executor, listener));
    protected RequestListenerTransport(@Nullable TRequest request, Executor executor,
            TListener listener) {
        super(executor, listener);
        mRequest = request;
    }

    /**
     * Removes the listener with the given key.
     * Returns the request associated with this transport.
     */
    public void removeListener(@NonNull Object key) {
        removeRegistration(key);
    public final @Nullable TRequest getRequest() {
        return mRequest;
    }
}
+194 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading