Merge "[Output Switcher] Update device selection layout" into tm-qpr-dev

This commit is contained in:
Shaowei Shen
2022-12-08 06:22:40 +00:00
committed by Android (Google) Code Review
11 changed files with 255 additions and 39 deletions

View File

@@ -18,7 +18,7 @@
<item
android:id="@+id/checked"
android:state_checked="true"
android:drawable="@drawable/media_output_status_check" />
android:drawable="@drawable/media_output_status_filled_checked" />
<item
android:id="@+id/unchecked"
android:state_checked="false"

View File

@@ -0,0 +1,26 @@
<!--
~ 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/checked"
android:state_checked="true"
android:drawable="@drawable/media_output_status_checked" />
<item
android:id="@+id/unchecked"
android:state_checked="false"
android:drawable="@drawable/media_output_status_selectable" />
</selector>

View File

@@ -0,0 +1,26 @@
<!--
~ 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M9.55,18 L3.85,12.3 5.275,10.875 9.55,15.15 18.725,5.975 20.15,7.4Z"/>
</vector>

View File

@@ -0,0 +1,26 @@
<!--
~ 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M11,19V13H5V11H11V5H13V11H19V13H13V19Z"/>
</vector>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 The Android Open Source Project
~ Copyright (C) 2020 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.
@@ -15,23 +15,22 @@
~ limitations under the License.
-->
<LinearLayout
<FrameLayout
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">
android:layout_height="wrap_content">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="64dp"
android:id="@+id/item_layout"
android:background="@drawable/media_output_item_background"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginEnd="80dp"
android:layout_marginBottom="12dp">
<FrameLayout
android:id="@+id/item_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/media_output_item_background"
android:layout_gravity="center_vertical|start">
<com.android.systemui.media.dialog.MediaOutputSeekbar
android:id="@+id/volume_seekbar"
@@ -132,27 +131,26 @@
android:indeterminateOnly="true"
android:importantForAccessibility="no"
android:visibility="gone"/>
<LinearLayout
android:id="@+id/end_action_area"
android:visibility="gone"
android:orientation="vertical"
android:layout_width="48dp"
android:layout_height="64dp"
android:layout_gravity="end|center"
android:gravity="center_vertical">
<CheckBox
android:id="@+id/check_box"
android:focusable="false"
android:importantForAccessibility="no"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="16dp"
android:layout_gravity="end"
android:button="@drawable/ic_circle_check_box"
android:visibility="gone"
/>
</LinearLayout>
</FrameLayout>
</LinearLayout>
<FrameLayout
android:id="@+id/end_action_area"
android:layout_width="64dp"
android:layout_height="64dp"
android:visibility="gone"
android:layout_marginBottom="6dp"
android:layout_marginEnd="8dp"
android:layout_gravity="end|center"
android:gravity="center"
android:background="@drawable/media_output_item_background_active">
<CheckBox
android:id="@+id/check_box"
android:focusable="false"
android:importantForAccessibility="no"
android:layout_gravity="center"
android:layout_width="24dp"
android:layout_height="24dp"
android:button="@drawable/media_output_item_check_box"
android:visibility="gone"
/>
</FrameLayout>
</FrameLayout>

View File

@@ -1208,6 +1208,8 @@
<dimen name="media_output_dialog_app_tier_icon_size">20dp</dimen>
<dimen name="media_output_dialog_background_radius">16dp</dimen>
<dimen name="media_output_dialog_active_background_radius">28dp</dimen>
<dimen name="media_output_dialog_default_margin_end">16dp</dimen>
<dimen name="media_output_dialog_selectable_margin_end">80dp</dimen>
<!-- Distance that the full shade transition takes in order to complete by tapping on a button
like "expand". -->

View File

@@ -166,6 +166,20 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
setUpDeviceIcon(device);
updateFullItemClickListener(v -> cancelMuteAwaitConnection());
setSingleLineLayout(getItemTitle(device));
} else if (mController.isCurrentConnectedDeviceRemote()
&& !mController.getSelectableMediaDevice().isEmpty()
&& mController.isAdvancedLayoutSupported()) {
//If device is connected and there's other selectable devices, layout as
// one of selected devices.
boolean isDeviceDeselectable = isDeviceIncluded(
mController.getDeselectableMediaDevice(), device);
updateGroupableCheckBox(true, isDeviceDeselectable, device);
updateEndClickArea(device, isDeviceDeselectable);
setUpContentDescriptionForView(mContainerLayout, false, device);
setSingleLineLayout(getItemTitle(device), true /* showSeekBar */,
false /* showProgressBar */, true /* showCheckBox */,
true /* showEndTouchArea */);
initSeekbar(device, isCurrentSeekbarInvisible);
} else {
updateTitleIcon(R.drawable.media_output_icon_volume,
mController.getColorItemContent());
@@ -179,7 +193,12 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
} else if (isDeviceIncluded(mController.getSelectableMediaDevice(), device)) {
setUpDeviceIcon(device);
updateGroupableCheckBox(false, true, device);
updateFullItemClickListener(v -> onGroupActionTriggered(true, device));
if (mController.isAdvancedLayoutSupported()) {
updateEndClickArea(device, true);
}
updateFullItemClickListener(mController.isAdvancedLayoutSupported()
? v -> onItemClick(v, device)
: v -> onGroupActionTriggered(true, device));
setSingleLineLayout(getItemTitle(device), false /* showSeekBar */,
false /* showProgressBar */, true /* showCheckBox */,
true /* showEndTouchArea */);
@@ -217,6 +236,11 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
isDeviceDeselectable ? (v) -> mCheckBox.performClick() : null);
mEndTouchArea.setImportantForAccessibility(
View.IMPORTANT_FOR_ACCESSIBILITY_YES);
if (mController.isAdvancedLayoutSupported()) {
mEndTouchArea.getBackground().setColorFilter(
new PorterDuffColorFilter(mController.getColorItemBackground(),
PorterDuff.Mode.SRC_IN));
}
setUpContentDescriptionForView(mEndTouchArea, true, device);
}

