summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/res/layout/media_output_list_group_divider.xml35
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaItem.java97
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java98
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java120
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java81
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java117
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java21
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);