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

Commit f4648b52 authored by Jean-Michel Trivi's avatar Jean-Michel Trivi Committed by Android (Google) Code Review
Browse files

Merge "Utilities for managing event listeners from AudioService"

parents 9a4d1d6d 91f0baa8
Loading
Loading
Loading
Loading
+59 −200
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.media.AudioAttributes.AttributeSystemUsage;
import android.media.CallbackUtil.ListenerInfo;
import android.media.audiopolicy.AudioPolicy;
import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener;
import android.media.audiopolicy.AudioProductStrategy;
@@ -2900,77 +2901,31 @@ public class AudioManager {
     * List is lazy-initialized on first registration
     */
    @GuardedBy("mModeListenerLock")
    private @Nullable ArrayList<ModeListenerInfo> mModeListeners;
    private @Nullable ArrayList<ListenerInfo<OnModeChangedListener>> mModeListeners;

    @GuardedBy("mModeListenerLock")
    private ModeDispatcherStub mModeDispatcherStub;

    private final class ModeDispatcherStub
            extends IAudioModeDispatcher.Stub {
    private final class ModeDispatcherStub extends IAudioModeDispatcher.Stub {

        @Override
        public void dispatchAudioModeChanged(int mode) {
            // make a shallow copy of listeners so callback is not executed under lock
            final ArrayList<ModeListenerInfo> modeListeners;
            synchronized (mModeListenerLock) {
                if (mModeListeners == null || mModeListeners.size() == 0) {
                    return;
                }
                modeListeners = (ArrayList<ModeListenerInfo>) mModeListeners.clone();
            }
            final long ident = Binder.clearCallingIdentity();
        public void register(boolean register) {
            try {
                for (ModeListenerInfo info : modeListeners) {
                    info.mExecutor.execute(() ->
                            info.mListener.onModeChanged(mode));
                }
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }
    }

    private static class ModeListenerInfo {
        final @NonNull OnModeChangedListener mListener;
        final @NonNull Executor mExecutor;

        ModeListenerInfo(OnModeChangedListener listener, Executor exe) {
            mListener = listener;
            mExecutor = exe;
        }
    }

    @GuardedBy("mModeListenerLock")
    private boolean hasModeListener(OnModeChangedListener listener) {
        return getModeListenerInfo(listener) != null;
    }

    @GuardedBy("mModeListenerLock")
    private @Nullable ModeListenerInfo getModeListenerInfo(
            OnModeChangedListener listener) {
        if (mModeListeners == null) {
            return null;
        }
        for (ModeListenerInfo info : mModeListeners) {
            if (info.mListener == listener) {
                return info;
                if (register) {
                    getService().registerModeDispatcher(this);
                } else {
                    getService().unregisterModeDispatcher(this);
                }
            } catch (RemoteException e) {
                e.rethrowFromSystemServer();
            }
        return null;
        }


    @GuardedBy("mModeListenerLock")
    /**
     * @return true if the listener was removed from the list
     */
    private boolean removeModeListener(OnModeChangedListener listener) {
        final ModeListenerInfo infoToRemove = getModeListenerInfo(listener);
        if (infoToRemove != null) {
            mModeListeners.remove(infoToRemove);
            return true;
        @Override
        @SuppressLint("GuardedBy") // lock applied inside callListeners method
        public void dispatchAudioModeChanged(int mode) {
            CallbackUtil.callListeners(mModeListeners, mModeListenerLock,
                    (listener) -> listener.onModeChanged(mode));
        }
        return false;
    }

    /**
@@ -2982,30 +2937,14 @@ public class AudioManager {
    public void addOnModeChangedListener(
            @NonNull @CallbackExecutor Executor executor,
            @NonNull OnModeChangedListener listener) {
        Objects.requireNonNull(executor);
        Objects.requireNonNull(listener);
        synchronized (mModeListenerLock) {
            if (hasModeListener(listener)) {
                throw new IllegalArgumentException("attempt to call addOnModeChangedListener() "
                        + "on a previously registered listener");
            }
            // lazy initialization of the list of strategy-preferred device listener
            if (mModeListeners == null) {
                mModeListeners = new ArrayList<>();
            }
            final int oldCbCount = mModeListeners.size();
            mModeListeners.add(new ModeListenerInfo(listener, executor));
            if (oldCbCount == 0) {
                // register binder for callbacks
                if (mModeDispatcherStub == null) {
                    mModeDispatcherStub = new ModeDispatcherStub();
                }
                try {
                    getService().registerModeDispatcher(mModeDispatcherStub);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            final Pair<ArrayList<ListenerInfo<OnModeChangedListener>>, ModeDispatcherStub> res =
                    CallbackUtil.addListener("addOnModeChangedListener",
                            executor, listener, mModeListeners, mModeDispatcherStub,
                            () -> new ModeDispatcherStub(),
                            stub -> stub.register(true));
            mModeListeners = res.first;
            mModeDispatcherStub = res.second;
        }
    }

@@ -3015,23 +2954,13 @@ public class AudioManager {
     * @param listener
     */
    public void removeOnModeChangedListener(@NonNull OnModeChangedListener listener) {
        Objects.requireNonNull(listener);
        synchronized (mModeListenerLock) {
            if (!removeModeListener(listener)) {
                throw new IllegalArgumentException("attempt to call removeOnModeChangedListener() "
                        + "on an unregistered listener");
            }
            if (mModeListeners.size() == 0) {
                // unregister binder for callbacks
                try {
                    getService().unregisterModeDispatcher(mModeDispatcherStub);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                } finally {
                    mModeDispatcherStub = null;
                    mModeListeners = null;
                }
            }
            final Pair<ArrayList<ListenerInfo<OnModeChangedListener>>, ModeDispatcherStub> res =
                    CallbackUtil.removeListener("removeOnModeChangedListener",
                            listener, mModeListeners, mModeDispatcherStub,
                            stub -> stub.register(false));
            mModeListeners = res.first;
            mModeDispatcherStub = res.second;
        }
    }

@@ -7646,31 +7575,15 @@ public class AudioManager {
    public void addOnCommunicationDeviceChangedListener(
            @NonNull @CallbackExecutor Executor executor,
            @NonNull OnCommunicationDeviceChangedListener listener) {
        Objects.requireNonNull(executor);
        Objects.requireNonNull(listener);
        synchronized (mCommDevListenerLock) {
            if (hasCommDevListener(listener)) {
                throw new IllegalArgumentException(
                        "attempt to call addOnCommunicationDeviceChangedListener() "
                                + "on a previously registered listener");
            }
            // lazy initialization of the list of strategy-preferred device listener
            if (mCommDevListeners == null) {
                mCommDevListeners = new ArrayList<>();
            }
            final int oldCbCount = mCommDevListeners.size();
            mCommDevListeners.add(new CommDevListenerInfo(listener, executor));
            if (oldCbCount == 0 && mCommDevListeners.size() > 0) {
                // register binder for callbacks
                if (mCommDevDispatcherStub == null) {
                    mCommDevDispatcherStub = new CommunicationDeviceDispatcherStub();
                }
                try {
                    getService().registerCommunicationDeviceDispatcher(mCommDevDispatcherStub);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            final Pair<ArrayList<ListenerInfo<OnCommunicationDeviceChangedListener>>,
                    CommunicationDeviceDispatcherStub> res =
                    CallbackUtil.addListener("addOnCommunicationDeviceChangedListener",
                            executor, listener, mCommDevListeners, mCommDevDispatcherStub,
                            () -> new CommunicationDeviceDispatcherStub(),
                            stub -> stub.register(true));
            mCommDevListeners = res.first;
            mCommDevDispatcherStub = res.second;
        }
    }

@@ -7681,25 +7594,14 @@ public class AudioManager {
     */
    public void removeOnCommunicationDeviceChangedListener(
            @NonNull OnCommunicationDeviceChangedListener listener) {
        Objects.requireNonNull(listener);
        synchronized (mCommDevListenerLock) {
            if (!removeCommDevListener(listener)) {
                throw new IllegalArgumentException(
                        "attempt to call removeOnCommunicationDeviceChangedListener() "
                                + "on an unregistered listener");
            }
            if (mCommDevListeners.size() == 0) {
                // unregister binder for callbacks
                try {
                    getService().unregisterCommunicationDeviceDispatcher(
                            mCommDevDispatcherStub);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                } finally {
                    mCommDevDispatcherStub = null;
                    mCommDevListeners = null;
                }
            }
            final Pair<ArrayList<ListenerInfo<OnCommunicationDeviceChangedListener>>,
                    CommunicationDeviceDispatcherStub> res =
                    CallbackUtil.removeListener("removeOnCommunicationDeviceChangedListener",
                            listener, mCommDevListeners, mCommDevDispatcherStub,
                            stub -> stub.register(false));
            mCommDevListeners = res.first;
            mCommDevDispatcherStub = res.second;
        }
    }

@@ -7709,17 +7611,8 @@ public class AudioManager {
     * List is lazy-initialized on first registration
     */
    @GuardedBy("mCommDevListenerLock")
    private @Nullable ArrayList<CommDevListenerInfo> mCommDevListeners;

    private static class CommDevListenerInfo {
        final @NonNull OnCommunicationDeviceChangedListener mListener;
        final @NonNull Executor mExecutor;

        CommDevListenerInfo(OnCommunicationDeviceChangedListener listener, Executor exe) {
            mListener = listener;
            mExecutor = exe;
        }
    }
    private @Nullable
            ArrayList<ListenerInfo<OnCommunicationDeviceChangedListener>> mCommDevListeners;

    @GuardedBy("mCommDevListenerLock")
    private CommunicationDeviceDispatcherStub mCommDevDispatcherStub;
@@ -7727,59 +7620,25 @@ public class AudioManager {
    private final class CommunicationDeviceDispatcherStub
            extends ICommunicationDeviceDispatcher.Stub {

        @Override
        public void dispatchCommunicationDeviceChanged(int portId) {
            // make a shallow copy of listeners so callback is not executed under lock
            final ArrayList<CommDevListenerInfo> commDevListeners;
            synchronized (mCommDevListenerLock) {
                if (mCommDevListeners == null || mCommDevListeners.size() == 0) {
                    return;
                }
                commDevListeners = (ArrayList<CommDevListenerInfo>) mCommDevListeners.clone();
            }
            AudioDeviceInfo device = getDeviceForPortId(portId, GET_DEVICES_OUTPUTS);
            final long ident = Binder.clearCallingIdentity();
        public void register(boolean register) {
            try {
                for (CommDevListenerInfo info : commDevListeners) {
                    info.mExecutor.execute(() ->
                            info.mListener.onCommunicationDeviceChanged(device));
                }
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }
    }

    @GuardedBy("mCommDevListenerLock")
    private @Nullable CommDevListenerInfo getCommDevListenerInfo(
            OnCommunicationDeviceChangedListener listener) {
        if (mCommDevListeners == null) {
            return null;
        }
        for (CommDevListenerInfo info : mCommDevListeners) {
            if (info.mListener == listener) {
                return info;
                if (register) {
                    getService().registerCommunicationDeviceDispatcher(this);
                } else {
                    getService().unregisterCommunicationDeviceDispatcher(this);
                }
            } catch (RemoteException e) {
                e.rethrowFromSystemServer();
            }
        return null;
        }

    @GuardedBy("mCommDevListenerLock")
    private boolean hasCommDevListener(OnCommunicationDeviceChangedListener listener) {
        return getCommDevListenerInfo(listener) != null;
    }

    @GuardedBy("mCommDevListenerLock")
    /**
     * @return true if the listener was removed from the list
     */
    private boolean removeCommDevListener(OnCommunicationDeviceChangedListener listener) {
        final CommDevListenerInfo infoToRemove = getCommDevListenerInfo(listener);
        if (infoToRemove != null) {
            mCommDevListeners.remove(infoToRemove);
            return true;
        @Override
        @SuppressLint("GuardedBy") // lock applied inside callListeners method
        public void dispatchCommunicationDeviceChanged(int portId) {
            AudioDeviceInfo device = getDeviceForPortId(portId, GET_DEVICES_OUTPUTS);
            CallbackUtil.callListeners(mCommDevListeners, mCommDevListenerLock,
                    (listener) -> listener.onCommunicationDeviceChanged(device));
        }
        return false;
    }

    //---------------------------------------------------------
+224 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.media;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.media.permission.ClearCallingIdentityContext;
import android.media.permission.SafeCloseable;
import android.util.Log;
import android.util.Pair;

import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.Executor;

/**
 * @hide
 * A utility class to implement callback listeners and their management.
 * This is meant to be used for lazily-initialized listener lists and stubs for event reception,
 * typically received from server (e.g. AudioService).
 */

/*package*/ class CallbackUtil {

    private static final String TAG = "CallbackUtil";

    /**
     * Container class to store a listener and associated Executor
     * @param <T> the type of the listener
     */
    static class ListenerInfo<T> {
        final @NonNull T mListener;
        final @NonNull Executor mExecutor;

        ListenerInfo(@NonNull T listener, @NonNull Executor exe) {
            mListener = listener;
            mExecutor = exe;
        }
    }

    /**
     * Finds the listener information (listener + Executor) in a given list of listeners
     * @param listener the listener to find
     * @param listeners the list of listener informations, can be null if not instantiated yet
     * @param <T> the type of the listeners
     * @return null if the listener is not in the given list of listener informations
     */
    static <T> @Nullable ListenerInfo<T> getListenerInfo(
            @NonNull T listener, @Nullable ArrayList<ListenerInfo<T>> listeners) {
        if (listeners == null) {
            return null;
        }
        for (ListenerInfo<T> info : listeners) {
            if (info.mListener == listener) {
                return info;
            }
        }
        return null;
    }

    /**
     * Returns true if the given listener is present in the list of listener informations
     * @param listener the listener to find
     * @param listeners the list of listener informations, can be null if not instantiated yet
     * @param <T> the type of the listeners
     * @return true if the listener is in the list
     */
    static <T> boolean hasListener(@NonNull T listener,
            @Nullable ArrayList<ListenerInfo<T>> listeners) {
        return getListenerInfo(listener, listeners) != null;
    }

    /**
     * Removes the given listener from the list of listener informations
     * @param listener the listener to remove
     * @param listeners the list of listener informations, can be null if not instantiated yet
     * @param <T> the type of the listeners
     * @return true if the listener was found and removed from the list, false otherwise
     */
    static <T> boolean removeListener(@NonNull T listener,
            @Nullable ArrayList<ListenerInfo<T>> listeners) {
        final ListenerInfo<T> infoToRemove = getListenerInfo(listener, listeners);
        if (infoToRemove != null) {
            listeners.remove(infoToRemove);
            return true;
        }
        return false;
    }

    /**
     * Adds a listener and associated Executor in the list of listeners.
     * This method handles the lazy initialization of both the list of listeners and the stub
     * used to receive the events that will be forwarded to the listener, see the returned pair
     * for the updated references.
     * @param methodName the name of the method calling this, for inclusion in the
     *                   string in case of IllegalArgumentException
     * @param executor the Executor for the listener
     * @param listener the listener to add
     * @param listeners the list of listener informations, can be null if not instantiated yet
     * @param dispatchStub the stub that receives the events to be forwarded to the listeners,
     *                    can be null if not instantiated yet
     * @param newStub the function to create a new stub if needed
     * @param registerStub the function for the stub registration if needed
     * @param <T> the type of the listener interface
     * @param <S> the type of the event receiver stub
     * @return a pair of the listener list and the event receiver stub which may have been
     *         initialized if needed (e.g. on the first ever addition of a listener)
     */
    static <T, S> Pair<ArrayList<ListenerInfo<T>>, S> addListener(String methodName,
            @NonNull Executor executor,
            @NonNull T listener,
            @Nullable ArrayList<ListenerInfo<T>> listeners,
            @Nullable S dispatchStub,
            @NonNull java.util.function.Supplier<S> newStub,
            @NonNull java.util.function.Consumer<S> registerStub) {
        Objects.requireNonNull(executor);
        Objects.requireNonNull(listener);

        if (hasListener(listener, listeners)) {
            throw new IllegalArgumentException("attempt to call " + methodName
                    + "on a previously registered listener");
        }
        // lazy initialization of the list of strategy-preferred device listener
        if (listeners == null) {
            listeners = new ArrayList<>();
        }
        if (listeners.size() == 0) {
            // register binder for callbacks
            if (dispatchStub == null) {
                try {
                    dispatchStub = newStub.get();
                } catch (Exception e) {
                    Log.e(TAG, "Exception while creating stub in " + methodName, e);
                    return new Pair<>(null, null);
                }
            }
            registerStub.accept(dispatchStub);
        }
        listeners.add(new ListenerInfo<T>(listener, executor));
        return new Pair(listeners, dispatchStub);
    }

    /**
     * Removes a listener from the list of listeners.
     * This method handles the freeing of both the list of listeners and the stub
     * used to receive the events that will be forwarded to the listener,see the returned pair
     * for the updated references.
     * @param methodName the name of the method calling this, for inclusion in the
     *                   string in case of IllegalArgumentException
     * @param listener the listener to remove
     * @param listeners the list of listener informations, can be null if not instantiated yet
     * @param dispatchStub the stub that receives the events to be forwarded to the listeners,
     *                    can be null if not instantiated yet
     * @param unregisterStub the function to unregister the stub if needed
     * @param <T> the type of the listener interface
     * @param <S> the type of the event receiver stub
     * @return a pair of the listener list and the event receiver stub which may have been
     *         changed if needed (e.g. on the removal of the last listener)
     */
    static <T, S> Pair<ArrayList<ListenerInfo<T>>, S> removeListener(String methodName,
            @NonNull T listener,
            @Nullable ArrayList<ListenerInfo<T>> listeners,
            @Nullable S dispatchStub,
            @NonNull java.util.function.Consumer<S> unregisterStub) {
        Objects.requireNonNull(listener);

        if (!removeListener(listener, listeners)) {
            throw new IllegalArgumentException("attempt to call " + methodName
                    + "on an unregistered listener");
        }
        if (listeners.size() == 0) {
            unregisterStub.accept(dispatchStub);
            return new Pair<>(null, null);
        } else {
            return new Pair<>(listeners, dispatchStub);
        }
    }

    interface CallbackMethod<T> {
        void callbackMethod(T listener);
    }

    /**
     * Exercise the callback of the listeners
     * @param listeners the list of listeners
     * @param listenerLock the lock guarding the list of listeners
     * @param callback the function to call for each listener
     * @param <T>  the type of the listener interface
     */
    static <T> void callListeners(
            @Nullable ArrayList<ListenerInfo<T>> listeners,
            @NonNull Object listenerLock,
            @NonNull CallbackMethod<T> callback) {
        Objects.requireNonNull(listenerLock);
        // make a shallow copy of listeners so callback is not executed under lock
        final ArrayList<ListenerInfo<T>> listenersShallowCopy;
        synchronized (listenerLock) {
            if (listeners == null || listeners.size() == 0) {
                return;
            }
            listenersShallowCopy = (ArrayList<ListenerInfo<T>>) listeners.clone();
        }
        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
            for (ListenerInfo<T> info : listenersShallowCopy) {
                info.mExecutor.execute(() -> callback.callbackMethod(info.mListener));
            }
        }

    }
}
+72 −227

File changed.

Preview size limit exceeded, changes collapsed.