[Output Switcher] [Group by Type] Add MediaItem to store data

Apply new class MediaItem to store item in the list, this change will
not have impact on layout and functionality should remain the same.
This is first refactor stage of making OutputSwitcher support custom
item, which will be used to apply grouping devices by type request.

design: go/output-switcher-custom-items

Bug: 255124239
Test: atest MediaOutputAdapterTest MediaOutputControllerTest MediaOutputBaseDialogTest MediaOutputDialogTest
Change-Id: Ibda4f0af1204d9e9eb48a3137d50ab591a276319
This commit is contained in:
shaoweishen
2022-12-05 15:20:14 +00:00
committed by Shaowei Shen
parent 2016b1dcd1
commit 0160b80640
8 changed files with 552 additions and 28 deletions

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/device_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:layout_gravity="center_vertical|start"
android:layout_marginStart="16dp"
android:layout_marginEnd="56dp"
android:ellipsize="end"
android:maxLines="1"
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
android:textSize="16sp"/>
</LinearLayout>

View File

@@ -0,0 +1,97 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.media.dialog;
import androidx.annotation.IntDef;
import com.android.settingslib.media.MediaDevice;
import com.android.systemui.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Optional;
/**
* MediaItem represents an item in OutputSwitcher list (could be a MediaDevice, group divider or
* connect new device item).
*/
public class MediaItem {
private final Optional<MediaDevice> mMediaDeviceOptional;
private final String mTitle;
@MediaItemType
private final int mMediaItemType;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
MediaItemType.TYPE_DEVICE,
MediaItemType.TYPE_GROUP_DIVIDER,
MediaItemType.TYPE_PAIR_NEW_DEVICE})
public @interface MediaItemType {
int TYPE_DEVICE = 0;
int TYPE_GROUP_DIVIDER = 1;
int TYPE_PAIR_NEW_DEVICE = 2;
}
public MediaItem() {
this.mMediaDeviceOptional = Optional.empty();
this.mTitle = null;
this.mMediaItemType = MediaItemType.TYPE_PAIR_NEW_DEVICE;
}
public MediaItem(String title, int mediaItemType) {
this.mMediaDeviceOptional = Optional.empty();
this.mTitle = title;
this.mMediaItemType = mediaItemType;
}
public MediaItem(MediaDevice mediaDevice) {
this.mMediaDeviceOptional = Optional.of(mediaDevice);
this.mTitle = mediaDevice.getName();
this.mMediaItemType = MediaItemType.TYPE_DEVICE;
}
public Optional<MediaDevice> getMediaDevice() {
return mMediaDeviceOptional;
}
/**
* Get layout id based on media item Type.
*/
public static int getMediaLayoutId(int mediaItemType) {
switch (mediaItemType) {
case MediaItemType.TYPE_DEVICE:
case MediaItemType.TYPE_PAIR_NEW_DEVICE:
return R.layout.media_output_list_item_advanced;
case MediaItemType.TYPE_GROUP_DIVIDER:
default:
return R.layout.media_output_list_group_divider;
}
}
public String getTitle() {
return mTitle;
}
public boolean isMutingExpectedDevice() {
return mMediaDeviceOptional.isPresent()
&& mMediaDeviceOptional.get().isMutingExpectedDevice();
}
public int getMediaItemType() {
return mMediaItemType;
}
}

View File

