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

Commit 985485b5 authored by Derek Jedral's avatar Derek Jedral
Browse files

Add methods for suggested devices called from UMO

Add methods to connect to a suggested device, and also refactor code
around determining the icon for the device type so UMO can surface the
proper icon.

Flag: com.android.media.flags.enable_suggested_device_api
Test: atest
Bug: 393216614
Change-Id: I1da4bc84eb5986287cdef703d7cf69c0e4b1ab68
parent 30f73993
Loading
Loading
Loading
Loading
+8 −2
Original line number Diff line number Diff line
@@ -54,12 +54,18 @@ public class ComplexMediaDevice extends MediaDevice {

    @Override
    public Drawable getIcon() {
        return mContext.getDrawable(R.drawable.ic_media_avr_device);
        return getIcon(mContext);
    }

    @Override
    public Drawable getIconWithoutBackground() {
        return mContext.getDrawable(R.drawable.ic_media_avr_device);
        return getIcon(mContext);
    }

    /** Gets the drawable associated with the complex media device. */
    @NonNull
    public static Drawable getIcon(Context context) {
        return context.getDrawable(R.drawable.ic_media_avr_device);
    }

    @Override
+7 −1
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import android.graphics.drawable.Drawable;
import android.media.MediaRoute2Info;
import android.media.RouteListingPreference;

import androidx.annotation.DrawableRes;
import androidx.annotation.VisibleForTesting;

import com.android.settingslib.R;
@@ -75,9 +76,14 @@ public class InfoMediaDevice extends MediaDevice {

    @VisibleForTesting
    @SuppressWarnings("NewApi")
    @DrawableRes
    int getDrawableResIdByType() {
        return getDrawableResIdByType(mRouteInfo.getType());
    }

    static int getDrawableResIdByType(@MediaRoute2Info.Type int type) {
        int resId;
        switch (mRouteInfo.getType()) {
        switch (type) {
            case TYPE_GROUP:
                resId = R.drawable.ic_media_group_device;
                break;
+111 −44
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.media.MediaRoute2Info;
import android.media.RouteListingPreference;
import android.media.RoutingSessionInfo;
@@ -69,6 +70,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;

import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.R;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.flags.Flags;
@@ -161,6 +163,12 @@ public abstract class InfoMediaManager {
            return mConnectionState;
        }

        /** Gets the drawable associated with the suggested device type. */
        @NonNull
        public Drawable getIcon(Context context) {
            return getDrawableForSuggestion(context, this);
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
@@ -681,6 +689,11 @@ public abstract class InfoMediaManager {
        return getActiveRoutingSession().getName();
    }

    @Nullable
    public SuggestedDeviceState getSuggestedDevice() {
        return mSuggestedDeviceState;
    }

    @TargetApi(Build.VERSION_CODES.R)
    boolean shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo) {
        return sessionInfo.isSystemSession() // System sessions are not remote
@@ -717,6 +730,11 @@ public abstract class InfoMediaManager {
                }
            }
            if (newSuggestedDeviceState == null) {
                if (topSuggestion
                        .getRouteId()
                        .equals(previousState.getSuggestedDeviceInfo().getRouteId())) {
                    return;
                }
                newSuggestedDeviceState = new SuggestedDeviceState(topSuggestion);
            }
        }
@@ -862,6 +880,59 @@ public abstract class InfoMediaManager {
    void addMediaDevice(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo activeSession) {
        final int deviceType = route.getType();
        MediaDevice mediaDevice = null;
        if (isInfoMediaDevice(deviceType)) {
            mediaDevice =
                    new InfoMediaDevice(mContext, route, mPreferenceItemMap.get(route.getId()));

        } else if (isPhoneMediaDevice(deviceType)) {
            mediaDevice =
                    new PhoneMediaDevice(
                            mContext, route, mPreferenceItemMap.getOrDefault(route.getId(), null));

        } else if (isBluetoothMediaDevice(deviceType)) {
            if (route.getAddress() == null) {
                Log.e(TAG, "Ignoring bluetooth route with no set address: " + route);
            } else {
                final BluetoothDevice device =
                        BluetoothAdapter.getDefaultAdapter().getRemoteDevice(route.getAddress());
                final CachedBluetoothDevice cachedDevice =
                        mBluetoothManager.getCachedDeviceManager().findDevice(device);
                if (cachedDevice != null) {
                    mediaDevice =
                            new BluetoothMediaDevice(
                                    mContext,
                                    cachedDevice,
                                    route,
                                    mPreferenceItemMap.getOrDefault(route.getId(), null));
                }
            }
        } else if (isComplexMediaDevice(deviceType)) {
            mediaDevice =
                    new ComplexMediaDevice(mContext, route, mPreferenceItemMap.get(route.getId()));

        } else {
            Log.w(TAG, "addMediaDevice() unknown device type : " + deviceType);
        }
        if (mediaDevice != null) {
            if (activeSession.getSelectedRoutes().contains(route.getId())) {
                setDeviceState(mediaDevice, STATE_SELECTED);
            }
            mMediaDevices.add(mediaDevice);
        }
    }

    /** Updates the state of the device and updates liteners of the updated device state. */
    public void setDeviceState(MediaDevice device, @LocalMediaManager.MediaDeviceState int state) {
        if (device.getState() == state) {
            return;
        }
        device.setState(state);
        if (device.isSuggestedDevice()) {
            updateDeviceSuggestion();
        }
    }

    private static boolean isInfoMediaDevice(int deviceType) {
        switch (deviceType) {
            case TYPE_UNKNOWN:
            case TYPE_REMOTE_TV:
@@ -874,12 +945,14 @@ public abstract class InfoMediaManager {
            case TYPE_REMOTE_CAR:
            case TYPE_REMOTE_SMARTWATCH:
            case TYPE_REMOTE_SMARTPHONE:
                mediaDevice =
                        new InfoMediaDevice(
                                mContext,
                                route,
                                mPreferenceItemMap.get(route.getId()));
                break;
                return true;
            default:
                return false;
        }
    }

    private static boolean isPhoneMediaDevice(int deviceType) {
        switch (deviceType) {
            case TYPE_BUILTIN_SPEAKER:
            case TYPE_USB_DEVICE:
            case TYPE_USB_HEADSET:
@@ -893,51 +966,45 @@ public abstract class InfoMediaManager {
            case TYPE_AUX_LINE:
            case TYPE_WIRED_HEADSET:
            case TYPE_WIRED_HEADPHONES:
                mediaDevice =
                        new PhoneMediaDevice(
                                mContext,
                                route,
                                mPreferenceItemMap.getOrDefault(route.getId(), null));
                break;
                return true;
            default:
                return false;
        }
    }

    private static boolean isBluetoothMediaDevice(int deviceType) {
        switch (deviceType) {
            case TYPE_HEARING_AID:
            case TYPE_BLUETOOTH_A2DP:
            case TYPE_BLE_HEADSET:
                if (route.getAddress() == null) {
                    Log.e(TAG, "Ignoring bluetooth route with no set address: " + route);
                    break;
                return true;
            default:
                return false;
        }
                final BluetoothDevice device =
                        BluetoothAdapter.getDefaultAdapter()
                                .getRemoteDevice(route.getAddress());
                final CachedBluetoothDevice cachedDevice =
                        mBluetoothManager.getCachedDeviceManager().findDevice(device);
                if (cachedDevice != null) {
                    mediaDevice =
                            new BluetoothMediaDevice(
                                    mContext,
                                    cachedDevice,
                                    route,
                                    mPreferenceItemMap.getOrDefault(route.getId(), null));
    }
                break;
            case TYPE_REMOTE_AUDIO_VIDEO_RECEIVER:
                mediaDevice =
                        new ComplexMediaDevice(
                                mContext,
                                route,
                                mPreferenceItemMap.get(route.getId()));
                break;
            default:
                Log.w(TAG, "addMediaDevice() unknown device type : " + deviceType);
                break;

    private static boolean isComplexMediaDevice(int deviceType) {
        return deviceType == TYPE_REMOTE_AUDIO_VIDEO_RECEIVER;
    }

        if (mediaDevice != null) {
            if (activeSession.getSelectedRoutes().contains(route.getId())) {
                mediaDevice.setState(STATE_SELECTED);
    private static Drawable getDrawableForSuggestion(
            Context context, SuggestedDeviceState suggestion) {
        if (suggestion.getConnectionState()
                == LocalMediaManager.MediaDeviceState.STATE_CONNECTING_FAILED) {
            return context.getDrawable(android.R.drawable.ic_info);
        }
            mMediaDevices.add(mediaDevice);
        int deviceType = suggestion.getSuggestedDeviceInfo().getType();
        if (isInfoMediaDevice(deviceType)) {
            return context.getDrawable(InfoMediaDevice.getDrawableResIdByType(deviceType));
        }
        if (isPhoneMediaDevice(deviceType)) {
            return context.getDrawable(
                    new DeviceIconUtil(context).getIconResIdFromMediaRouteType(deviceType));
        }
        if (isBluetoothMediaDevice(deviceType)) {
            return ComplexMediaDevice.getIcon(context);
        }
        return context.getDrawable(R.drawable.ic_media_speaker_device);
    }

    @RequiresApi(34)
+8 −2
Original line number Diff line number Diff line
@@ -62,6 +62,8 @@ public final class InputRouteManager {

    private final AudioManager mAudioManager;

    private final InfoMediaManager mInfoMediaManager;

    @VisibleForTesting final List<MediaDevice> mInputMediaDevices = new CopyOnWriteArrayList<>();

    private @AudioDeviceType int mSelectedInputDeviceType;
@@ -107,9 +109,13 @@ public final class InputRouteManager {
                }
            };

    public InputRouteManager(@NonNull Context context, @NonNull AudioManager audioManager) {
    public InputRouteManager(
            @NonNull Context context,
            @NonNull AudioManager audioManager,
            @NonNull InfoMediaManager infoMediaManager) {
        mContext = context;
        mAudioManager = audioManager;
        mInfoMediaManager = infoMediaManager;
        Handler handler = new Handler(context.getMainLooper());

        mAudioManager.registerAudioDeviceCallback(mAudioDeviceCallback, handler);
@@ -210,7 +216,7 @@ public final class InputRouteManager {
                            getProductNameFromAudioDeviceInfo(info));
            if (mediaDevice != null) {
                if (info.getType() == mSelectedInputDeviceType) {
                    mediaDevice.setState(STATE_SELECTED);
                    mInfoMediaManager.setDeviceState(mediaDevice, STATE_SELECTED);
                }
                mInputMediaDevices.add(mediaDevice);
            }
+112 −8
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.media.AudioDeviceAttributes;
import android.media.AudioManager;
import android.media.RoutingSessionInfo;
import android.os.Build;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;

@@ -35,6 +36,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.bluetooth.A2dpProfile;
import com.android.settingslib.bluetooth.BluetoothCallback;
@@ -44,12 +46,14 @@ import com.android.settingslib.bluetooth.HearingAidProfile;
import com.android.settingslib.bluetooth.LeAudioProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfile;
import com.android.settingslib.media.InfoMediaManager.SuggestedDeviceState;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;

/**
@@ -98,6 +102,13 @@ public class LocalMediaManager implements BluetoothCallback {
    @VisibleForTesting
    DeviceAttributeChangeCallback mDeviceAttributeChangeCallback =
            new DeviceAttributeChangeCallback();

    @GuardedBy("mMediaDevicesLock")
    @Nullable
    ConnectingSuggestedDeviceState mConnectingSuggestedDeviceState;

    @VisibleForTesting Handler mConnectSuggestedDeviceHandler;

    @VisibleForTesting
    BluetoothAdapter mBluetoothAdapter;

@@ -140,6 +151,7 @@ public class LocalMediaManager implements BluetoothCallback {
                LocalBluetoothManager.getInstance(context, /* onInitCallback= */ null);
        mAudioManager = context.getSystemService(AudioManager.class);
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        mConnectSuggestedDeviceHandler = new Handler(context.getMainLooper());
        if (mLocalBluetoothManager == null) {
            Log.e(TAG, "Bluetooth is not supported on this device");
            return;
@@ -169,6 +181,7 @@ public class LocalMediaManager implements BluetoothCallback {
        mPackageName = packageName;
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        mAudioManager = context.getSystemService(AudioManager.class);
        mConnectSuggestedDeviceHandler = new Handler(context.getMainLooper());
    }

    /**
@@ -187,7 +200,7 @@ public class LocalMediaManager implements BluetoothCallback {
                    ((BluetoothMediaDevice) device).getCachedDevice();
            if (!cachedDevice.isConnected() && !cachedDevice.isBusy()) {
                mOnTransferBluetoothDevice = connectDevice;
                device.setState(MediaDeviceState.STATE_CONNECTING);
                mInfoMediaManager.setDeviceState(device, MediaDeviceState.STATE_CONNECTING);
                cachedDevice.connect();
                return true;
            }
@@ -198,11 +211,53 @@ public class LocalMediaManager implements BluetoothCallback {
            return false;
        }

        device.setState(MediaDeviceState.STATE_CONNECTING);
        mInfoMediaManager.setDeviceState(device, MediaDeviceState.STATE_CONNECTING);
        mInfoMediaManager.connectToDevice(device);
        return true;
    }

    /**
     * Connects to a suggested device. If the device is not already scanned, a scan will be started
     * to attempt to discover the device.
     *
     * @param suggestion the suggested device to connect to.
     */
    public void connectSuggestedDevice(SuggestedDeviceState suggestion) {
        synchronized (mMediaDevicesLock) {
            if (suggestion == null || mConnectingSuggestedDeviceState != null) {
                return;
            }
            SuggestedDeviceState currentSuggestion = mInfoMediaManager.getSuggestedDevice();
            if (!Objects.equals(suggestion, currentSuggestion)) {
                return;
            }
            for (MediaDevice device : mMediaDevices) {
                if (suggestion.getSuggestedDeviceInfo().getRouteId().equals(device.getId())) {
                    connectDevice(device);
                    return;
                }
            }
            mConnectingSuggestedDeviceState =
                    new ConnectingSuggestedDeviceState(
                            currentSuggestion, mConnectSuggestedDeviceHandler);
            mConnectingSuggestedDeviceState.tryConnect();
        }
    }

    private boolean connectToDeviceIfConnectionPending(MediaDevice device) {
        synchronized (mMediaDevicesLock) {
            if (mConnectingSuggestedDeviceState != null
                    && mConnectingSuggestedDeviceState
                            .mSuggestedDeviceState
                            .getSuggestedDeviceInfo()
                            .getRouteId()
                            .equals(device.getId())) {
                return connectDevice(device);
            }
            return false;
        }
    }

    void dispatchSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state) {
        for (DeviceCallback callback : getCallbacks()) {
            callback.onSelectedDeviceStateChanged(device, state);
@@ -327,7 +382,7 @@ public class LocalMediaManager implements BluetoothCallback {
     * @return If add device successful return {@code true}, otherwise return {@code false}
     */
    public boolean addDeviceToPlayMedia(MediaDevice device) {
        device.setState(MediaDeviceState.STATE_GROUPING);
        mInfoMediaManager.setDeviceState(device, MediaDeviceState.STATE_GROUPING);
        return mInfoMediaManager.addDeviceToPlayMedia(device);
    }

@@ -338,7 +393,7 @@ public class LocalMediaManager implements BluetoothCallback {
     * @return If device stop successful return {@code true}, otherwise return {@code false}
     */
    public boolean removeDeviceFromPlayMedia(MediaDevice device) {
        device.setState(MediaDeviceState.STATE_GROUPING);
        mInfoMediaManager.setDeviceState(device, MediaDeviceState.STATE_GROUPING);
        return mInfoMediaManager.removeDeviceFromPlayMedia(device);
    }

@@ -556,7 +611,8 @@ public class LocalMediaManager implements BluetoothCallback {
            dispatchDeviceListUpdate();
            if (mOnTransferBluetoothDevice != null && mOnTransferBluetoothDevice.isConnected()) {
                connectDevice(mOnTransferBluetoothDevice);
                mOnTransferBluetoothDevice.setState(MediaDeviceState.STATE_CONNECTED);
                mInfoMediaManager.setDeviceState(
                        mOnTransferBluetoothDevice, MediaDeviceState.STATE_CONNECTED);
                dispatchSelectedDeviceStateChanged(mOnTransferBluetoothDevice,
                        MediaDeviceState.STATE_CONNECTED);
                mOnTransferBluetoothDevice = null;
@@ -671,7 +727,7 @@ public class LocalMediaManager implements BluetoothCallback {

            mCurrentConnectedDevice = connectDevice;
            if (connectDevice != null) {
                connectDevice.setState(MediaDeviceState.STATE_CONNECTED);
                mInfoMediaManager.setDeviceState(connectDevice, MediaDeviceState.STATE_CONNECTED);

                dispatchSelectedDeviceStateChanged(mCurrentConnectedDevice,
                        MediaDeviceState.STATE_CONNECTED);
@@ -683,7 +739,8 @@ public class LocalMediaManager implements BluetoothCallback {
            synchronized (mMediaDevicesLock) {
                for (MediaDevice device : mMediaDevices) {
                    if (device.getState() == MediaDeviceState.STATE_CONNECTING) {
                        device.setState(MediaDeviceState.STATE_CONNECTING_FAILED);
                        mInfoMediaManager.setDeviceState(
                                device, MediaDeviceState.STATE_CONNECTING_FAILED);
                    }
                }
            }
@@ -782,11 +839,58 @@ public class LocalMediaManager implements BluetoothCallback {
                    .isBusy()
                    && !mOnTransferBluetoothDevice.isConnected()) {
                // Failed to connect
                mOnTransferBluetoothDevice.setState(MediaDeviceState.STATE_CONNECTING_FAILED);
                mInfoMediaManager.setDeviceState(
                        mOnTransferBluetoothDevice, MediaDeviceState.STATE_CONNECTING_FAILED);
                mOnTransferBluetoothDevice = null;
                dispatchOnRequestFailed(REASON_UNKNOWN_ERROR);
            }
            dispatchDeviceAttributesChanged();
        }
    }

    private class ConnectingSuggestedDeviceState {
        private static final int SCAN_DURATION_MS = 10000;

        @NonNull final SuggestedDeviceState mSuggestedDeviceState;
        @NonNull final Handler mConnectSuggestedDeviceHandler;
        @NonNull final DeviceCallback mDeviceCallback;
        @NonNull final Runnable mConnectionAttemptFinishedRunnable;

        ConnectingSuggestedDeviceState(SuggestedDeviceState suggestedDeviceState, Handler handler) {
            mSuggestedDeviceState = suggestedDeviceState;
            mConnectSuggestedDeviceHandler = handler;
            mDeviceCallback =
                    new DeviceCallback() {
                        @Override
                        public void onDeviceListUpdate(List<MediaDevice> mediaDevices) {
                            for (MediaDevice mediaDevice : mediaDevices) {
                                if (connectToDeviceIfConnectionPending(mediaDevice)) {
                                    mConnectSuggestedDeviceHandler.removeCallbacks(
                                            mConnectionAttemptFinishedRunnable);
                                    mConnectionAttemptFinishedRunnable.run();
                                    break;
                                }
                            }
                        }
                    };
            mConnectionAttemptFinishedRunnable =
                    new Runnable() {
                        @Override
                        public void run() {
                            synchronized (mMediaDevicesLock) {
                                mConnectingSuggestedDeviceState = null;
                            }
                            unregisterCallback(mDeviceCallback);
                            stopScan();
                        }
                    };
        }

        void tryConnect() {
            registerCallback(mDeviceCallback);
            startScan();
            mConnectSuggestedDeviceHandler.postDelayed(
                    mConnectionAttemptFinishedRunnable, SCAN_DURATION_MS);
        }
    }
}
Loading