Loading packages/SettingsLib/src/com/android/settingslib/media/ComplexMediaDevice.java +8 −2 Original line number Original line Diff line number Diff line Loading @@ -54,12 +54,18 @@ public class ComplexMediaDevice extends MediaDevice { @Override @Override public Drawable getIcon() { public Drawable getIcon() { return mContext.getDrawable(R.drawable.ic_media_avr_device); return getIcon(mContext); } } @Override @Override public Drawable getIconWithoutBackground() { 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 @Override Loading packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java +7 −1 Original line number Original line Diff line number Diff line Loading @@ -33,6 +33,7 @@ import android.graphics.drawable.Drawable; import android.media.MediaRoute2Info; import android.media.MediaRoute2Info; import android.media.RouteListingPreference; import android.media.RouteListingPreference; import androidx.annotation.DrawableRes; import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting; import com.android.settingslib.R; import com.android.settingslib.R; Loading Loading @@ -75,9 +76,14 @@ public class InfoMediaDevice extends MediaDevice { @VisibleForTesting @VisibleForTesting @SuppressWarnings("NewApi") @SuppressWarnings("NewApi") @DrawableRes int getDrawableResIdByType() { int getDrawableResIdByType() { return getDrawableResIdByType(mRouteInfo.getType()); } static int getDrawableResIdByType(@MediaRoute2Info.Type int type) { int resId; int resId; switch (mRouteInfo.getType()) { switch (type) { case TYPE_GROUP: case TYPE_GROUP: resId = R.drawable.ic_media_group_device; resId = R.drawable.ic_media_group_device; break; break; Loading packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +111 −44 Original line number Original line Diff line number Diff line Loading @@ -52,6 +52,7 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice; import android.content.ComponentName; import android.content.ComponentName; import android.content.Context; import android.content.Context; import android.graphics.drawable.Drawable; import android.media.MediaRoute2Info; import android.media.MediaRoute2Info; import android.media.RouteListingPreference; import android.media.RouteListingPreference; import android.media.RoutingSessionInfo; import android.media.RoutingSessionInfo; Loading @@ -69,6 +70,7 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.R; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.media.flags.Flags; import com.android.settingslib.media.flags.Flags; Loading Loading @@ -161,6 +163,12 @@ public abstract class InfoMediaManager { return mConnectionState; return mConnectionState; } } /** Gets the drawable associated with the suggested device type. */ @NonNull public Drawable getIcon(Context context) { return getDrawableForSuggestion(context, this); } @Override @Override public boolean equals(Object obj) { public boolean equals(Object obj) { if (this == obj) { if (this == obj) { Loading Loading @@ -681,6 +689,11 @@ public abstract class InfoMediaManager { return getActiveRoutingSession().getName(); return getActiveRoutingSession().getName(); } } @Nullable public SuggestedDeviceState getSuggestedDevice() { return mSuggestedDeviceState; } @TargetApi(Build.VERSION_CODES.R) @TargetApi(Build.VERSION_CODES.R) boolean shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo) { boolean shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo) { return sessionInfo.isSystemSession() // System sessions are not remote return sessionInfo.isSystemSession() // System sessions are not remote Loading Loading @@ -717,6 +730,11 @@ public abstract class InfoMediaManager { } } } } if (newSuggestedDeviceState == null) { if (newSuggestedDeviceState == null) { if (topSuggestion .getRouteId() .equals(previousState.getSuggestedDeviceInfo().getRouteId())) { return; } newSuggestedDeviceState = new SuggestedDeviceState(topSuggestion); newSuggestedDeviceState = new SuggestedDeviceState(topSuggestion); } } } } Loading Loading @@ -862,6 +880,59 @@ public abstract class InfoMediaManager { void addMediaDevice(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo activeSession) { void addMediaDevice(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo activeSession) { final int deviceType = route.getType(); final int deviceType = route.getType(); MediaDevice mediaDevice = null; 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) { switch (deviceType) { case TYPE_UNKNOWN: case TYPE_UNKNOWN: case TYPE_REMOTE_TV: case TYPE_REMOTE_TV: Loading @@ -874,12 +945,14 @@ public abstract class InfoMediaManager { case TYPE_REMOTE_CAR: case TYPE_REMOTE_CAR: case TYPE_REMOTE_SMARTWATCH: case TYPE_REMOTE_SMARTWATCH: case TYPE_REMOTE_SMARTPHONE: case TYPE_REMOTE_SMARTPHONE: mediaDevice = return true; new InfoMediaDevice( default: mContext, return false; route, } mPreferenceItemMap.get(route.getId())); } break; private static boolean isPhoneMediaDevice(int deviceType) { switch (deviceType) { case TYPE_BUILTIN_SPEAKER: case TYPE_BUILTIN_SPEAKER: case TYPE_USB_DEVICE: case TYPE_USB_DEVICE: case TYPE_USB_HEADSET: case TYPE_USB_HEADSET: Loading @@ -893,51 +966,45 @@ public abstract class InfoMediaManager { case TYPE_AUX_LINE: case TYPE_AUX_LINE: case TYPE_WIRED_HEADSET: case TYPE_WIRED_HEADSET: case TYPE_WIRED_HEADPHONES: case TYPE_WIRED_HEADPHONES: mediaDevice = return true; new PhoneMediaDevice( default: mContext, return false; route, } mPreferenceItemMap.getOrDefault(route.getId(), null)); } break; private static boolean isBluetoothMediaDevice(int deviceType) { switch (deviceType) { case TYPE_HEARING_AID: case TYPE_HEARING_AID: case TYPE_BLUETOOTH_A2DP: case TYPE_BLUETOOTH_A2DP: case TYPE_BLE_HEADSET: case TYPE_BLE_HEADSET: if (route.getAddress() == null) { return true; Log.e(TAG, "Ignoring bluetooth route with no set address: " + route); default: break; 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: private static boolean isComplexMediaDevice(int deviceType) { mediaDevice = return deviceType == TYPE_REMOTE_AUDIO_VIDEO_RECEIVER; new ComplexMediaDevice( mContext, route, mPreferenceItemMap.get(route.getId())); break; default: Log.w(TAG, "addMediaDevice() unknown device type : " + deviceType); break; } } if (mediaDevice != null) { private static Drawable getDrawableForSuggestion( if (activeSession.getSelectedRoutes().contains(route.getId())) { Context context, SuggestedDeviceState suggestion) { mediaDevice.setState(STATE_SELECTED); 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) @RequiresApi(34) Loading packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java +8 −2 Original line number Original line Diff line number Diff line Loading @@ -62,6 +62,8 @@ public final class InputRouteManager { private final AudioManager mAudioManager; private final AudioManager mAudioManager; private final InfoMediaManager mInfoMediaManager; @VisibleForTesting final List<MediaDevice> mInputMediaDevices = new CopyOnWriteArrayList<>(); @VisibleForTesting final List<MediaDevice> mInputMediaDevices = new CopyOnWriteArrayList<>(); private @AudioDeviceType int mSelectedInputDeviceType; private @AudioDeviceType int mSelectedInputDeviceType; Loading Loading @@ -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; mContext = context; mAudioManager = audioManager; mAudioManager = audioManager; mInfoMediaManager = infoMediaManager; Handler handler = new Handler(context.getMainLooper()); Handler handler = new Handler(context.getMainLooper()); mAudioManager.registerAudioDeviceCallback(mAudioDeviceCallback, handler); mAudioManager.registerAudioDeviceCallback(mAudioDeviceCallback, handler); Loading Loading @@ -210,7 +216,7 @@ public final class InputRouteManager { getProductNameFromAudioDeviceInfo(info)); getProductNameFromAudioDeviceInfo(info)); if (mediaDevice != null) { if (mediaDevice != null) { if (info.getType() == mSelectedInputDeviceType) { if (info.getType() == mSelectedInputDeviceType) { mediaDevice.setState(STATE_SELECTED); mInfoMediaManager.setDeviceState(mediaDevice, STATE_SELECTED); } } mInputMediaDevices.add(mediaDevice); mInputMediaDevices.add(mediaDevice); } } Loading packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +112 −8 Original line number Original line Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.media.AudioDeviceAttributes; import android.media.AudioManager; import android.media.AudioManager; import android.media.RoutingSessionInfo; import android.media.RoutingSessionInfo; import android.os.Build; import android.os.Build; import android.os.Handler; import android.text.TextUtils; import android.text.TextUtils; import android.util.Log; import android.util.Log; Loading @@ -35,6 +36,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.BluetoothCallback; Loading @@ -44,12 +46,14 @@ import com.android.settingslib.bluetooth.HearingAidProfile; import com.android.settingslib.bluetooth.LeAudioProfile; import com.android.settingslib.bluetooth.LeAudioProfile; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfile; import com.android.settingslib.bluetooth.LocalBluetoothProfile; import com.android.settingslib.media.InfoMediaManager.SuggestedDeviceState; import java.lang.annotation.Retention; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.ArrayList; import java.util.Collection; import java.util.Collection; import java.util.List; import java.util.List; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList; /** /** Loading Loading @@ -98,6 +102,13 @@ public class LocalMediaManager implements BluetoothCallback { @VisibleForTesting @VisibleForTesting DeviceAttributeChangeCallback mDeviceAttributeChangeCallback = DeviceAttributeChangeCallback mDeviceAttributeChangeCallback = new DeviceAttributeChangeCallback(); new DeviceAttributeChangeCallback(); @GuardedBy("mMediaDevicesLock") @Nullable ConnectingSuggestedDeviceState mConnectingSuggestedDeviceState; @VisibleForTesting Handler mConnectSuggestedDeviceHandler; @VisibleForTesting @VisibleForTesting BluetoothAdapter mBluetoothAdapter; BluetoothAdapter mBluetoothAdapter; Loading Loading @@ -140,6 +151,7 @@ public class LocalMediaManager implements BluetoothCallback { LocalBluetoothManager.getInstance(context, /* onInitCallback= */ null); LocalBluetoothManager.getInstance(context, /* onInitCallback= */ null); mAudioManager = context.getSystemService(AudioManager.class); mAudioManager = context.getSystemService(AudioManager.class); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); mConnectSuggestedDeviceHandler = new Handler(context.getMainLooper()); if (mLocalBluetoothManager == null) { if (mLocalBluetoothManager == null) { Log.e(TAG, "Bluetooth is not supported on this device"); Log.e(TAG, "Bluetooth is not supported on this device"); return; return; Loading Loading @@ -169,6 +181,7 @@ public class LocalMediaManager implements BluetoothCallback { mPackageName = packageName; mPackageName = packageName; mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); mAudioManager = context.getSystemService(AudioManager.class); mAudioManager = context.getSystemService(AudioManager.class); mConnectSuggestedDeviceHandler = new Handler(context.getMainLooper()); } } /** /** Loading @@ -187,7 +200,7 @@ public class LocalMediaManager implements BluetoothCallback { ((BluetoothMediaDevice) device).getCachedDevice(); ((BluetoothMediaDevice) device).getCachedDevice(); if (!cachedDevice.isConnected() && !cachedDevice.isBusy()) { if (!cachedDevice.isConnected() && !cachedDevice.isBusy()) { mOnTransferBluetoothDevice = connectDevice; mOnTransferBluetoothDevice = connectDevice; device.setState(MediaDeviceState.STATE_CONNECTING); mInfoMediaManager.setDeviceState(device, MediaDeviceState.STATE_CONNECTING); cachedDevice.connect(); cachedDevice.connect(); return true; return true; } } Loading @@ -198,11 +211,53 @@ public class LocalMediaManager implements BluetoothCallback { return false; return false; } } device.setState(MediaDeviceState.STATE_CONNECTING); mInfoMediaManager.setDeviceState(device, MediaDeviceState.STATE_CONNECTING); mInfoMediaManager.connectToDevice(device); mInfoMediaManager.connectToDevice(device); return true; 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) { void dispatchSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state) { for (DeviceCallback callback : getCallbacks()) { for (DeviceCallback callback : getCallbacks()) { callback.onSelectedDeviceStateChanged(device, state); callback.onSelectedDeviceStateChanged(device, state); Loading Loading @@ -327,7 +382,7 @@ public class LocalMediaManager implements BluetoothCallback { * @return If add device successful return {@code true}, otherwise return {@code false} * @return If add device successful return {@code true}, otherwise return {@code false} */ */ public boolean addDeviceToPlayMedia(MediaDevice device) { public boolean addDeviceToPlayMedia(MediaDevice device) { device.setState(MediaDeviceState.STATE_GROUPING); mInfoMediaManager.setDeviceState(device, MediaDeviceState.STATE_GROUPING); return mInfoMediaManager.addDeviceToPlayMedia(device); return mInfoMediaManager.addDeviceToPlayMedia(device); } } Loading @@ -338,7 +393,7 @@ public class LocalMediaManager implements BluetoothCallback { * @return If device stop successful return {@code true}, otherwise return {@code false} * @return If device stop successful return {@code true}, otherwise return {@code false} */ */ public boolean removeDeviceFromPlayMedia(MediaDevice device) { public boolean removeDeviceFromPlayMedia(MediaDevice device) { device.setState(MediaDeviceState.STATE_GROUPING); mInfoMediaManager.setDeviceState(device, MediaDeviceState.STATE_GROUPING); return mInfoMediaManager.removeDeviceFromPlayMedia(device); return mInfoMediaManager.removeDeviceFromPlayMedia(device); } } Loading Loading @@ -556,7 +611,8 @@ public class LocalMediaManager implements BluetoothCallback { dispatchDeviceListUpdate(); dispatchDeviceListUpdate(); if (mOnTransferBluetoothDevice != null && mOnTransferBluetoothDevice.isConnected()) { if (mOnTransferBluetoothDevice != null && mOnTransferBluetoothDevice.isConnected()) { connectDevice(mOnTransferBluetoothDevice); connectDevice(mOnTransferBluetoothDevice); mOnTransferBluetoothDevice.setState(MediaDeviceState.STATE_CONNECTED); mInfoMediaManager.setDeviceState( mOnTransferBluetoothDevice, MediaDeviceState.STATE_CONNECTED); dispatchSelectedDeviceStateChanged(mOnTransferBluetoothDevice, dispatchSelectedDeviceStateChanged(mOnTransferBluetoothDevice, MediaDeviceState.STATE_CONNECTED); MediaDeviceState.STATE_CONNECTED); mOnTransferBluetoothDevice = null; mOnTransferBluetoothDevice = null; Loading Loading @@ -671,7 +727,7 @@ public class LocalMediaManager implements BluetoothCallback { mCurrentConnectedDevice = connectDevice; mCurrentConnectedDevice = connectDevice; if (connectDevice != null) { if (connectDevice != null) { connectDevice.setState(MediaDeviceState.STATE_CONNECTED); mInfoMediaManager.setDeviceState(connectDevice, MediaDeviceState.STATE_CONNECTED); dispatchSelectedDeviceStateChanged(mCurrentConnectedDevice, dispatchSelectedDeviceStateChanged(mCurrentConnectedDevice, MediaDeviceState.STATE_CONNECTED); MediaDeviceState.STATE_CONNECTED); Loading @@ -683,7 +739,8 @@ public class LocalMediaManager implements BluetoothCallback { synchronized (mMediaDevicesLock) { synchronized (mMediaDevicesLock) { for (MediaDevice device : mMediaDevices) { for (MediaDevice device : mMediaDevices) { if (device.getState() == MediaDeviceState.STATE_CONNECTING) { if (device.getState() == MediaDeviceState.STATE_CONNECTING) { device.setState(MediaDeviceState.STATE_CONNECTING_FAILED); mInfoMediaManager.setDeviceState( device, MediaDeviceState.STATE_CONNECTING_FAILED); } } } } } } Loading Loading @@ -782,11 +839,58 @@ public class LocalMediaManager implements BluetoothCallback { .isBusy() .isBusy() && !mOnTransferBluetoothDevice.isConnected()) { && !mOnTransferBluetoothDevice.isConnected()) { // Failed to connect // Failed to connect mOnTransferBluetoothDevice.setState(MediaDeviceState.STATE_CONNECTING_FAILED); mInfoMediaManager.setDeviceState( mOnTransferBluetoothDevice, MediaDeviceState.STATE_CONNECTING_FAILED); mOnTransferBluetoothDevice = null; mOnTransferBluetoothDevice = null; dispatchOnRequestFailed(REASON_UNKNOWN_ERROR); dispatchOnRequestFailed(REASON_UNKNOWN_ERROR); } } dispatchDeviceAttributesChanged(); 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
packages/SettingsLib/src/com/android/settingslib/media/ComplexMediaDevice.java +8 −2 Original line number Original line Diff line number Diff line Loading @@ -54,12 +54,18 @@ public class ComplexMediaDevice extends MediaDevice { @Override @Override public Drawable getIcon() { public Drawable getIcon() { return mContext.getDrawable(R.drawable.ic_media_avr_device); return getIcon(mContext); } } @Override @Override public Drawable getIconWithoutBackground() { 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 @Override Loading
packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java +7 −1 Original line number Original line Diff line number Diff line Loading @@ -33,6 +33,7 @@ import android.graphics.drawable.Drawable; import android.media.MediaRoute2Info; import android.media.MediaRoute2Info; import android.media.RouteListingPreference; import android.media.RouteListingPreference; import androidx.annotation.DrawableRes; import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting; import com.android.settingslib.R; import com.android.settingslib.R; Loading Loading @@ -75,9 +76,14 @@ public class InfoMediaDevice extends MediaDevice { @VisibleForTesting @VisibleForTesting @SuppressWarnings("NewApi") @SuppressWarnings("NewApi") @DrawableRes int getDrawableResIdByType() { int getDrawableResIdByType() { return getDrawableResIdByType(mRouteInfo.getType()); } static int getDrawableResIdByType(@MediaRoute2Info.Type int type) { int resId; int resId; switch (mRouteInfo.getType()) { switch (type) { case TYPE_GROUP: case TYPE_GROUP: resId = R.drawable.ic_media_group_device; resId = R.drawable.ic_media_group_device; break; break; Loading
packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +111 −44 Original line number Original line Diff line number Diff line Loading @@ -52,6 +52,7 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice; import android.content.ComponentName; import android.content.ComponentName; import android.content.Context; import android.content.Context; import android.graphics.drawable.Drawable; import android.media.MediaRoute2Info; import android.media.MediaRoute2Info; import android.media.RouteListingPreference; import android.media.RouteListingPreference; import android.media.RoutingSessionInfo; import android.media.RoutingSessionInfo; Loading @@ -69,6 +70,7 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.R; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.media.flags.Flags; import com.android.settingslib.media.flags.Flags; Loading Loading @@ -161,6 +163,12 @@ public abstract class InfoMediaManager { return mConnectionState; return mConnectionState; } } /** Gets the drawable associated with the suggested device type. */ @NonNull public Drawable getIcon(Context context) { return getDrawableForSuggestion(context, this); } @Override @Override public boolean equals(Object obj) { public boolean equals(Object obj) { if (this == obj) { if (this == obj) { Loading Loading @@ -681,6 +689,11 @@ public abstract class InfoMediaManager { return getActiveRoutingSession().getName(); return getActiveRoutingSession().getName(); } } @Nullable public SuggestedDeviceState getSuggestedDevice() { return mSuggestedDeviceState; } @TargetApi(Build.VERSION_CODES.R) @TargetApi(Build.VERSION_CODES.R) boolean shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo) { boolean shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo) { return sessionInfo.isSystemSession() // System sessions are not remote return sessionInfo.isSystemSession() // System sessions are not remote Loading Loading @@ -717,6 +730,11 @@ public abstract class InfoMediaManager { } } } } if (newSuggestedDeviceState == null) { if (newSuggestedDeviceState == null) { if (topSuggestion .getRouteId() .equals(previousState.getSuggestedDeviceInfo().getRouteId())) { return; } newSuggestedDeviceState = new SuggestedDeviceState(topSuggestion); newSuggestedDeviceState = new SuggestedDeviceState(topSuggestion); } } } } Loading Loading @@ -862,6 +880,59 @@ public abstract class InfoMediaManager { void addMediaDevice(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo activeSession) { void addMediaDevice(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo activeSession) { final int deviceType = route.getType(); final int deviceType = route.getType(); MediaDevice mediaDevice = null; 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) { switch (deviceType) { case TYPE_UNKNOWN: case TYPE_UNKNOWN: case TYPE_REMOTE_TV: case TYPE_REMOTE_TV: Loading @@ -874,12 +945,14 @@ public abstract class InfoMediaManager { case TYPE_REMOTE_CAR: case TYPE_REMOTE_CAR: case TYPE_REMOTE_SMARTWATCH: case TYPE_REMOTE_SMARTWATCH: case TYPE_REMOTE_SMARTPHONE: case TYPE_REMOTE_SMARTPHONE: mediaDevice = return true; new InfoMediaDevice( default: mContext, return false; route, } mPreferenceItemMap.get(route.getId())); } break; private static boolean isPhoneMediaDevice(int deviceType) { switch (deviceType) { case TYPE_BUILTIN_SPEAKER: case TYPE_BUILTIN_SPEAKER: case TYPE_USB_DEVICE: case TYPE_USB_DEVICE: case TYPE_USB_HEADSET: case TYPE_USB_HEADSET: Loading @@ -893,51 +966,45 @@ public abstract class InfoMediaManager { case TYPE_AUX_LINE: case TYPE_AUX_LINE: case TYPE_WIRED_HEADSET: case TYPE_WIRED_HEADSET: case TYPE_WIRED_HEADPHONES: case TYPE_WIRED_HEADPHONES: mediaDevice = return true; new PhoneMediaDevice( default: mContext, return false; route, } mPreferenceItemMap.getOrDefault(route.getId(), null)); } break; private static boolean isBluetoothMediaDevice(int deviceType) { switch (deviceType) { case TYPE_HEARING_AID: case TYPE_HEARING_AID: case TYPE_BLUETOOTH_A2DP: case TYPE_BLUETOOTH_A2DP: case TYPE_BLE_HEADSET: case TYPE_BLE_HEADSET: if (route.getAddress() == null) { return true; Log.e(TAG, "Ignoring bluetooth route with no set address: " + route); default: break; 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: private static boolean isComplexMediaDevice(int deviceType) { mediaDevice = return deviceType == TYPE_REMOTE_AUDIO_VIDEO_RECEIVER; new ComplexMediaDevice( mContext, route, mPreferenceItemMap.get(route.getId())); break; default: Log.w(TAG, "addMediaDevice() unknown device type : " + deviceType); break; } } if (mediaDevice != null) { private static Drawable getDrawableForSuggestion( if (activeSession.getSelectedRoutes().contains(route.getId())) { Context context, SuggestedDeviceState suggestion) { mediaDevice.setState(STATE_SELECTED); 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) @RequiresApi(34) Loading
packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java +8 −2 Original line number Original line Diff line number Diff line Loading @@ -62,6 +62,8 @@ public final class InputRouteManager { private final AudioManager mAudioManager; private final AudioManager mAudioManager; private final InfoMediaManager mInfoMediaManager; @VisibleForTesting final List<MediaDevice> mInputMediaDevices = new CopyOnWriteArrayList<>(); @VisibleForTesting final List<MediaDevice> mInputMediaDevices = new CopyOnWriteArrayList<>(); private @AudioDeviceType int mSelectedInputDeviceType; private @AudioDeviceType int mSelectedInputDeviceType; Loading Loading @@ -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; mContext = context; mAudioManager = audioManager; mAudioManager = audioManager; mInfoMediaManager = infoMediaManager; Handler handler = new Handler(context.getMainLooper()); Handler handler = new Handler(context.getMainLooper()); mAudioManager.registerAudioDeviceCallback(mAudioDeviceCallback, handler); mAudioManager.registerAudioDeviceCallback(mAudioDeviceCallback, handler); Loading Loading @@ -210,7 +216,7 @@ public final class InputRouteManager { getProductNameFromAudioDeviceInfo(info)); getProductNameFromAudioDeviceInfo(info)); if (mediaDevice != null) { if (mediaDevice != null) { if (info.getType() == mSelectedInputDeviceType) { if (info.getType() == mSelectedInputDeviceType) { mediaDevice.setState(STATE_SELECTED); mInfoMediaManager.setDeviceState(mediaDevice, STATE_SELECTED); } } mInputMediaDevices.add(mediaDevice); mInputMediaDevices.add(mediaDevice); } } Loading
packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +112 −8 Original line number Original line Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.media.AudioDeviceAttributes; import android.media.AudioManager; import android.media.AudioManager; import android.media.RoutingSessionInfo; import android.media.RoutingSessionInfo; import android.os.Build; import android.os.Build; import android.os.Handler; import android.text.TextUtils; import android.text.TextUtils; import android.util.Log; import android.util.Log; Loading @@ -35,6 +36,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.BluetoothCallback; Loading @@ -44,12 +46,14 @@ import com.android.settingslib.bluetooth.HearingAidProfile; import com.android.settingslib.bluetooth.LeAudioProfile; import com.android.settingslib.bluetooth.LeAudioProfile; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfile; import com.android.settingslib.bluetooth.LocalBluetoothProfile; import com.android.settingslib.media.InfoMediaManager.SuggestedDeviceState; import java.lang.annotation.Retention; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.ArrayList; import java.util.Collection; import java.util.Collection; import java.util.List; import java.util.List; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList; /** /** Loading Loading @@ -98,6 +102,13 @@ public class LocalMediaManager implements BluetoothCallback { @VisibleForTesting @VisibleForTesting DeviceAttributeChangeCallback mDeviceAttributeChangeCallback = DeviceAttributeChangeCallback mDeviceAttributeChangeCallback = new DeviceAttributeChangeCallback(); new DeviceAttributeChangeCallback(); @GuardedBy("mMediaDevicesLock") @Nullable ConnectingSuggestedDeviceState mConnectingSuggestedDeviceState; @VisibleForTesting Handler mConnectSuggestedDeviceHandler; @VisibleForTesting @VisibleForTesting BluetoothAdapter mBluetoothAdapter; BluetoothAdapter mBluetoothAdapter; Loading Loading @@ -140,6 +151,7 @@ public class LocalMediaManager implements BluetoothCallback { LocalBluetoothManager.getInstance(context, /* onInitCallback= */ null); LocalBluetoothManager.getInstance(context, /* onInitCallback= */ null); mAudioManager = context.getSystemService(AudioManager.class); mAudioManager = context.getSystemService(AudioManager.class); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); mConnectSuggestedDeviceHandler = new Handler(context.getMainLooper()); if (mLocalBluetoothManager == null) { if (mLocalBluetoothManager == null) { Log.e(TAG, "Bluetooth is not supported on this device"); Log.e(TAG, "Bluetooth is not supported on this device"); return; return; Loading Loading @@ -169,6 +181,7 @@ public class LocalMediaManager implements BluetoothCallback { mPackageName = packageName; mPackageName = packageName; mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); mAudioManager = context.getSystemService(AudioManager.class); mAudioManager = context.getSystemService(AudioManager.class); mConnectSuggestedDeviceHandler = new Handler(context.getMainLooper()); } } /** /** Loading @@ -187,7 +200,7 @@ public class LocalMediaManager implements BluetoothCallback { ((BluetoothMediaDevice) device).getCachedDevice(); ((BluetoothMediaDevice) device).getCachedDevice(); if (!cachedDevice.isConnected() && !cachedDevice.isBusy()) { if (!cachedDevice.isConnected() && !cachedDevice.isBusy()) { mOnTransferBluetoothDevice = connectDevice; mOnTransferBluetoothDevice = connectDevice; device.setState(MediaDeviceState.STATE_CONNECTING); mInfoMediaManager.setDeviceState(device, MediaDeviceState.STATE_CONNECTING); cachedDevice.connect(); cachedDevice.connect(); return true; return true; } } Loading @@ -198,11 +211,53 @@ public class LocalMediaManager implements BluetoothCallback { return false; return false; } } device.setState(MediaDeviceState.STATE_CONNECTING); mInfoMediaManager.setDeviceState(device, MediaDeviceState.STATE_CONNECTING); mInfoMediaManager.connectToDevice(device); mInfoMediaManager.connectToDevice(device); return true; 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) { void dispatchSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state) { for (DeviceCallback callback : getCallbacks()) { for (DeviceCallback callback : getCallbacks()) { callback.onSelectedDeviceStateChanged(device, state); callback.onSelectedDeviceStateChanged(device, state); Loading Loading @@ -327,7 +382,7 @@ public class LocalMediaManager implements BluetoothCallback { * @return If add device successful return {@code true}, otherwise return {@code false} * @return If add device successful return {@code true}, otherwise return {@code false} */ */ public boolean addDeviceToPlayMedia(MediaDevice device) { public boolean addDeviceToPlayMedia(MediaDevice device) { device.setState(MediaDeviceState.STATE_GROUPING); mInfoMediaManager.setDeviceState(device, MediaDeviceState.STATE_GROUPING); return mInfoMediaManager.addDeviceToPlayMedia(device); return mInfoMediaManager.addDeviceToPlayMedia(device); } } Loading @@ -338,7 +393,7 @@ public class LocalMediaManager implements BluetoothCallback { * @return If device stop successful return {@code true}, otherwise return {@code false} * @return If device stop successful return {@code true}, otherwise return {@code false} */ */ public boolean removeDeviceFromPlayMedia(MediaDevice device) { public boolean removeDeviceFromPlayMedia(MediaDevice device) { device.setState(MediaDeviceState.STATE_GROUPING); mInfoMediaManager.setDeviceState(device, MediaDeviceState.STATE_GROUPING); return mInfoMediaManager.removeDeviceFromPlayMedia(device); return mInfoMediaManager.removeDeviceFromPlayMedia(device); } } Loading Loading @@ -556,7 +611,8 @@ public class LocalMediaManager implements BluetoothCallback { dispatchDeviceListUpdate(); dispatchDeviceListUpdate(); if (mOnTransferBluetoothDevice != null && mOnTransferBluetoothDevice.isConnected()) { if (mOnTransferBluetoothDevice != null && mOnTransferBluetoothDevice.isConnected()) { connectDevice(mOnTransferBluetoothDevice); connectDevice(mOnTransferBluetoothDevice); mOnTransferBluetoothDevice.setState(MediaDeviceState.STATE_CONNECTED); mInfoMediaManager.setDeviceState( mOnTransferBluetoothDevice, MediaDeviceState.STATE_CONNECTED); dispatchSelectedDeviceStateChanged(mOnTransferBluetoothDevice, dispatchSelectedDeviceStateChanged(mOnTransferBluetoothDevice, MediaDeviceState.STATE_CONNECTED); MediaDeviceState.STATE_CONNECTED); mOnTransferBluetoothDevice = null; mOnTransferBluetoothDevice = null; Loading Loading @@ -671,7 +727,7 @@ public class LocalMediaManager implements BluetoothCallback { mCurrentConnectedDevice = connectDevice; mCurrentConnectedDevice = connectDevice; if (connectDevice != null) { if (connectDevice != null) { connectDevice.setState(MediaDeviceState.STATE_CONNECTED); mInfoMediaManager.setDeviceState(connectDevice, MediaDeviceState.STATE_CONNECTED); dispatchSelectedDeviceStateChanged(mCurrentConnectedDevice, dispatchSelectedDeviceStateChanged(mCurrentConnectedDevice, MediaDeviceState.STATE_CONNECTED); MediaDeviceState.STATE_CONNECTED); Loading @@ -683,7 +739,8 @@ public class LocalMediaManager implements BluetoothCallback { synchronized (mMediaDevicesLock) { synchronized (mMediaDevicesLock) { for (MediaDevice device : mMediaDevices) { for (MediaDevice device : mMediaDevices) { if (device.getState() == MediaDeviceState.STATE_CONNECTING) { if (device.getState() == MediaDeviceState.STATE_CONNECTING) { device.setState(MediaDeviceState.STATE_CONNECTING_FAILED); mInfoMediaManager.setDeviceState( device, MediaDeviceState.STATE_CONNECTING_FAILED); } } } } } } Loading Loading @@ -782,11 +839,58 @@ public class LocalMediaManager implements BluetoothCallback { .isBusy() .isBusy() && !mOnTransferBluetoothDevice.isConnected()) { && !mOnTransferBluetoothDevice.isConnected()) { // Failed to connect // Failed to connect mOnTransferBluetoothDevice.setState(MediaDeviceState.STATE_CONNECTING_FAILED); mInfoMediaManager.setDeviceState( mOnTransferBluetoothDevice, MediaDeviceState.STATE_CONNECTING_FAILED); mOnTransferBluetoothDevice = null; mOnTransferBluetoothDevice = null; dispatchOnRequestFailed(REASON_UNKNOWN_ERROR); dispatchOnRequestFailed(REASON_UNKNOWN_ERROR); } } dispatchDeviceAttributesChanged(); 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); } } } }