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
This commit is contained in:
committed by
Shaowei Shen
parent
de900861e9
commit
73706ceebf
@@ -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.
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
synchronized (mMediaDevicesLock) {
|
||||||
|
mCachedMediaDevices.clear();
|
||||||
mMediaDevices.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();
|
||||||
}
|
}
|
||||||
|
synchronized (mMediaDevicesLock) {
|
||||||
|
mCachedMediaDevices.clear();
|
||||||
mMediaDevices.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) {
|
||||||
|
if (mMediaDevices.isEmpty() || !mIsRefreshing) {
|
||||||
buildMediaDevices(devices);
|
buildMediaDevices(devices);
|
||||||
mCallback.onDeviceListChanged();
|
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,7 +468,11 @@ 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) {
|
||||||
|
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()) {
|
if (mMediaDevices.isEmpty()) {
|
||||||
final MediaDevice connectedMediaDevice = getCurrentConnectedMediaDevice();
|
final MediaDevice connectedMediaDevice = getCurrentConnectedMediaDevice();
|
||||||
if (connectedMediaDevice == null) {
|
if (connectedMediaDevice == null) {
|
||||||
@@ -452,7 +492,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// To keep the same list order
|
// To keep the same list order
|
||||||
final Collection<MediaDevice> targetMediaDevices = new ArrayList<>();
|
final List<MediaDevice> targetMediaDevices = new ArrayList<>();
|
||||||
for (MediaDevice originalDevice : mMediaDevices) {
|
for (MediaDevice originalDevice : mMediaDevices) {
|
||||||
for (MediaDevice newDevice : devices) {
|
for (MediaDevice newDevice : devices) {
|
||||||
if (TextUtils.equals(originalDevice.getId(), newDevice.getId())) {
|
if (TextUtils.equals(originalDevice.getId(), newDevice.getId())) {
|
||||||
@@ -467,15 +507,16 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
|
|||||||
}
|
}
|
||||||
mMediaDevices.clear();
|
mMediaDevices.clear();
|
||||||
mMediaDevices.addAll(targetMediaDevices);
|
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,15 +650,18 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean isTransferring() {
|
boolean isTransferring() {
|
||||||
|
synchronized (mMediaDevicesLock) {
|
||||||
for (MediaDevice device : mMediaDevices) {
|
for (MediaDevice device : mMediaDevices) {
|
||||||
if (device.getState() == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) {
|
if (device.getState() == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isZeroMode() {
|
boolean isZeroMode() {
|
||||||
|
synchronized (mMediaDevicesLock) {
|
||||||
if (mMediaDevices.size() == 1) {
|
if (mMediaDevices.size() == 1) {
|
||||||
final MediaDevice device = mMediaDevices.iterator().next();
|
final MediaDevice device = mMediaDevices.iterator().next();
|
||||||
// Add "pair new" only when local output device exists
|
// Add "pair new" only when local output device exists
|
||||||
@@ -630,6 +674,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void launchBluetoothPairing(View view) {
|
void launchBluetoothPairing(View view) {
|
||||||
ActivityLaunchAnimator.Controller controller =
|
ActivityLaunchAnimator.Controller controller =
|
||||||
|
|||||||
Reference in New Issue
Block a user