From 73706ceebfc232072b89808694990c6ac71a810d Mon Sep 17 00:00:00 2001 From: shaoweishen Date: Thu, 28 Apr 2022 09:18:23 +0000 Subject: [PATCH] DO NOT MERGE: Downbranch merge conflict [Output Switcher] Behavior improvement Use Lock when access MediaDevices in MediaOutputController as in LocalMediaController, prevent devices changed while refreshing. Sort devices in Controller to make sure the sequence is correct. Add grouping state to make dynamic grouping has transition state. Bug: 228511134 Bug: 228255441 Test: N/A Change-Id: I4976176f3133e48c41a56d746861a9a4027f9a34 --- .../settingslib/media/LocalMediaManager.java | 10 +- .../res/layout/media_output_list_item.xml | 1 + .../media/dialog/MediaOutputAdapter.java | 8 + .../media/dialog/MediaOutputBaseDialog.java | 19 ++- .../media/dialog/MediaOutputController.java | 143 ++++++++++++------ 5 files changed, 126 insertions(+), 55 deletions(-) diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index e1a2e8daf971d..d6d73046bed3d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -46,8 +46,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -56,7 +54,6 @@ import java.util.concurrent.CopyOnWriteArrayList; */ @RequiresApi(Build.VERSION_CODES.R) public class LocalMediaManager implements BluetoothCallback { - private static final Comparator COMPARATOR = Comparator.naturalOrder(); private static final String TAG = "LocalMediaManager"; private static final int MAX_DISCONNECTED_DEVICE_NUM = 5; @@ -65,13 +62,15 @@ public class LocalMediaManager implements BluetoothCallback { MediaDeviceState.STATE_CONNECTING, MediaDeviceState.STATE_DISCONNECTED, MediaDeviceState.STATE_CONNECTING_FAILED, - MediaDeviceState.STATE_SELECTED}) + MediaDeviceState.STATE_SELECTED, + MediaDeviceState.STATE_GROUPING}) public @interface MediaDeviceState { int STATE_CONNECTED = 0; int STATE_CONNECTING = 1; int STATE_DISCONNECTED = 2; int STATE_CONNECTING_FAILED = 3; int STATE_SELECTED = 4; + int STATE_GROUPING = 5; } private final Collection mCallbacks = new CopyOnWriteArrayList<>(); @@ -322,6 +321,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); 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} */ public boolean removeDeviceFromPlayMedia(MediaDevice device) { + device.setState(MediaDeviceState.STATE_GROUPING); return mInfoMediaManager.removeDeviceFromPlayMedia(device); } @@ -524,7 +525,6 @@ public class LocalMediaManager implements BluetoothCallback { @Override public void onDeviceListAdded(List devices) { synchronized (mMediaDevicesLock) { - Collections.sort(devices, COMPARATOR); mMediaDevices.clear(); mMediaDevices.addAll(devices); // Add disconnected bluetooth devices only when phone output device is available. diff --git a/packages/SystemUI/res/layout/media_output_list_item.xml b/packages/SystemUI/res/layout/media_output_list_item.xml index daa9c04584aec..f79e534670d8b 100644 --- a/packages/SystemUI/res/layout/media_output_list_item.xml +++ b/packages/SystemUI/res/layout/media_output_list_item.xml @@ -39,6 +39,7 @@ android:visibility="gone" android:paddingStart="0dp" android:paddingEnd="0dp" + android:background="@null" android:progressDrawable="@drawable/media_output_dialog_seekbar_background" android:thumb="@null" android:layout_width="match_parent" diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index 694149a1e6e7d..a397f32dcbbfa 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -166,6 +166,14 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { true /* showSubtitle */, true /* showStatus */); mSubTitleText.setText(R.string.media_output_dialog_connect_failed); 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 && isDeviceIncluded(mController.getSelectedMediaDevice(), device)) { mTitleText.setTextColor(mController.getColorItemContent()); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index 22bdacae1bcd9..5bb6557c3fe7c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -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, MediaOutputController mediaOutputController) { super(context, R.style.Theme_SystemUI_Dialog_Media); @@ -192,7 +205,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements mContext = getContext(); mBroadcastSender = broadcastSender; mMediaOutputController = mediaOutputController; - mLayoutManager = new LinearLayoutManager(mContext); + mLayoutManager = new LayoutManagerWrapper(mContext); mListMaxHeight = context.getResources().getDimensionPixelSize( R.dimen.media_output_dialog_list_max_height); mExecutor = Executors.newSingleThreadExecutor(); @@ -274,6 +287,10 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements } void refresh(boolean deviceSetChanged) { + if (mMediaOutputController.isRefreshing()) { + return; + } + mMediaOutputController.setRefreshing(true); // Update header icon final int iconRes = getHeaderIconRes(); final IconCompat iconCompat = getHeaderIcon(); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index 082f4bc3efe3d..e7f97d25fabef 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -81,6 +81,8 @@ import com.android.systemui.statusbar.phone.SystemUIDialog; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Optional; @@ -108,11 +110,15 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, private final DialogLaunchAnimator mDialogLaunchAnimator; private final List mGroupMediaDevices = new CopyOnWriteArrayList<>(); private final CommonNotifCollection mNotifCollection; + private final Object mMediaDevicesLock = new Object(); @VisibleForTesting final List mMediaDevices = new CopyOnWriteArrayList<>(); + final List mCachedMediaDevices = new CopyOnWriteArrayList<>(); private final NearbyMediaDevicesManager mNearbyMediaDevicesManager; private final Map mNearbyDeviceInfoMap = new ConcurrentHashMap<>(); + private boolean mIsRefreshing = false; + private boolean mNeedRefresh = false; private MediaController mMediaController; @VisibleForTesting Callback mCallback; @@ -172,7 +178,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, } void start(@NonNull Callback cb) { - mMediaDevices.clear(); + synchronized (mMediaDevicesLock) { + mCachedMediaDevices.clear(); + mMediaDevices.clear(); + } mNearbyDeviceInfoMap.clear(); if (mNearbyMediaDevicesManager != null) { mNearbyMediaDevicesManager.registerNearbyDevicesCallback(this); @@ -211,6 +220,14 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, return routerParams != null && !routerParams.isMediaTransferReceiverEnabled(); } + void setRefreshing(boolean refreshing) { + mIsRefreshing = refreshing; + } + + boolean isRefreshing() { + return mIsRefreshing; + } + void stop() { if (mMediaController != null) { mMediaController.unregisterCallback(mCb); @@ -219,7 +236,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, mLocalMediaManager.unregisterCallback(this); mLocalMediaManager.stopScan(); } - mMediaDevices.clear(); + synchronized (mMediaDevicesLock) { + mCachedMediaDevices.clear(); + mMediaDevices.clear(); + } if (mNearbyMediaDevicesManager != null) { mNearbyMediaDevicesManager.unregisterNearbyDevicesCallback(this); } @@ -228,15 +248,23 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, @Override public void onDeviceListUpdate(List devices) { - buildMediaDevices(devices); - mCallback.onDeviceListChanged(); + if (mMediaDevices.isEmpty() || !mIsRefreshing) { + buildMediaDevices(devices); + mCallback.onDeviceListChanged(); + } else { + synchronized (mMediaDevicesLock) { + mNeedRefresh = true; + mCachedMediaDevices.clear(); + mCachedMediaDevices.addAll(devices); + } + } } @Override public void onSelectedDeviceStateChanged(MediaDevice device, @LocalMediaManager.MediaDeviceState int state) { mCallback.onRouteChanged(); - mMetricLogger.logOutputSuccess(device.toString(), mMediaDevices); + mMetricLogger.logOutputSuccess(device.toString(), new ArrayList<>(mMediaDevices)); } @Override @@ -247,7 +275,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, @Override public void onRequestFailed(int reason) { mCallback.onRouteChanged(); - mMetricLogger.logOutputFailure(mMediaDevices, reason); + mMetricLogger.logOutputFailure(new ArrayList<>(mMediaDevices), reason); } 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() { return mColorConnectedItemBackground; } @@ -432,50 +468,55 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, } private void buildMediaDevices(List devices) { - // For the first time building list, to make sure the top device is the connected device. - if (mMediaDevices.isEmpty()) { - final MediaDevice connectedMediaDevice = getCurrentConnectedMediaDevice(); - if (connectedMediaDevice == null) { - if (DEBUG) { - Log.d(TAG, "No connected media device."); + synchronized (mMediaDevicesLock) { + attachRangeInfo(devices); + Collections.sort(devices, Comparator.naturalOrder()); + // For the first time building list, to make sure the top device is the connected + // 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; } - for (MediaDevice device : devices) { - if (TextUtils.equals(device.getId(), connectedMediaDevice.getId())) { - mMediaDevices.add(0, device); - } else { - mMediaDevices.add(device); + // To keep the same list order + final List targetMediaDevices = new ArrayList<>(); + for (MediaDevice originalDevice : mMediaDevices) { + for (MediaDevice newDevice : devices) { + if (TextUtils.equals(originalDevice.getId(), newDevice.getId())) { + targetMediaDevices.add(newDevice); + break; + } } } - return; - } - // To keep the same list order - final Collection targetMediaDevices = new ArrayList<>(); - for (MediaDevice originalDevice : mMediaDevices) { - for (MediaDevice newDevice : devices) { - if (TextUtils.equals(originalDevice.getId(), newDevice.getId())) { - targetMediaDevices.add(newDevice); - break; - } + if (targetMediaDevices.size() != devices.size()) { + devices.removeAll(targetMediaDevices); + targetMediaDevices.addAll(devices); } + 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() { - for (MediaDevice mediaDevice : mMediaDevices) { + private void attachRangeInfo(List devices) { + for (MediaDevice mediaDevice : devices) { if (mNearbyDeviceInfoMap.containsKey(mediaDevice.getId())) { mediaDevice.setRangeZone(mNearbyDeviceInfoMap.get(mediaDevice.getId())); } } + } List getGroupMediaDevices() { @@ -609,26 +650,30 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, } boolean isTransferring() { - for (MediaDevice device : mMediaDevices) { - if (device.getState() == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) { - return true; + synchronized (mMediaDevicesLock) { + for (MediaDevice device : mMediaDevices) { + if (device.getState() == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) { + return true; + } } } return false; } boolean isZeroMode() { - if (mMediaDevices.size() == 1) { - final MediaDevice device = mMediaDevices.iterator().next(); - // Add "pair new" only when local output device exists - final int type = device.getDeviceType(); - if (type == MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE - || type == MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE - || type == MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE) { - return true; + synchronized (mMediaDevicesLock) { + if (mMediaDevices.size() == 1) { + final MediaDevice device = mMediaDevices.iterator().next(); + // Add "pair new" only when local output device exists + final int type = device.getDeviceType(); + if (type == MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE + || type == MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE + || type == MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE) { + return true; + } } + return false; } - return false; } void launchBluetoothPairing(View view) {