diff options
| author | 2022-12-05 15:20:14 +0000 | |
|---|---|---|
| committer | 2022-12-12 09:52:13 +0000 | |
| commit | 0160b80640201304c8c1ff70f711e70922628149 (patch) | |
| tree | ec3d5ebd9654f15c0fae6ce1f484d833f2b21142 | |
| parent | 2016b1dcd1410e2cc03cb4bb44a3d988f97a37f4 (diff) | |
[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
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); |