@@ -24,9 +24,11 @@ import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.widget.CompoundButtonCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.android.settingslib.media.LocalMediaManager.MediaDeviceState;
import com.android.settingslib.media.MediaDevice;
@@ -48,29 +50,68 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
}
@Override
public MediaDeviceBaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
int viewType) {
super.onCreateViewHolder(viewGroup, viewType);
return new MediaDeviceViewHolder(mHolderView);
if (mController.isAdvancedLayoutSupported()) {
switch (viewType) {
case MediaItem.MediaItemType.TYPE_GROUP_DIVIDER:
return new MediaGroupDividerViewHolder(mHolderView);
case MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE:
case MediaItem.MediaItemType.TYPE_DEVICE:
default:
return new MediaDeviceViewHolder(mHolderView);
}
} else {
return new MediaDeviceViewHolder(mHolderView);
}
}
@Override
public void onBindViewHolder(@NonNull MediaDeviceBaseViewHolder viewHolder, int position) {
final int size = mController.getMediaDevices().size();
if (position == size) {
viewHolder.onBind(CUSTOMIZED_ITEM_PAIR_NEW, false /* topMargin */,
true /* bottomMargin */);
} else if (position < size) {
viewHolder.onBind(((List<MediaDevice>) (mController.getMediaDevices())).get(position),
position == 0 /* topMargin */, position == (size - 1) /* bottomMargin */,
position);
} else if (DEBUG) {
Log.d(TAG, "Incorrect position: " + position);
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
if (mController.isAdvancedLayoutSupported()) {
MediaItem currentMediaItem = mController.getMediaItemList().get(position);
switch (currentMediaItem.getMediaItemType()) {
case MediaItem.MediaItemType.TYPE_GROUP_DIVIDER:
((MediaGroupDividerViewHolder) viewHolder).onBind(currentMediaItem.getTitle());
break;
case MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE:
((MediaDeviceViewHolder) viewHolder).onBind(CUSTOMIZED_ITEM_PAIR_NEW);
break;
case MediaItem.MediaItemType.TYPE_DEVICE:
((MediaDeviceViewHolder) viewHolder).onBind(
currentMediaItem.getMediaDevice().get(),
position);
break;
default:
Log.d(TAG, "Incorrect position: " + position);
}
} else {
final int size = mController.getMediaDevices().size();
if (position == size) {
((MediaDeviceViewHolder) viewHolder).onBind(CUSTOMIZED_ITEM_PAIR_NEW);
} else if (position < size) {
((MediaDeviceViewHolder) viewHolder).onBind(
((List<MediaDevice>) (mController.getMediaDevices())).get(position),
position);
} else if (DEBUG) {
Log.d(TAG, "Incorrect position: " + position);
}
}
}
@Override
public long getItemId(int position) {
if (mController.isAdvancedLayoutSupported()) {
if (position >= mController.getMediaItemList().size()) {
Log.d(TAG, "Incorrect position for item id: " + position);
return position;
}
MediaItem currentMediaItem = mController.getMediaItemList().get(position);
return currentMediaItem.getMediaDevice().isPresent()
? currentMediaItem.getMediaDevice().get().getId().hashCode()
: position;
}
final int size = mController.getMediaDevices().size();
if (position == size) {
return -1;
@@ -83,10 +124,19 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
return position;
}
@Override
public int getItemViewType(int position) {
return mController.isAdvancedLayoutSupported()
? mController.getMediaItemList().get(position).getMediaItemType()
: super.getItemViewType(position);
}
@Override
public int getItemCount() {
// Add extra one for "pair new"
return mController.getMediaDevices().size() + 1;
return mController.isAdvancedLayoutSupported()
? mController.getMediaItemList().size()
: mController.getMediaDevices().size() + 1;
}
class MediaDeviceViewHolder extends MediaDeviceBaseViewHolder {
@@ -96,8 +146,8 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
}
@Override
void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) {
super.onBind(device, topMargin, bottomMargin, position);
void onBind(MediaDevice device, int position) {
super.onBind(device, position);
boolean isMutingExpectedDeviceExist = mController.hasMutingExpectedDevice();
final boolean currentlyConnected = isCurrentlyConnected(device);
boolean isCurrentSeekbarInvisible = mSeekBar.getVisibility() == View.GONE;
@@ -261,7 +311,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
}
@Override
void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) {
void onBind(int customizedItem) {
if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) {
mTitleText.setTextColor(mController.getColorItemContent());
mCheckBox.setVisibility(View.GONE);
@@ -318,4 +368,18 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
: R.string.accessibility_cast_name, device.getName()));
}
}
class MediaGroupDividerViewHolder extends RecyclerView.ViewHolder {
final TextView mTitleText;
MediaGroupDividerViewHolder(@NonNull View itemView) {
super(itemView);
mTitleText = itemView.requireViewById(R.id.title);
}
void onBind(String groupDividerTitle) {
mTitleText.setTextColor(mController.getColorItemContent());
mTitleText.setText(groupDividerTitle);
}
}
}

View File

