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

Commit d92cb04a authored by Alex Shabalin's avatar Alex Shabalin Committed by Alexandr Shabalin
Browse files

Run InfoMediaManager callbacks on a single thread.

- Creates an `Executor` and a `Handler` that run on the same thread.
- Terminates the handler thread when the last callback is unregistered.

Before this change `InfoMediaManager` used to have 2 threads for
 callbacks from 2 classes:
- For `MediaRouter2` it used a background thread executor.
- For `MediaController` it used a default handler which is a main thread
handler.

Fix: 425977951
Flag: EXEMPT bugfix
Test: on a physical device
Change-Id: I6bc89b54882b62fe876f055032392f8d72a9df67
parent cf894ee5
Loading
Loading
Loading
Loading
+48 −25
Original line number Diff line number Diff line
@@ -41,6 +41,8 @@ import android.media.SuggestedDeviceInfo;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
@@ -56,7 +58,6 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -66,7 +67,8 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Executor;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -132,7 +134,7 @@ public abstract class InfoMediaManager {
    @NonNull protected final Context mContext;
    @NonNull protected final String mPackageName;
    @NonNull protected final UserHandle mUserHandle;
    private final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
    private final Set<MediaDeviceCallback> mCallbacks = new CopyOnWriteArraySet<>();
    @GuardedBy("mLock")
    private MediaDevice mCurrentConnectedDevice;
    private MediaController mMediaController;
@@ -147,6 +149,9 @@ public abstract class InfoMediaManager {

    private final MediaController.Callback mMediaControllerCallback = new MediaControllerCallback();

    @GuardedBy("mLock")
    @Nullable private HandlerThread mCallbackHandlerThread;

    /* package */ InfoMediaManager(
            @NonNull Context context,
            @NonNull String packageName,
@@ -231,7 +236,7 @@ public abstract class InfoMediaManager {

    protected abstract void startScanOnRouter();

    protected abstract void registerRouter();
    protected abstract void registerRouter(Executor executor);

    protected abstract void unregisterRouter();

@@ -334,22 +339,29 @@ public abstract class InfoMediaManager {
     * updates.
     */
    public final void registerCallback(@NonNull MediaDeviceCallback callback) {
        boolean wasEmpty = mCallbacks.isEmpty();
        if (!mCallbacks.contains(callback)) {
            mCallbacks.add(callback);
            if (wasEmpty) {
        boolean firstCallbackAdded;
        Handler callbackHandler = null;

        synchronized (mLock) {
            firstCallbackAdded = mCallbacks.isEmpty();
            mCallbacks.add(callback);
            if (firstCallbackAdded) {
                mMediaDevices.clear();
                mCallbackHandlerThread = new HandlerThread("callbackHandlerThread");
                mCallbackHandlerThread.start();
                callbackHandler = new Handler(mCallbackHandlerThread.getLooper());
            }
        }
                registerRouter();

        if (firstCallbackAdded) {
            registerRouter(callbackHandler::post);
            if (mMediaController != null) {
                    mMediaController.registerCallback(mMediaControllerCallback);
                mMediaController.registerCallback(mMediaControllerCallback, callbackHandler);
            }
            updateRouteListingPreference();
            refreshDevices();
        }
    }
    }

    /**
     * Unregisters the specified {@code callback}.
@@ -357,7 +369,22 @@ public abstract class InfoMediaManager {
     * @see #registerCallback(MediaDeviceCallback)
     */
    public final void unregisterCallback(@NonNull MediaDeviceCallback callback) {
        if (mCallbacks.remove(callback) && mCallbacks.isEmpty()) {
        boolean lastCallbackRemoved;
        HandlerThread callbackThread = null;

        synchronized (mLock) {
            mCallbacks.remove(callback);
            lastCallbackRemoved = mCallbacks.isEmpty();
            if (lastCallbackRemoved && mCallbackHandlerThread != null) {
                callbackThread = mCallbackHandlerThread;
                mCallbackHandlerThread = null;
            }
        }

        if (lastCallbackRemoved) {
            if (callbackThread != null) {
                callbackThread.quitSafely();
            }
            if (mMediaController != null) {
                mMediaController.unregisterCallback(mMediaControllerCallback);
            }
@@ -372,29 +399,25 @@ public abstract class InfoMediaManager {
                Log.d(TAG, device.toString());
            }
        }
        for (MediaDeviceCallback callback : getCallbacks()) {
        for (MediaDeviceCallback callback : mCallbacks) {
            callback.onDeviceListAdded(new ArrayList<>(devices));
        }
    }

    private void dispatchConnectedDeviceChanged(String id) {
        Log.i(TAG, "dispatchConnectedDeviceChanged(), id = " + id);
        for (MediaDeviceCallback callback : getCallbacks()) {
        for (MediaDeviceCallback callback : mCallbacks) {
            callback.onConnectedDeviceChanged(id);
        }
    }

    protected void dispatchOnRequestFailed(int reason) {
        Log.i(TAG, "dispatchOnRequestFailed(), reason = " + reason);
        for (MediaDeviceCallback callback : getCallbacks()) {
        for (MediaDeviceCallback callback : mCallbacks) {
            callback.onRequestFailed(reason);
        }
    }

    private Collection<MediaDeviceCallback> getCallbacks() {
        return new CopyOnWriteArrayList<>(mCallbacks);
    }

    /**
     * Get current device that played media.
     * @return MediaDevice
@@ -715,7 +738,7 @@ public abstract class InfoMediaManager {
    private void dispatchOnSuggestedDeviceUpdated() {
        SuggestedDeviceState state = getSuggestedDevice();
        Log.i(TAG, "dispatchOnSuggestedDeviceUpdated(), state: " + state);
        for (MediaDeviceCallback callback : getCallbacks()) {
        for (MediaDeviceCallback callback : mCallbacks) {
            callback.onSuggestedDeviceUpdated(state);
        }
    }
+2 −1
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;

/**
 * No-op implementation of {@link InfoMediaManager}.
@@ -73,7 +74,7 @@ import java.util.List;
    }

    @Override
    protected void registerRouter() {
    protected void registerRouter(Executor executor) {
        // Do nothing.
    }

+6 −8
Original line number Diff line number Diff line
@@ -47,7 +47,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.stream.Collectors;

@@ -63,7 +62,6 @@ public final class RouterInfoMediaManager extends InfoMediaManager {
    @VisibleForTesting
    MediaRouter2Manager mRouterManager;

    private final Executor mExecutor = Executors.newSingleThreadExecutor();
    @VisibleForTesting
    final RouteCallback mRouteCallback = new RouteCallback();
    @VisibleForTesting
@@ -163,20 +161,20 @@ public final class RouterInfoMediaManager extends InfoMediaManager {
    }

    @Override
    protected void registerRouter() {
        mRouter.registerRouteCallback(mExecutor, mRouteCallback, RouteDiscoveryPreference.EMPTY);
    protected void registerRouter(Executor executor) {
        mRouter.registerRouteCallback(executor, mRouteCallback, RouteDiscoveryPreference.EMPTY);
        mRouter.registerRouteListingPreferenceUpdatedCallback(
                mExecutor, mRouteListingPreferenceCallback);
                executor, mRouteListingPreferenceCallback);
        mRouter.registerDeviceSuggestionsUpdatesCallback(
                mExecutor, mDeviceSuggestionsUpdatesCallback);
                executor, mDeviceSuggestionsUpdatesCallback);
        if (Flags.enableSuggestedDeviceApi()) {
            for (Map.Entry<String, List<SuggestedDeviceInfo>> entry :
                    mRouter.getDeviceSuggestions().entrySet()) {
                notifyDeviceSuggestionUpdated(entry.getKey(), entry.getValue());
            }
        }
        mRouter.registerTransferCallback(mExecutor, mTransferCallback);
        mRouter.registerControllerCallback(mExecutor, mControllerCallback);
        mRouter.registerTransferCallback(executor, mTransferCallback);
        mRouter.registerControllerCallback(executor, mControllerCallback);
    }

    @Override