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

Commit 7a488993 authored by Alexandr Shabalin's avatar Alexandr Shabalin Committed by Android (Google) Code Review
Browse files

Merge changes I28e35f0b,I73e1ad30,Id990d397 into main

* changes:
  Convert SuggestedDeviceState to a Kotlin data class.
  Don't set the AV receiver icon for a BT suggested device.
  Respect CONNECTING device state from the MediaRouter.
parents 2a72e610 56fe34c8
Loading
Loading
Loading
Loading
+7 −165
Original line number Diff line number Diff line
@@ -15,34 +15,7 @@
 */
package com.android.settingslib.media;

import static android.media.MediaRoute2Info.TYPE_AUX_LINE;
import static android.media.MediaRoute2Info.TYPE_BLE_HEADSET;
import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
import static android.media.MediaRoute2Info.TYPE_DOCK;
import static android.media.MediaRoute2Info.TYPE_GROUP;
import static android.media.MediaRoute2Info.TYPE_HDMI;
import static android.media.MediaRoute2Info.TYPE_HDMI_ARC;
import static android.media.MediaRoute2Info.TYPE_HDMI_EARC;
import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
import static android.media.MediaRoute2Info.TYPE_LINE_ANALOG;
import static android.media.MediaRoute2Info.TYPE_LINE_DIGITAL;
import static android.media.MediaRoute2Info.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER;
import static android.media.MediaRoute2Info.TYPE_REMOTE_CAR;
import static android.media.MediaRoute2Info.TYPE_REMOTE_COMPUTER;
import static android.media.MediaRoute2Info.TYPE_REMOTE_GAME_CONSOLE;
import static android.media.MediaRoute2Info.TYPE_REMOTE_SMARTPHONE;
import static android.media.MediaRoute2Info.TYPE_REMOTE_SMARTWATCH;
import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
import static android.media.MediaRoute2Info.TYPE_REMOTE_TABLET;
import static android.media.MediaRoute2Info.TYPE_REMOTE_TABLET_DOCKED;
import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
import static android.media.MediaRoute2Info.TYPE_UNKNOWN;
import static android.media.MediaRoute2Info.TYPE_USB_ACCESSORY;
import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
import static android.media.MediaRoute2Info.CONNECTION_STATE_CONNECTING;
import static android.media.session.MediaController.PlaybackInfo;

import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_CONNECTED;
@@ -50,13 +23,16 @@ import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.S
import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_CONNECTING_FAILED;
import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED;
import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
import static com.android.settingslib.media.MediaDeviceUtilKt.isBluetoothMediaDevice;
import static com.android.settingslib.media.MediaDeviceUtilKt.isComplexMediaDevice;
import static com.android.settingslib.media.MediaDeviceUtilKt.isInfoMediaDevice;
import static com.android.settingslib.media.MediaDeviceUtilKt.isPhoneMediaDevice;

import android.annotation.TargetApi;
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.RoutingChangeInfo;
@@ -76,7 +52,6 @@ import androidx.annotation.RequiresApi;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.R;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;