@@ -58,7 +58,7 @@ import java.util.List;
* Base adapter for media output dialog.
*/
public abstract class MediaOutputBaseAdapter extends
RecyclerView.Adapter<MediaOutputBaseAdapter.MediaDeviceBaseViewHolder> {
RecyclerView.Adapter<RecyclerView.ViewHolder> {
static final int CUSTOMIZED_ITEM_PAIR_NEW = 1;
static final int CUSTOMIZED_ITEM_GROUP = 2;
@@ -80,11 +80,12 @@ public abstract class MediaOutputBaseAdapter extends
}
@Override
public MediaDeviceBaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
int viewType) {
mContext = viewGroup.getContext();
mHolderView = LayoutInflater.from(mContext).inflate(
mController.isAdvancedLayoutSupported() ? R.layout.media_output_list_item_advanced
mController.isAdvancedLayoutSupported() ? MediaItem.getMediaLayoutId(
viewType) /*R.layout.media_output_list_item_advanced*/
: R.layout.media_output_list_item, viewGroup, false);
return null;
@@ -175,7 +176,7 @@ public abstract class MediaOutputBaseAdapter extends
initAnimator();
}
void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) {
void onBind(MediaDevice device, int position) {
mDeviceId = device.getId();
mCheckBox.setVisibility(View.GONE);
mStatusIcon.setVisibility(View.GONE);
@@ -196,7 +197,7 @@ public abstract class MediaOutputBaseAdapter extends
PorterDuff.Mode.SRC_IN));
}
abstract void onBind(int customizedItem, boolean topMargin, boolean bottomMargin);
abstract void onBind(int customizedItem);
void setSingleLineLayout(CharSequence title) {
setSingleLineLayout(title, false, false, false, false);

View File

@@ -93,6 +93,7 @@ import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
import javax.inject.Inject;
@@ -121,6 +122,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
@VisibleForTesting
final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
final List<MediaDevice> mCachedMediaDevices = new CopyOnWriteArrayList<>();
private final List<MediaItem> mMediaItemList = new CopyOnWriteArrayList<>();
private final AudioManager mAudioManager;
private final PowerExemptionManager mPowerExemptionManager;
private final KeyguardManager mKeyGuardManager;
@@ -212,6 +214,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
synchronized (mMediaDevicesLock) {
mCachedMediaDevices.clear();
mMediaDevices.clear();
mMediaItemList.clear();
}
mNearbyDeviceInfoMap.clear();
if (mNearbyMediaDevicesManager != null) {
@@ -262,6 +265,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
synchronized (mMediaDevicesLock) {
mCachedMediaDevices.clear();
mMediaDevices.clear();
mMediaItemList.clear();
}
if (mNearbyMediaDevicesManager != null) {
mNearbyMediaDevicesManager.unregisterNearbyDevicesCallback(this);
@@ -287,7 +291,11 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
public void onSelectedDeviceStateChanged(MediaDevice device,
@LocalMediaManager.MediaDeviceState int state) {
mCallback.onRouteChanged();
mMetricLogger.logOutputSuccess(device.toString(), new ArrayList<>(mMediaDevices));
if (isAdvancedLayoutSupported()) {
mMetricLogger.logOutputItemSuccess(device.toString(), new ArrayList<>(mMediaItemList));
} else {
mMetricLogger.logOutputSuccess(device.toString(), new ArrayList<>(mMediaDevices));
}
}
@Override
@@ -298,7 +306,11 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
@Override
public void onRequestFailed(int reason) {
mCallback.onRouteChanged();
mMetricLogger.logOutputFailure(new ArrayList<>(mMediaDevices), reason);
if (isAdvancedLayoutSupported()) {
mMetricLogger.logOutputItemFailure(new ArrayList<>(mMediaItemList), reason);
} else {
mMetricLogger.logOutputFailure(new ArrayList<>(mMediaDevices), reason);
}
}
/**
@@ -318,6 +330,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
try {
synchronized (mMediaDevicesLock) {
mMediaDevices.removeIf(MediaDevice::isMutingExpectedDevice);
mMediaItemList.removeIf((MediaItem::isMutingExpectedDevice));
}
mAudioManager.cancelMuteAwaitConnection(mAudioManager.getMutingExpectedDevice());
} catch (Exception e) {
@@ -547,6 +560,14 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
}
private void buildMediaDevices(List<MediaDevice> devices) {
if (isAdvancedLayoutSupported()) {
buildMediaItems(devices);
} else {
buildDefaultMediaDevices(devices);
}
}
private void buildDefaultMediaDevices(List<MediaDevice> devices) {
synchronized (mMediaDevicesLock) {
attachRangeInfo(devices);
Collections.sort(devices, Comparator.naturalOrder());
@@ -603,6 +624,81 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
}
}
private void buildMediaItems(List<MediaDevice> devices) {
synchronized (mMediaDevicesLock) {
//TODO(b/257851968): do the organization only when there's no suggested sorted order
// we get from application
attachRangeInfo(devices);
Collections.sort(devices, Comparator.naturalOrder());
// For the first time building list, to make sure the top device is the connected
// device.
if (mMediaItemList.isEmpty()) {
boolean needToHandleMutingExpectedDevice =
hasMutingExpectedDevice() && !isCurrentConnectedDeviceRemote();
final MediaDevice connectedMediaDevice =
needToHandleMutingExpectedDevice ? null
: getCurrentConnectedMediaDevice();
if (connectedMediaDevice == null) {
if (DEBUG) {
Log.d(TAG, "No connected media device or muting expected device exist.");
}
if (needToHandleMutingExpectedDevice) {
for (MediaDevice device : devices) {
if (device.isMutingExpectedDevice()) {
mMediaItemList.add(0, new MediaItem(device));
} else {
mMediaItemList.add(new MediaItem(device));
}
}
} else {
mMediaItemList.addAll(
devices.stream().map(MediaItem::new).collect(Collectors.toList()));
}
categorizeMediaItems();
return;
}
// selected device exist
for (MediaDevice device : devices) {
if (TextUtils.equals(device.getId(), connectedMediaDevice.getId())) {
mMediaItemList.add(0, new MediaItem(device));
} else {
mMediaItemList.add(new MediaItem(device));
}
}
categorizeMediaItems();
return;
}
// To keep the same list order
final List<MediaDevice> targetMediaDevices = new ArrayList<>();
for (MediaItem originalMediaItem : mMediaItemList) {
for (MediaDevice newDevice : devices) {
if (originalMediaItem.getMediaDevice().isPresent()
&& TextUtils.equals(originalMediaItem.getMediaDevice().get().getId(),
newDevice.getId())) {
targetMediaDevices.add(newDevice);
break;
}
}
}
if (targetMediaDevices.size() != devices.size()) {
devices.removeAll(targetMediaDevices);
targetMediaDevices.addAll(devices);
}
mMediaItemList.clear();
mMediaItemList.addAll(
targetMediaDevices.stream().map(MediaItem::new).collect(Collectors.toList()));
categorizeMediaItems();
}
}
private void categorizeMediaItems() {
synchronized (mMediaDevicesLock) {
//TODO(255124239): do the categorization here
mMediaItemList.add(new MediaItem());
}
}
private void attachRangeInfo(List<MediaDevice> devices) {
for (MediaDevice mediaDevice : devices) {
if (mNearbyDeviceInfoMap.containsKey(mediaDevice.getId())) {
@@ -670,6 +766,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
return mMediaDevices;
}
public List<MediaItem> getMediaItemList() {
return mMediaItemList;
}
MediaDevice getCurrentConnectedMediaDevice() {
return mLocalMediaManager.getCurrentConnectedDevice();
}
@@ -749,9 +849,19 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
boolean isAnyDeviceTransferring() {
synchronized (mMediaDevicesLock) {
for (MediaDevice device : mMediaDevices) {
if (device.getState() == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) {
return true;
if (isAdvancedLayoutSupported()) {
for (MediaItem mediaItem : mMediaItemList) {
if (mediaItem.getMediaDevice().isPresent()
&& mediaItem.getMediaDevice().get().getState()
== LocalMediaManager.MediaDeviceState.STATE_CONNECTING) {
return true;
}
}
} else {
for (MediaDevice device : mMediaDevices) {
if (device.getState() == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) {
return true;
}
}
}
}

View File

@@ -95,6 +95,31 @@ public class MediaOutputMetricLogger {
mAppliedDeviceCountWithinRemoteGroup);
}
/**
* Do the metric logging of content switching success.
* @param selectedDeviceType string representation of the target media device
* @param deviceItemList media item list for device count updating
*/
public void logOutputItemSuccess(String selectedDeviceType, List<MediaItem> deviceItemList) {
if (DEBUG) {
Log.d(TAG, "logOutputSuccess - selected device: " + selectedDeviceType);
}
updateLoggingMediaItemCount(deviceItemList);
SysUiStatsLog.write(
SysUiStatsLog.MEDIAOUTPUT_OP_SWITCH_REPORTED,
getLoggingDeviceType(mSourceDevice, true),
getLoggingDeviceType(mTargetDevice, false),
SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__RESULT__OK,
SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__NO_ERROR,
getLoggingPackageName(),
mWiredDeviceCount,
mConnectedBluetoothDeviceCount,
mRemoteDeviceCount,
mAppliedDeviceCountWithinRemoteGroup);
}
/**
* Do the metric logging of volume adjustment.
* @param source the device been adjusted
@@ -166,6 +191,31 @@ public class MediaOutputMetricLogger {
mAppliedDeviceCountWithinRemoteGroup);
}
/**
* Do the metric logging of content switching failure.
* @param deviceItemList media item list for device count updating
* @param reason the reason of content switching failure
*/
public void logOutputItemFailure(List<MediaItem> deviceItemList, int reason) {
if (DEBUG) {
Log.e(TAG, "logRequestFailed - " + reason);
}
updateLoggingMediaItemCount(deviceItemList);
SysUiStatsLog.write(
SysUiStatsLog.MEDIAOUTPUT_OP_SWITCH_REPORTED,
getLoggingDeviceType(mSourceDevice, true),
getLoggingDeviceType(mTargetDevice, false),
SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__RESULT__ERROR,
getLoggingSwitchOpSubResult(reason),
getLoggingPackageName(),
mWiredDeviceCount,
mConnectedBluetoothDeviceCount,
mRemoteDeviceCount,
mAppliedDeviceCountWithinRemoteGroup);
}
private void updateLoggingDeviceCount(List<MediaDevice> deviceList) {
mWiredDeviceCount = mConnectedBluetoothDeviceCount = mRemoteDeviceCount = 0;
mAppliedDeviceCountWithinRemoteGroup = 0;
@@ -196,6 +246,37 @@ public class MediaOutputMetricLogger {
}
}
private void updateLoggingMediaItemCount(List<MediaItem> deviceItemList) {
mWiredDeviceCount = mConnectedBluetoothDeviceCount = mRemoteDeviceCount = 0;
mAppliedDeviceCountWithinRemoteGroup = 0;
for (MediaItem mediaItem: deviceItemList) {
if (mediaItem.getMediaDevice().isPresent()
&& mediaItem.getMediaDevice().get().isConnected()) {
switch (mediaItem.getMediaDevice().get().getDeviceType()) {
case MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE:
case MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE:
mWiredDeviceCount++;
break;
case MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE:
mConnectedBluetoothDeviceCount++;
break;
case MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE:
case MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE:
mRemoteDeviceCount++;
break;
default:
}
}
}
if (DEBUG) {
Log.d(TAG, "connected devices:" + " wired: " + mWiredDeviceCount
+ " bluetooth: " + mConnectedBluetoothDeviceCount
+ " remote: " + mRemoteDeviceCount);
}
}
private int getLoggingDeviceType(MediaDevice device, boolean isSourceDevice) {
if (device == null) {
return isSourceDevice

View File

@@ -46,6 +46,7 @@ import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -71,10 +72,13 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
private MediaOutputAdapter mMediaOutputAdapter;
private MediaOutputAdapter.MediaDeviceViewHolder mViewHolder;
private List<MediaDevice> mMediaDevices = new ArrayList<>();
private List<MediaItem> mMediaItems = new ArrayList<>();
MediaOutputSeekbar mSpyMediaOutputSeekbar;
@Before
public void setUp() {
when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(false);
when(mMediaOutputController.getMediaItemList()).thenReturn(mMediaItems);
when(mMediaOutputController.getMediaDevices()).thenReturn(mMediaDevices);
when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(false);
when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false);
@@ -82,7 +86,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
when(mMediaOutputController.getDeviceIconCompat(mMediaDevice2)).thenReturn(mIconCompat);
when(mMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(mMediaDevice1);
when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(true);
when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(false);
when(mIconCompat.toIcon(mContext)).thenReturn(mIcon);
when(mMediaDevice1.getName()).thenReturn(TEST_DEVICE_NAME_1);
when(mMediaDevice1.getId()).thenReturn(TEST_DEVICE_ID_1);
@@ -94,6 +97,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
mMediaDevices.add(mMediaDevice1);
mMediaDevices.add(mMediaDevice2);
mMediaItems.add(new MediaItem(mMediaDevice1));
mMediaItems.add(new MediaItem(mMediaDevice2));
mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
@@ -118,6 +123,26 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
assertThat(mMediaOutputAdapter.getItemId(invalidPosition)).isEqualTo(invalidPosition);
}
@Test
public void advanced_getItemCount_returnsMediaItemSize() {
when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
assertThat(mMediaOutputAdapter.getItemCount()).isEqualTo(mMediaItems.size());
}
@Test
public void advanced_getItemId_validPosition_returnCorrespondingId() {
when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
assertThat(mMediaOutputAdapter.getItemId(0)).isEqualTo(mMediaItems.get(
0).getMediaDevice().get().getId().hashCode());
}
@Test
public void advanced_getItemId_invalidPosition_returnPosition() {
when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
int invalidPosition = mMediaItems.size() + 1;
assertThat(mMediaOutputAdapter.getItemId(invalidPosition)).isEqualTo(invalidPosition);
}
@Test
public void onBindViewHolder_bindPairNew_verifyView() {
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2);
@@ -158,6 +183,63 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void advanced_onBindViewHolder_bindPairNew_verifyView() {
when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaItems.add(new MediaItem());
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2);
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mTitleText.getText()).isEqualTo(mContext.getText(
R.string.media_output_dialog_pairing_new));
}
@Test
public void advanced_onBindViewHolder_bindGroup_withSessionName_verifyView() {
when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(
mMediaItems.stream().map((item) -> item.getMediaDevice().get()).collect(
Collectors.toList()));
when(mMediaOutputController.getSessionName()).thenReturn(TEST_SESSION_NAME);
mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.getItemCount();
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void advanced_onBindViewHolder_bindGroup_noSessionName_verifyView() {
when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(
mMediaItems.stream().map((item) -> item.getMediaDevice().get()).collect(
Collectors.toList()));
when(mMediaOutputController.getSessionName()).thenReturn(null);
mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.getItemCount();
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void onBindViewHolder_bindConnectedDevice_verifyView() {
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -375,6 +457,19 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
verify(mMediaOutputController).launchBluetoothPairing(mViewHolder.mContainerLayout);
}
@Test
public void advanced_onItemClick_clickPairNew_verifyLaunchBluetoothPairing() {
when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaItems.add(new MediaItem());
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2);
mViewHolder.mContainerLayout.performClick();
verify(mMediaOutputController).launchBluetoothPairing(mViewHolder.mContainerLayout);
}
@Test
public void onItemClick_clickDevice_verifyConnectDevice() {
assertThat(mMediaDevice2.getState()).isEqualTo(
@@ -457,6 +552,26 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
assertThat(mViewHolder.mSeekBar.isEnabled()).isFalse();
}
@Test
public void advanced_onItemClick_onGroupActionTriggered_verifySeekbarDisabled() {
when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(
mMediaItems.stream().map((item) -> item.getMediaDevice().get()).collect(
Collectors.toList()));
mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
List<MediaDevice> selectableDevices = new ArrayList<>();
selectableDevices.add(mMediaDevice1);
when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices);
when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(true);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
mViewHolder.mContainerLayout.performClick();
assertThat(mViewHolder.mSeekBar.isEnabled()).isFalse();
}
@Test
public void onBindViewHolder_volumeControlChangeToEnabled_enableSeekbarAgain() {
when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(false);

View File

@@ -63,6 +63,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -144,6 +145,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
mKeyguardManager, mFlags);
when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT)).thenReturn(false);
mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
MediaDescription.Builder builder = new MediaDescription.Builder();
@@ -281,6 +283,25 @@ public class MediaOutputControllerTest extends SysuiTestCase {
verify(mCb).onDeviceListChanged();
}
@Test
public void advanced_onDeviceListUpdate_verifyDeviceListCallback() {
when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT)).thenReturn(true);
mMediaOutputController.start(mCb);
reset(mCb);
mMediaOutputController.onDeviceListUpdate(mMediaDevices);
final List<MediaDevice> devices = new ArrayList<>();
for (MediaItem item : mMediaOutputController.getMediaItemList()) {
if (item.getMediaDevice().isPresent()) {
devices.add(item.getMediaDevice().get());
}
}
assertThat(devices.containsAll(mMediaDevices)).isTrue();
assertThat(devices.size()).isEqualTo(mMediaDevices.size());
verify(mCb).onDeviceListChanged();
}
@Test
public void onDeviceListUpdate_isRefreshing_updatesNeedRefreshToTrue() {
mMediaOutputController.start(mCb);