Merge "DO NOT MERGE: Downbranch merge conflict [Output Switcher] Behavior improvement" into tm-dev

This commit is contained in:
Shaowei Shen
2022-05-03 13:49:43 +00:00
committed by Android (Google) Code Review
5 changed files with 126 additions and 55 deletions

View File

@@ -46,8 +46,6 @@ 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.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
@@ -56,7 +54,6 @@ import java.util.concurrent.CopyOnWriteArrayList;
*/ */
@RequiresApi(Build.VERSION_CODES.R) @RequiresApi(Build.VERSION_CODES.R)
public class LocalMediaManager implements BluetoothCallback { public class LocalMediaManager implements BluetoothCallback {
private static final Comparator<MediaDevice> COMPARATOR = Comparator.naturalOrder();
private static final String TAG = "LocalMediaManager"; private static final String TAG = "LocalMediaManager";
private static final int MAX_DISCONNECTED_DEVICE_NUM = 5; private static final int MAX_DISCONNECTED_DEVICE_NUM = 5;
@@ -65,13 +62,15 @@ public class LocalMediaManager implements BluetoothCallback {
MediaDeviceState.STATE_CONNECTING, MediaDeviceState.STATE_CONNECTING,
MediaDeviceState.STATE_DISCONNECTED, MediaDeviceState.STATE_DISCONNECTED,
MediaDeviceState.STATE_CONNECTING_FAILED, MediaDeviceState.STATE_CONNECTING_FAILED,
MediaDeviceState.STATE_SELECTED}) MediaDeviceState.STATE_SELECTED,
MediaDeviceState.STATE_GROUPING})
public @interface MediaDeviceState { public @interface MediaDeviceState {
int STATE_CONNECTED = 0; int STATE_CONNECTED = 0;
int STATE_CONNECTING = 1; int STATE_CONNECTING = 1;
int STATE_DISCONNECTED = 2; int STATE_DISCONNECTED = 2;
int STATE_CONNECTING_FAILED = 3; int STATE_CONNECTING_FAILED = 3;
int STATE_SELECTED = 4; int STATE_SELECTED = 4;
int STATE_GROUPING = 5;
} }
private final Collection<DeviceCallback> mCallbacks = new CopyOnWriteArrayList<>(); private final Collection<DeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
@@ -322,6 +321,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);
return mInfoMediaManager.addDeviceToPlayMedia(device); return mInfoMediaManager.addDeviceToPlayMedia(device);
} }
@@ -332,6 +332,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);
return mInfoMediaManager.removeDeviceFromPlayMedia(device); return mInfoMediaManager.removeDeviceFromPlayMedia(device);
} }
@@ -524,7 +525,6 @@ public class LocalMediaManager implements BluetoothCallback {
@Override @Override
public void onDeviceListAdded(List<MediaDevice> devices) { public void onDeviceListAdded(List<MediaDevice> devices) {
synchronized (mMediaDevicesLock) { synchronized (mMediaDevicesLock) {
Collections.sort(devices, COMPARATOR);
mMediaDevices.clear(); mMediaDevices.clear();
mMediaDevices.addAll(devices); mMediaDevices.addAll(devices);
// Add disconnected bluetooth devices only when phone output device is available. // Add disconnected bluetooth devices only when phone output device is available.

View File

@@ -39,6 +39,7 @@
android:visibility="gone" android:visibility="gone"
android:paddingStart="0dp" android:paddingStart="0dp"
android:paddingEnd="0dp" android:paddingEnd="0dp"
android:background="@null"
android:progressDrawable="@drawable/media_output_dialog_seekbar_background" android:progressDrawable="@drawable/media_output_dialog_seekbar_background"
android:thumb="@null" android:thumb="@null"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -166,6 +166,14 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
true /* showSubtitle */, true /* showStatus */); true /* showSubtitle */, true /* showStatus */);
mSubTitleText.setText(R.string.media_output_dialog_connect_failed); mSubTitleText.setText(R.string.media_output_dialog_connect_failed);
mContainerLayout.setOnClickListener(v -> onItemClick(v, device)); mContainerLayout.setOnClickListener(v -> onItemClick(v, device));
} else if (device.getState() == MediaDeviceState.STATE_GROUPING) {
mProgressBar.getIndeterminateDrawable().setColorFilter(
new PorterDuffColorFilter(
mController.getColorItemContent(),
PorterDuff.Mode.SRC_IN));
setSingleLineLayout(getItemTitle(device), true /* bFocused */,
false /* showSeekBar*/,
true /* showProgressBar */, false /* showStatus */);
} else if (mController.getSelectedMediaDevice().size() > 1 } else if (mController.getSelectedMediaDevice().size() > 1
&& isDeviceIncluded(mController.getSelectedMediaDevice(), device)) { && isDeviceIncluded(mController.getSelectedMediaDevice(), device)) {
mTitleText.setTextColor(mController.getColorItemContent()); mTitleText.setTextColor(mController.getColorItemContent());

View File

@@ -184,6 +184,19 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
} }
}; };
private class LayoutManagerWrapper extends LinearLayoutManager {
LayoutManagerWrapper(Context context) {
super(context);
}
@Override
public void onLayoutCompleted(RecyclerView.State state) {
super.onLayoutCompleted(state);
mMediaOutputController.setRefreshing(false);
mMediaOutputController.refreshDataSetIfNeeded();
}
}
public MediaOutputBaseDialog(Context context, BroadcastSender broadcastSender, public MediaOutputBaseDialog(Context context, BroadcastSender broadcastSender,
MediaOutputController mediaOutputController) { MediaOutputController mediaOutputController) {
super(context, R.style.Theme_SystemUI_Dialog_Media); super(context, R.style.Theme_SystemUI_Dialog_Media);
@@ -192,7 +205,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
mContext = getContext(); mContext = getContext();
mBroadcastSender = broadcastSender; mBroadcastSender = broadcastSender;
mMediaOutputController = mediaOutputController; mMediaOutputController = mediaOutputController;
mLayoutManager = new LinearLayoutManager(mContext); mLayoutManager = new LayoutManagerWrapper(mContext);
mListMaxHeight = context.getResources().getDimensionPixelSize( mListMaxHeight = context.getResources().getDimensionPixelSize(
R.dimen.media_output_dialog_list_max_height); R.dimen.media_output_dialog_list_max_height);
mExecutor = Executors.newSingleThreadExecutor(); mExecutor = Executors.newSingleThreadExecutor();
@@ -274,6 +287,10 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
} }
void refresh(boolean deviceSetChanged) { void refresh(boolean deviceSetChanged) {
if (mMediaOutputController.isRefreshing()) {
return;
}
mMediaOutputController.setRefreshing(true);
// Update header icon // Update header icon
final int iconRes = getHeaderIconRes(); final int iconRes = getHeaderIconRes();
final IconCompat iconCompat = getHeaderIcon(); final IconCompat iconCompat = getHeaderIcon();

View File

@@ -81,6 +81,8 @@ import com.android.systemui.statusbar.phone.SystemUIDialog;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@@ -108,11 +110,15 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
private final DialogLaunchAnimator mDialogLaunchAnimator; private final DialogLaunchAnimator mDialogLaunchAnimator;
private final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>(); private final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>();
private final CommonNotifCollection mNotifCollection; private final CommonNotifCollection mNotifCollection;
private final Object mMediaDevicesLock = new Object();
@VisibleForTesting @VisibleForTesting
final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>(); final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
final List<MediaDevice> mCachedMediaDevices = new CopyOnWriteArrayList<>();
private final NearbyMediaDevicesManager mNearbyMediaDevicesManager; private final NearbyMediaDevicesManager mNearbyMediaDevicesManager;
private final Map<String, Integer> mNearbyDeviceInfoMap = new ConcurrentHashMap<>(); private final Map<String, Integer> mNearbyDeviceInfoMap = new ConcurrentHashMap<>();
private boolean mIsRefreshing = false;
private boolean mNeedRefresh = false;
private MediaController mMediaController; private MediaController mMediaController;
@VisibleForTesting @VisibleForTesting
Callback mCallback; Callback mCallback;
@@ -172,7 +178,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
} }
void start(@NonNull Callback cb) { void start(@NonNull Callback cb) {
mMediaDevices.clear(); synchronized (mMediaDevicesLock) {
mCachedMediaDevices.clear();
mMediaDevices.clear();
}
mNearbyDeviceInfoMap.clear(); mNearbyDeviceInfoMap.clear();
if (mNearbyMediaDevicesManager != null) { if (mNearbyMediaDevicesManager != null) {
mNearbyMediaDevicesManager.registerNearbyDevicesCallback(this); mNearbyMediaDevicesManager.registerNearbyDevicesCallback(this);
@@ -211,6 +220,14 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
return routerParams != null && !routerParams.isMediaTransferReceiverEnabled(); return routerParams != null && !routerParams.isMediaTransferReceiverEnabled();
} }
void setRefreshing(boolean refreshing) {
mIsRefreshing = refreshing;
}
boolean isRefreshing() {
return mIsRefreshing;
}
void stop() { void stop() {
if (mMediaController != null) { if (mMediaController != null) {
mMediaController.unregisterCallback(mCb); mMediaController.unregisterCallback(mCb);
@@ -219,7 +236,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
mLocalMediaManager.unregisterCallback(this); mLocalMediaManager.unregisterCallback(this);
mLocalMediaManager.stopScan(); mLocalMediaManager.stopScan();
} }
mMediaDevices.clear(); synchronized (mMediaDevicesLock) {
mCachedMediaDevices.clear();
mMediaDevices.clear();
}
if (mNearbyMediaDevicesManager != null) { if (mNearbyMediaDevicesManager != null) {
mNearbyMediaDevicesManager.unregisterNearbyDevicesCallback(this); mNearbyMediaDevicesManager.unregisterNearbyDevicesCallback(this);
} }
@@ -228,15 +248,23 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
@Override @Override
public void onDeviceListUpdate(List<MediaDevice> devices) { public void onDeviceListUpdate(List<MediaDevice> devices) {
buildMediaDevices(devices); if (mMediaDevices.isEmpty() || !mIsRefreshing) {
mCallback.onDeviceListChanged(); buildMediaDevices(devices);
mCallback.onDeviceListChanged();
} else {
synchronized (mMediaDevicesLock) {
mNeedRefresh = true;
mCachedMediaDevices.clear();
mCachedMediaDevices.addAll(devices);
}
}
} }
@Override @Override
public void onSelectedDeviceStateChanged(MediaDevice device, public void onSelectedDeviceStateChanged(MediaDevice device,
@LocalMediaManager.MediaDeviceState int state) { @LocalMediaManager.MediaDeviceState int state) {
mCallback.onRouteChanged(); mCallback.onRouteChanged();
mMetricLogger.logOutputSuccess(device.toString(), mMediaDevices); mMetricLogger.logOutputSuccess(device.toString(), new ArrayList<>(mMediaDevices));
} }
@Override @Override
@@ -247,7 +275,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
@Override @Override
public void onRequestFailed(int reason) { public void onRequestFailed(int reason) {
mCallback.onRouteChanged(); mCallback.onRouteChanged();
mMetricLogger.logOutputFailure(mMediaDevices, reason); mMetricLogger.logOutputFailure(new ArrayList<>(mMediaDevices), reason);
} }
Drawable getAppSourceIcon() { Drawable getAppSourceIcon() {
@@ -399,6 +427,14 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
} }
} }
void refreshDataSetIfNeeded() {
if (mNeedRefresh) {
buildMediaDevices(mCachedMediaDevices);
mCallback.onDeviceListChanged();
mNeedRefresh = false;
}
}
public int getColorConnectedItemBackground() { public int getColorConnectedItemBackground() {
return mColorConnectedItemBackground; return mColorConnectedItemBackground;
} }
@@ -432,50 +468,55 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
} }
private void buildMediaDevices(List<MediaDevice> devices) { private void buildMediaDevices(List<MediaDevice> devices) {
// For the first time building list, to make sure the top device is the connected device. synchronized (mMediaDevicesLock) {
if (mMediaDevices.isEmpty()) { attachRangeInfo(devices);
final MediaDevice connectedMediaDevice = getCurrentConnectedMediaDevice(); Collections.sort(devices, Comparator.naturalOrder());
if (connectedMediaDevice == null) { // For the first time building list, to make sure the top device is the connected
if (DEBUG) { // device.
Log.d(TAG, "No connected media device."); if (mMediaDevices.isEmpty()) {
final MediaDevice connectedMediaDevice = getCurrentConnectedMediaDevice();
if (connectedMediaDevice == null) {
if (DEBUG) {
Log.d(TAG, "No connected media device.");
}
mMediaDevices.addAll(devices);
return;
}
for (MediaDevice device : devices) {
if (TextUtils.equals(device.getId(), connectedMediaDevice.getId())) {
mMediaDevices.add(0, device);
} else {
mMediaDevices.add(device);
}
} }
mMediaDevices.addAll(devices);
return; return;
} }
for (MediaDevice device : devices) { // To keep the same list order
if (TextUtils.equals(device.getId(), connectedMediaDevice.getId())) { final List<MediaDevice> targetMediaDevices = new ArrayList<>();
mMediaDevices.add(0, device); for (MediaDevice originalDevice : mMediaDevices) {
} else { for (MediaDevice newDevice : devices) {
mMediaDevices.add(device); if (TextUtils.equals(originalDevice.getId(), newDevice.getId())) {
targetMediaDevices.add(newDevice);
break;
}
} }
} }
return; if (targetMediaDevices.size() != devices.size()) {
} devices.removeAll(targetMediaDevices);
// To keep the same list order targetMediaDevices.addAll(devices);
final Collection<MediaDevice> targetMediaDevices = new ArrayList<>();
for (MediaDevice originalDevice : mMediaDevices) {
for (MediaDevice newDevice : devices) {
if (TextUtils.equals(originalDevice.getId(), newDevice.getId())) {
targetMediaDevices.add(newDevice);
break;
}
} }
mMediaDevices.clear();
mMediaDevices.addAll(targetMediaDevices);
} }
if (targetMediaDevices.size() != devices.size()) {
devices.removeAll(targetMediaDevices);
targetMediaDevices.addAll(devices);
}
mMediaDevices.clear();
mMediaDevices.addAll(targetMediaDevices);
attachRangeInfo();
} }
private void attachRangeInfo() { private void attachRangeInfo(List<MediaDevice> devices) {
for (MediaDevice mediaDevice : mMediaDevices) { for (MediaDevice mediaDevice : devices) {
if (mNearbyDeviceInfoMap.containsKey(mediaDevice.getId())) { if (mNearbyDeviceInfoMap.containsKey(mediaDevice.getId())) {
mediaDevice.setRangeZone(mNearbyDeviceInfoMap.get(mediaDevice.getId())); mediaDevice.setRangeZone(mNearbyDeviceInfoMap.get(mediaDevice.getId()));
} }
} }
} }
List<MediaDevice> getGroupMediaDevices() { List<MediaDevice> getGroupMediaDevices() {
@@ -609,26 +650,30 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
} }
boolean isTransferring() { boolean isTransferring() {
for (MediaDevice device : mMediaDevices) { synchronized (mMediaDevicesLock) {
if (device.getState() == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) { for (MediaDevice device : mMediaDevices) {
return true; if (device.getState() == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) {
return true;
}
} }
} }
return false; return false;
} }
boolean isZeroMode() { boolean isZeroMode() {
if (mMediaDevices.size() == 1) { synchronized (mMediaDevicesLock) {
final MediaDevice device = mMediaDevices.iterator().next(); if (mMediaDevices.size() == 1) {
// Add "pair new" only when local output device exists final MediaDevice device = mMediaDevices.iterator().next();
final int type = device.getDeviceType(); // Add "pair new" only when local output device exists
if (type == MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE final int type = device.getDeviceType();
|| type == MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE if (type == MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE
|| type == MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE) { || type == MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE
return true; || type == MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE) {
return true;
}
} }
return false;
} }
return false;
} }
void launchBluetoothPairing(View view) { void launchBluetoothPairing(View view) {