diff options
8 files changed, 552 insertions, 28 deletions
diff --git a/packages/SystemUI/res/layout/media_output_list_group_divider.xml b/packages/SystemUI/res/layout/media_output_list_group_divider.xml new file mode 100644 index 000000000000..5e96866c0a9a --- /dev/null +++ b/packages/SystemUI/res/layout/media_output_list_group_divider.xml @@ -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>
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaItem.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaItem.java new file mode 100644 index 000000000000..875a01087908 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaItem.java @@ -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; + } +} 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 3dccae03e906..fb47d97ed1d4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -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; @@ -84,9 +125,18 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { } @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); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java index db62e51be7e7..3b1d861d0312 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -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); 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 9b361e3edc57..b43656207821 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -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; + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java index 7d3e82c9d47f..2250d72d8658 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java @@ -96,6 +96,31 @@ public class MediaOutputMetricLogger { } /** + * 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 diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java index 5c0f0fee096a..7c3c9d2a1bb1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java @@ -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 @@ -119,6 +124,26 @@ public class MediaOutputAdapterTest extends SysuiTestCase { } @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); @@ -159,6 +184,63 @@ public class MediaOutputAdapterTest extends SysuiTestCase { } @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); @@ -376,6 +458,19 @@ public class MediaOutputAdapterTest extends SysuiTestCase { } @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( LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED); @@ -458,6 +553,26 @@ public class MediaOutputAdapterTest extends SysuiTestCase { } @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); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java index c544c0e02d34..71c300c3fb6f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java @@ -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(); @@ -282,6 +284,25 @@ public class MediaOutputControllerTest extends SysuiTestCase { } @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); reset(mCb); |