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

Commit 98e86501 authored by Soonil Nagarkar's avatar Soonil Nagarkar Committed by Android (Google) Code Review
Browse files

Merge "Refactor and reorganize listener multiplexers"

parents 392bbe14 490c46ca
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