@@ -141,67 +116,6 @@ public abstract class InfoMediaManager {
        ;
    }

    /**
     * Wrapper class around SuggestedDeviceInfo and the corresponding connection state of the
     * suggestion.
     */
    public static class SuggestedDeviceState {
        private final SuggestedDeviceInfo mSuggestedDeviceInfo;
        private final @LocalMediaManager.MediaDeviceState int mConnectionState;

        @VisibleForTesting
        SuggestedDeviceState(@NonNull SuggestedDeviceInfo suggestedDeviceInfo) {
            mSuggestedDeviceInfo = suggestedDeviceInfo;
            mConnectionState = STATE_DISCONNECTED;
        }

        @VisibleForTesting
        SuggestedDeviceState(
                @NonNull SuggestedDeviceInfo suggestedDeviceInfo,
                @LocalMediaManager.MediaDeviceState int state) {
            mSuggestedDeviceInfo = suggestedDeviceInfo;
            mConnectionState = state;
        }

        @NonNull
        public SuggestedDeviceInfo getSuggestedDeviceInfo() {
            return mSuggestedDeviceInfo;
        }

        public @LocalMediaManager.MediaDeviceState int getConnectionState() {
            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) {
                return true;
            }
            if (!(obj instanceof SuggestedDeviceState)) {
                return false;
            }
            return Objects.equals(
                            mSuggestedDeviceInfo, ((SuggestedDeviceState) obj).mSuggestedDeviceInfo)
                    && Objects.equals(
                            mConnectionState, ((SuggestedDeviceState) obj).mConnectionState);
        }

        @Override
        public int hashCode() {
            return Objects.hash(mSuggestedDeviceInfo, mConnectionState);
        }

        @Override
        public String toString() {
            return "info: " + mSuggestedDeviceInfo + " state " + mConnectionState;
        }
    }

    /** Checked exception that signals the specified package is not present in the system. */
    public static class PackageNotAvailableException extends Exception {
@@ -947,6 +861,8 @@ public abstract class InfoMediaManager {
        if (mediaDevice != null) {
            if (mediaDevice.isSelected()) {
                mediaDevice.setState(STATE_SELECTED);
            } else if (route.getConnectionState() == CONNECTION_STATE_CONNECTING) {
                mediaDevice.setState(STATE_CONNECTING);
            }
            mMediaDevices.add(mediaDevice);
        }
@@ -1016,80 +932,6 @@ public abstract class InfoMediaManager {
        }
    }

    private static boolean isInfoMediaDevice(int deviceType) {
        switch (deviceType) {
            case TYPE_UNKNOWN:
            case TYPE_REMOTE_TV:
            case TYPE_REMOTE_SPEAKER:
            case TYPE_GROUP:
            case TYPE_REMOTE_TABLET:
            case TYPE_REMOTE_TABLET_DOCKED:
            case TYPE_REMOTE_COMPUTER:
            case TYPE_REMOTE_GAME_CONSOLE:
            case TYPE_REMOTE_CAR:
            case TYPE_REMOTE_SMARTWATCH:
            case TYPE_REMOTE_SMARTPHONE:
                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:
            case TYPE_USB_ACCESSORY:
            case TYPE_DOCK:
            case TYPE_HDMI:
            case TYPE_HDMI_ARC:
            case TYPE_HDMI_EARC:
            case TYPE_LINE_DIGITAL:
            case TYPE_LINE_ANALOG:
            case TYPE_AUX_LINE:
            case TYPE_WIRED_HEADSET:
            case TYPE_WIRED_HEADPHONES:
                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:
                return true;
            default:
                return false;
        }
    }

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

    private static Drawable getDrawableForSuggestion(
            Context context, SuggestedDeviceState suggestion) {
        if (suggestion.getConnectionState() == STATE_CONNECTING_FAILED) {
            return context.getDrawable(android.R.drawable.ic_info);
        }
        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)
    static class Api34Impl {
        @DoNotInline
+0 −1
Original line number Diff line number Diff line
@@ -49,7 +49,6 @@ 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;
+71 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.settingslib.media

import android.media.MediaRoute2Info

fun isInfoMediaDevice(deviceType: Int): Boolean {
  return when (deviceType) {
    MediaRoute2Info.TYPE_UNKNOWN,
    MediaRoute2Info.TYPE_REMOTE_TV,
    MediaRoute2Info.TYPE_REMOTE_SPEAKER,
    MediaRoute2Info.TYPE_GROUP,
    MediaRoute2Info.TYPE_REMOTE_TABLET,
    MediaRoute2Info.TYPE_REMOTE_TABLET_DOCKED,
    MediaRoute2Info.TYPE_REMOTE_COMPUTER,
    MediaRoute2Info.TYPE_REMOTE_GAME_CONSOLE,
    MediaRoute2Info.TYPE_REMOTE_CAR,
    MediaRoute2Info.TYPE_REMOTE_SMARTWATCH,
    MediaRoute2Info.TYPE_REMOTE_SMARTPHONE,
      -> true
    else -> false
  }
}

fun isPhoneMediaDevice(deviceType: Int): Boolean {
  return when (deviceType) {
    MediaRoute2Info.TYPE_BUILTIN_SPEAKER,
    MediaRoute2Info.TYPE_USB_DEVICE,
    MediaRoute2Info.TYPE_USB_HEADSET,
    MediaRoute2Info.TYPE_USB_ACCESSORY,
    MediaRoute2Info.TYPE_DOCK,
    MediaRoute2Info.TYPE_HDMI,
    MediaRoute2Info.TYPE_HDMI_ARC,
    MediaRoute2Info.TYPE_HDMI_EARC,
    MediaRoute2Info.TYPE_LINE_DIGITAL,
    MediaRoute2Info.TYPE_LINE_ANALOG,
    MediaRoute2Info.TYPE_AUX_LINE,
    MediaRoute2Info.TYPE_WIRED_HEADSET,
    MediaRoute2Info.TYPE_WIRED_HEADPHONES,
      -> true
    else -> false
  }
}

fun isBluetoothMediaDevice(deviceType: Int): Boolean {
  return when (deviceType) {
    MediaRoute2Info.TYPE_HEARING_AID,
    MediaRoute2Info.TYPE_BLUETOOTH_A2DP,
    MediaRoute2Info.TYPE_BLE_HEADSET,
      -> true
    else -> false
  }
}

fun isComplexMediaDevice(deviceType: Int): Boolean {
  return deviceType == MediaRoute2Info.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER
}
+49 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.settingslib.media

import android.content.Context
import android.graphics.drawable.Drawable
import android.media.SuggestedDeviceInfo
import com.android.settingslib.R
import com.android.settingslib.media.LocalMediaManager.MediaDeviceState
import com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_CONNECTING_FAILED
import com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED

/**
 * Wrapper class around [SuggestedDeviceInfo] and the corresponding connection state of the
 * suggestion.
 */
data class SuggestedDeviceState
@JvmOverloads
constructor(
    val suggestedDeviceInfo: SuggestedDeviceInfo,
    @MediaDeviceState val connectionState: Int = STATE_DISCONNECTED,
) {
  fun getIcon(context: Context): Drawable {
      val deviceType = suggestedDeviceInfo.type
      val resourceId = when {
          connectionState == STATE_CONNECTING_FAILED -> android.R.drawable.ic_info
          isInfoMediaDevice(deviceType) -> InfoMediaDevice.getDrawableResIdByType(deviceType)
          isPhoneMediaDevice(deviceType) ->
              DeviceIconUtil(context).getIconResIdFromMediaRouteType(deviceType)
          else -> R.drawable.ic_media_speaker_device
      }

      return context.getDrawable(resourceId)!!
  }
}
+14 −2
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.settingslib.media;

import static android.media.MediaRoute2Info.CONNECTION_STATE_CONNECTING;
import static android.media.MediaRoute2Info.TYPE_BLE_HEADSET;
import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
@@ -27,6 +28,7 @@ import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
import static android.media.MediaRoute2ProviderService.REASON_NETWORK_ERROR;
import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;

import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_CONNECTING;
import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;

import static com.google.common.truth.Truth.assertThat;
@@ -63,7 +65,6 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.InfoMediaManager.Api34Impl;
import com.android.settingslib.media.InfoMediaManager.SuggestedDeviceState;

import com.google.common.collect.ImmutableList;

@@ -156,7 +157,7 @@ public class InfoMediaManagerTest {
    private ArgumentCaptor<DeviceSuggestionsUpdatesCallback> mDeviceSuggestionsUpdatesCallback;

    @Captor
    private ArgumentCaptor<InfoMediaManager.SuggestedDeviceState> mSuggestedDeviceStateCaptor;
    private ArgumentCaptor<SuggestedDeviceState> mSuggestedDeviceStateCaptor;

    private RouterInfoMediaManager mInfoMediaManager;
    private Context mContext;
@@ -896,6 +897,17 @@ public class InfoMediaManagerTest {
        assertThat(mInfoMediaManager.mMediaDevices.get(0) instanceof PhoneMediaDevice).isTrue();
    }

    @Test
    public void addMediaDevice_routeInConnectingState_setsConnectingToDevice() {
        final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
        when(route2Info.getConnectionState()).thenReturn(CONNECTION_STATE_CONNECTING);
        when(route2Info.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
        when(route2Info.getId()).thenReturn(TEST_ID);
        mInfoMediaManager.addMediaDeviceLocked(route2Info, TEST_SYSTEM_ROUTING_SESSION);

        assertThat(mInfoMediaManager.mMediaDevices.get(0).getState()).isEqualTo(STATE_CONNECTING);
    }

    @Test
    public void addMediaDevice_cachedBluetoothDeviceIsNull_shouldNotAdded() {
        final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
Loading