View File

@@ -133,7 +133,7 @@ public abstract class MediaOutputBaseAdapter extends
private static final int ANIM_DURATION = 500;
final LinearLayout mContainerLayout;
final ViewGroup mContainerLayout;
final FrameLayout mItemLayout;
final FrameLayout mIconAreaLayout;
final TextView mTitleText;
@@ -146,7 +146,7 @@ public abstract class MediaOutputBaseAdapter extends
final LinearLayout mTwoLineLayout;
final ImageView mStatusIcon;
final CheckBox mCheckBox;
final LinearLayout mEndTouchArea;
final ViewGroup mEndTouchArea;
private String mDeviceId;
private ValueAnimator mCornerAnimator;
private ValueAnimator mVolumeAnimator;
@@ -253,6 +253,13 @@ public abstract class MediaOutputBaseAdapter extends
mTitleText.setVisibility(View.VISIBLE);
mCheckBox.setVisibility(showCheckBox ? View.VISIBLE : View.GONE);
mEndTouchArea.setVisibility(showEndTouchArea ? View.VISIBLE : View.GONE);
if (mController.isAdvancedLayoutSupported()) {
ViewGroup.MarginLayoutParams params =
(ViewGroup.MarginLayoutParams) mItemLayout.getLayoutParams();
params.rightMargin = showEndTouchArea ? mController.getItemMarginEndSelectable()
: mController.getItemMarginEndDefault();
}
mTitleIcon.setColorFilter(mController.getColorItemContent());
}
void setTwoLineLayout(MediaDevice device, boolean bFocused, boolean showSeekBar,
@@ -531,6 +538,7 @@ public abstract class MediaOutputBaseAdapter extends
return;
}
mTitleIcon.setImageIcon(icon);
mTitleIcon.setColorFilter(mController.getColorItemContent());
});
});
}

View File

@@ -147,6 +147,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
private int mColorConnectedItemBackground;
private int mColorPositiveButtonText;
private int mColorDialogBackground;
private int mItemMarginEndDefault;
private int mItemMarginEndSelectable;
private float mInactiveRadius;
private float mActiveRadius;
private FeatureFlags mFeatureFlags;
@@ -200,6 +202,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
R.dimen.media_output_dialog_active_background_radius);
mColorDialogBackground = Utils.getColorStateListDefaultColor(mContext,
R.color.media_dialog_background);
mItemMarginEndDefault = (int) mContext.getResources().getDimension(
R.dimen.media_output_dialog_default_margin_end);
mItemMarginEndSelectable = (int) mContext.getResources().getDimension(
R.dimen.media_output_dialog_selectable_margin_end);
}
void start(@NonNull Callback cb) {
@@ -532,6 +538,14 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
return mActiveRadius;
}
public int getItemMarginEndDefault() {
return mItemMarginEndDefault;
}
public int getItemMarginEndSelectable() {
return mItemMarginEndSelectable;
}
private void buildMediaDevices(List<MediaDevice> devices) {
synchronized (mMediaDevicesLock) {
attachRangeInfo(devices);

View File

@@ -38,6 +38,8 @@ import com.android.settingslib.media.MediaDevice;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -73,11 +75,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Before
public void setUp() {
mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mSpyMediaOutputSeekbar = spy(mViewHolder.mSeekBar);
when(mMediaOutputController.getMediaDevices()).thenReturn(mMediaDevices);
when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(false);
when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false);
@@ -85,6 +82,7 @@ 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);
@@ -96,6 +94,11 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
mMediaDevices.add(mMediaDevice1);
mMediaDevices.add(mMediaDevice2);
mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mSpyMediaOutputSeekbar = spy(mViewHolder.mSeekBar);
}
@Test
@@ -168,6 +171,63 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void advanced_onBindViewHolder_bindNonRemoteConnectedDevice_verifyView() {
when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void advanced_onBindViewHolder_bindConnectedRemoteDevice_verifyView() {
when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
ImmutableList.of(mMediaDevice2));
when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void advanced_onBindViewHolder_bindSingleConnectedRemoteDevice_verifyView() {
when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
ImmutableList.of());
when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.GONE);
}
@Test
public void onBindViewHolder_bindConnectedDeviceWithMutingExpectedDeviceExist_verifyView() {
when(mMediaOutputController.hasMutingExpectedDevice()).thenReturn(true);
@@ -351,6 +411,38 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
verify(mMediaOutputController).addDeviceToPlayMedia(mMediaDevice2);
}
@Test
public void advanced_onGroupActionTriggered_clicksEndAreaOfSelectableDevice_triggerGrouping() {
when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
List<MediaDevice> selectableDevices = new ArrayList<>();
selectableDevices.add(mMediaDevice2);
when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
mViewHolder.mEndTouchArea.performClick();
verify(mMediaOutputController).addDeviceToPlayMedia(mMediaDevice2);
}
@Test
public void advanced_onGroupActionTriggered_clickSelectedRemoteDevice_triggerUngrouping() {
when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
ImmutableList.of(mMediaDevice2));
when(mMediaOutputController.getDeselectableMediaDevice()).thenReturn(
ImmutableList.of(mMediaDevice1));
when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
mViewHolder.mEndTouchArea.performClick();
verify(mMediaOutputController).removeDeviceFromPlayMedia(mMediaDevice1);
}
@Test
public void onItemClick_onGroupActionTriggered_verifySeekbarDisabled() {
when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(mMediaDevices);