diff options
15 files changed, 426 insertions, 42 deletions
diff --git a/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml b/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml index 55dce8f7e66c..43cf00332f63 100644 --- a/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml +++ b/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml @@ -17,7 +17,10 @@ <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@android:id/background"> <shape> - <corners android:radius="28dp" /> + <corners + android:bottomRightRadius="28dp" + android:topRightRadius="28dp" + /> <solid android:color="@android:color/transparent" /> <size android:height="64dp"/> diff --git a/packages/SystemUI/res/drawable/media_output_icon_volume_off.xml b/packages/SystemUI/res/drawable/media_output_icon_volume_off.xml new file mode 100644 index 000000000000..f29f44ce8226 --- /dev/null +++ b/packages/SystemUI/res/drawable/media_output_icon_volume_off.xml @@ -0,0 +1,27 @@ +<!-- + ~ 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="48dp" + android:height="48dp" + android:viewportWidth="48" + android:viewportHeight="48" + android:tint="?attr/colorControlNormal" + android:autoMirrored="true"> + <path + android:fillColor="@color/media_dialog_item_main_content" + android:pathData="M40.65,45.2 L34.05,38.6Q32.65,39.6 31.025,40.325Q29.4,41.05 27.65,41.45V38.35Q28.8,38 29.875,37.575Q30.95,37.15 31.9,36.45L23.65,28.15V40L13.65,30H5.65V18H13.45L2.45,7L4.6,4.85L42.8,43ZM38.85,33.6 L36.7,31.45Q37.7,29.75 38.175,27.85Q38.65,25.95 38.65,23.95Q38.65,18.8 35.65,14.725Q32.65,10.65 27.65,9.55V6.45Q33.85,7.85 37.75,12.725Q41.65,17.6 41.65,23.95Q41.65,26.5 40.95,28.95Q40.25,31.4 38.85,33.6ZM32.15,26.9 L27.65,22.4V15.9Q30,17 31.325,19.2Q32.65,21.4 32.65,24Q32.65,24.75 32.525,25.475Q32.4,26.2 32.15,26.9ZM23.65,18.4 L18.45,13.2 23.65,8ZM20.65,32.7V25.2L16.45,21H8.65V27H14.95ZM18.55,23.1Z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/media_output_title_icon_area.xml b/packages/SystemUI/res/drawable/media_output_title_icon_area.xml new file mode 100644 index 000000000000..b93793773179 --- /dev/null +++ b/packages/SystemUI/res/drawable/media_output_title_icon_area.xml @@ -0,0 +1,25 @@ +<!-- + ~ 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. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <corners + android:bottomLeftRadius="28dp" + android:topLeftRadius="28dp" + android:bottomRightRadius="0dp" + android:topRightRadius="0dp"/> + <solid android:color="@color/media_dialog_item_background" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml new file mode 100644 index 000000000000..9d0bfd74af8e --- /dev/null +++ b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml @@ -0,0 +1,158 @@ +<?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"> + <FrameLayout + android:layout_width="match_parent" + android:layout_height="64dp" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + 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" + android:splitTrack="false" + android:visibility="gone" + android:paddingStart="64dp" + android:paddingEnd="0dp" + android:background="@null" + android:contentDescription="@string/media_output_dialog_accessibility_seekbar" + android:progressDrawable="@drawable/media_output_dialog_seekbar_background" + android:thumb="@null" + android:layout_width="match_parent" + android:layout_height="match_parent"/> + </FrameLayout> + + <FrameLayout + android:id="@+id/icon_area" + android:layout_width="64dp" + android:layout_height="64dp" + android:background="@drawable/media_output_title_icon_area" + android:layout_gravity="center_vertical|start"> + <ImageView + android:id="@+id/title_icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:animateLayoutChanges="true" + android:layout_gravity="center"/> + <TextView + android:id="@+id/volume_value" + android:animateLayoutChanges="true" + android:layout_gravity="center" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:fontFamily="@*android:string/config_headlineFontFamilyMedium" + android:textSize="16sp" + android:visibility="gone"/> + </FrameLayout> + + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|start" + android:layout_marginStart="72dp" + android:layout_marginEnd="56dp" + android:ellipsize="end" + android:maxLines="1" + android:fontFamily="@*android:string/config_headlineFontFamilyMedium" + android:textSize="16sp"/> + + <LinearLayout + android:id="@+id/two_line_layout" + android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_gravity="center_vertical|start" + android:layout_height="48dp" + android:layout_marginEnd="56dp" + android:layout_marginStart="72dp"> + <TextView + android:id="@+id/two_line_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="end" + android:maxLines="1" + android:fontFamily="@*android:string/config_headlineFontFamilyMedium" + android:textColor="@color/media_dialog_item_main_content" + android:textSize="16sp"/> + <TextView + android:id="@+id/subtitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="end" + android:maxLines="1" + android:textColor="@color/media_dialog_item_main_content" + android:textSize="14sp" + android:fontFamily="@*android:string/config_bodyFontFamily" + android:visibility="gone"/> + </LinearLayout> + + <ProgressBar + android:id="@+id/volume_indeterminate_progress" + style="?android:attr/progressBarStyleSmallTitle" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_marginEnd="16dp" + android:indeterminate="true" + android:layout_gravity="end|center" + android:indeterminateOnly="true" + android:visibility="gone"/> + + <ImageView + android:id="@+id/media_output_item_status" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_marginEnd="16dp" + android:indeterminate="true" + android:layout_gravity="end|center" + 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>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 30788cf90e6a..76816c576524 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2377,7 +2377,7 @@ <!-- Controls menu, edit [CHAR_LIMIT=30] --> <string name="controls_menu_edit">Edit controls</string> - <!-- Title for the media output group dialog with media related devices [CHAR LIMIT=50] --> + <!-- Title for the media output dialog with media related devices [CHAR LIMIT=50] --> <string name="media_output_dialog_add_output">Add outputs</string> <!-- Title for the media output slice with group devices [CHAR LIMIT=50] --> <string name="media_output_dialog_group">Group</string> @@ -2401,6 +2401,8 @@ <string name="media_output_dialog_accessibility_title">Available devices for audio output.</string> <!-- Accessibility text describing purpose of seekbar in media output dialog. [CHAR LIMIT=NONE] --> <string name="media_output_dialog_accessibility_seekbar">Volume</string> + <!-- Summary for media output volume of a device in percentage [CHAR LIMIT=NONE] --> + <string name="media_output_dialog_volume_percentage"><xliff:g id="percentage" example="10">%1$d</xliff:g>%%</string> <!-- Media Output Broadcast Dialog --> <!-- Title for Broadcast First Notify Dialog [CHAR LIMIT=60] --> diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 7c05356dd5fc..a113ff2e3c44 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -436,6 +436,11 @@ object Flags { val WARN_ON_BLOCKING_BINDER_TRANSACTIONS = unreleasedFlag(2400, "warn_on_blocking_binder_transactions") + // 2500 - output switcher + // TODO(b/261538825): Tracking Bug + @JvmField + val OUTPUT_SWITCHER_ADVANCED_LAYOUT = unreleasedFlag(2500, "output_switcher_advanced_layout") + // TODO(b259590361): Tracking bug val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release") } 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 ee5956105b7b..72d4e947b30f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -16,7 +16,6 @@ package com.android.systemui.media.dialog; -import android.annotation.DrawableRes; import android.content.res.ColorStateList; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; @@ -122,17 +121,19 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { // Set different layout for each device if (device.isMutingExpectedDevice() && !mController.isCurrentConnectedDeviceRemote()) { - updateTitleIcon(R.drawable.media_output_icon_volume, - mController.getColorItemContent()); + if (!mController.isAdvancedLayoutSupported()) { + updateTitleIcon(R.drawable.media_output_icon_volume, + mController.getColorItemContent()); + } initMutingExpectedDevice(); mCurrentActivePosition = position; - updateContainerClickListener(v -> onItemClick(v, device)); + updateFullItemClickListener(v -> onItemClick(v, device)); setSingleLineLayout(getItemTitle(device)); } else if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) { setUpDeviceIcon(device); updateConnectionFailedStatusIcon(); mSubTitleText.setText(R.string.media_output_dialog_connect_failed); - updateContainerClickListener(v -> onItemClick(v, device)); + updateFullItemClickListener(v -> onItemClick(v, device)); setTwoLineLayout(device, false /* bFocused */, false /* showSeekBar */, false /* showProgressBar */, true /* showSubtitle */, true /* showStatus */); @@ -146,8 +147,10 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { && isDeviceIncluded(mController.getSelectedMediaDevice(), device)) { boolean isDeviceDeselectable = isDeviceIncluded( mController.getDeselectableMediaDevice(), device); - updateTitleIcon(R.drawable.media_output_icon_volume, - mController.getColorItemContent()); + if (!mController.isAdvancedLayoutSupported()) { + updateTitleIcon(R.drawable.media_output_icon_volume, + mController.getColorItemContent()); + } updateGroupableCheckBox(true, isDeviceDeselectable, device); updateEndClickArea(device, isDeviceDeselectable); setUpContentDescriptionForView(mContainerLayout, false, device); @@ -161,7 +164,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { && !mController.isCurrentConnectedDeviceRemote()) { // mark as disconnected and set special click listener setUpDeviceIcon(device); - updateContainerClickListener(v -> cancelMuteAwaitConnection()); + updateFullItemClickListener(v -> cancelMuteAwaitConnection()); setSingleLineLayout(getItemTitle(device)); } else { updateTitleIcon(R.drawable.media_output_icon_volume, @@ -176,14 +179,14 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { } else if (isDeviceIncluded(mController.getSelectableMediaDevice(), device)) { setUpDeviceIcon(device); updateGroupableCheckBox(false, true, device); - updateContainerClickListener(v -> onGroupActionTriggered(true, device)); + updateFullItemClickListener(v -> onGroupActionTriggered(true, device)); setSingleLineLayout(getItemTitle(device), false /* showSeekBar */, false /* showProgressBar */, true /* showCheckBox */, true /* showEndTouchArea */); } else { setUpDeviceIcon(device); setSingleLineLayout(getItemTitle(device)); - updateContainerClickListener(v -> onItemClick(v, device)); + updateFullItemClickListener(v -> onItemClick(v, device)); } } } @@ -228,13 +231,9 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { setCheckBoxColor(mCheckBox, mController.getColorItemContent()); } - private void updateTitleIcon(@DrawableRes int id, int color) { - mTitleIcon.setImageDrawable(mContext.getDrawable(id)); - mTitleIcon.setColorFilter(color); - } - - private void updateContainerClickListener(View.OnClickListener listener) { + private void updateFullItemClickListener(View.OnClickListener listener) { mContainerLayout.setOnClickListener(listener); + updateIconAreaClickListener(listener); } @Override @@ -246,6 +245,11 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { final Drawable addDrawable = mContext.getDrawable(R.drawable.ic_add); mTitleIcon.setImageDrawable(addDrawable); mTitleIcon.setColorFilter(mController.getColorItemContent()); + if (mController.isAdvancedLayoutSupported()) { + mIconAreaLayout.getBackground().setColorFilter( + new PorterDuffColorFilter(mController.getColorItemBackground(), + PorterDuff.Mode.SRC_IN)); + } mContainerLayout.setOnClickListener(mController::launchBluetoothPairing); } } 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 3f7b2261ea52..8987e7ca9efb 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -16,8 +16,11 @@ package com.android.systemui.media.dialog; +import static com.android.systemui.media.dialog.MediaOutputSeekbar.VOLUME_PERCENTAGE_SCALE_SIZE; + import android.animation.Animator; import android.animation.ValueAnimator; +import android.annotation.DrawableRes; import android.app.WallpaperColors; import android.content.Context; import android.graphics.PorterDuff; @@ -80,8 +83,9 @@ public abstract class MediaOutputBaseAdapter extends public MediaDeviceBaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) { mContext = viewGroup.getContext(); - mHolderView = LayoutInflater.from(mContext).inflate(R.layout.media_output_list_item, - viewGroup, false); + mHolderView = LayoutInflater.from(mContext).inflate( + mController.isAdvancedLayoutSupported() ? R.layout.media_output_list_item_advanced + : R.layout.media_output_list_item, viewGroup, false); return null; } @@ -131,9 +135,11 @@ public abstract class MediaOutputBaseAdapter extends final LinearLayout mContainerLayout; final FrameLayout mItemLayout; + final FrameLayout mIconAreaLayout; final TextView mTitleText; final TextView mTwoLineTitleText; final TextView mSubTitleText; + final TextView mVolumeValueText; final ImageView mTitleIcon; final ProgressBar mProgressBar; final MediaOutputSeekbar mSeekBar; @@ -159,6 +165,13 @@ public abstract class MediaOutputBaseAdapter extends mStatusIcon = view.requireViewById(R.id.media_output_item_status); mCheckBox = view.requireViewById(R.id.check_box); mEndTouchArea = view.requireViewById(R.id.end_action_area); + if (mController.isAdvancedLayoutSupported()) { + mVolumeValueText = view.requireViewById(R.id.volume_value); + mIconAreaLayout = view.requireViewById(R.id.icon_area); + } else { + mVolumeValueText = null; + mIconAreaLayout = null; + } initAnimator(); } @@ -170,9 +183,14 @@ public abstract class MediaOutputBaseAdapter extends mEndTouchArea.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); mContainerLayout.setOnClickListener(null); mContainerLayout.setContentDescription(null); + mTitleIcon.setOnClickListener(null); mTitleText.setTextColor(mController.getColorItemContent()); mSubTitleText.setTextColor(mController.getColorItemContent()); mTwoLineTitleText.setTextColor(mController.getColorItemContent()); + if (mController.isAdvancedLayoutSupported()) { + mIconAreaLayout.setOnClickListener(null); + mVolumeValueText.setTextColor(mController.getColorItemContent()); + } mSeekBar.getProgressDrawable().setColorFilter( new PorterDuffColorFilter(mController.getColorSeekbarProgress(), PorterDuff.Mode.SRC_IN)); @@ -203,13 +221,28 @@ public abstract class MediaOutputBaseAdapter extends .findDrawableByLayerId(android.R.id.progress); final GradientDrawable progressDrawable = (GradientDrawable) clipDrawable.getDrawable(); - progressDrawable.setCornerRadius(mController.getActiveRadius()); + if (mController.isAdvancedLayoutSupported()) { + progressDrawable.setCornerRadii( + new float[]{0, 0, mController.getActiveRadius(), + mController.getActiveRadius(), + mController.getActiveRadius(), + mController.getActiveRadius(), 0, 0}); + } else { + progressDrawable.setCornerRadius(mController.getActiveRadius()); + } } } mItemLayout.getBackground().setColorFilter(new PorterDuffColorFilter( isActive ? mController.getColorConnectedItemBackground() : mController.getColorItemBackground(), PorterDuff.Mode.SRC_IN)); + if (mController.isAdvancedLayoutSupported()) { + mIconAreaLayout.getBackground().setColorFilter(new PorterDuffColorFilter( + showSeekBar ? mController.getColorSeekbarProgress() + : showProgressBar ? mController.getColorConnectedItemBackground() + : mController.getColorItemBackground(), + PorterDuff.Mode.SRC_IN)); + } mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE); mSeekBar.setAlpha(1); mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE); @@ -263,42 +296,134 @@ public abstract class MediaOutputBaseAdapter extends final int currentVolume = device.getCurrentVolume(); if (mSeekBar.getVolume() != currentVolume) { if (isCurrentSeekbarInvisible && !mIsInitVolumeFirstTime) { + if (mController.isAdvancedLayoutSupported()) { + updateTitleIcon(currentVolume == 0 ? R.drawable.media_output_icon_volume_off + : R.drawable.media_output_icon_volume, + mController.getColorItemContent()); + } animateCornerAndVolume(mSeekBar.getProgress(), MediaOutputSeekbar.scaleVolumeToProgress(currentVolume)); } else { if (!mVolumeAnimator.isStarted()) { + if (mController.isAdvancedLayoutSupported()) { + int percentage = + (int) ((double) currentVolume * VOLUME_PERCENTAGE_SCALE_SIZE + / (double) mSeekBar.getMax()); + if (percentage == 0) { + updateMutedVolumeIcon(); + } else { + updateUnmutedVolumeIcon(); + } + } mSeekBar.setVolume(currentVolume); } } + } else if (mController.isAdvancedLayoutSupported() && currentVolume == 0) { + mSeekBar.resetVolume(); + updateMutedVolumeIcon(); } if (mIsInitVolumeFirstTime) { mIsInitVolumeFirstTime = false; } + if (mController.isAdvancedLayoutSupported()) { + updateIconAreaClickListener((v) -> { + mSeekBar.resetVolume(); + mController.adjustVolume(device, 0); + updateMutedVolumeIcon(); + }); + } mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (device == null || !fromUser) { return; } - int currentVolume = MediaOutputSeekbar.scaleProgressToVolume(progress); + int progressToVolume = MediaOutputSeekbar.scaleProgressToVolume(progress); int deviceVolume = device.getCurrentVolume(); - if (currentVolume != deviceVolume) { - mController.adjustVolume(device, currentVolume); + if (mController.isAdvancedLayoutSupported()) { + int percentage = + (int) ((double) progressToVolume * VOLUME_PERCENTAGE_SCALE_SIZE + / (double) seekBar.getMax()); + mVolumeValueText.setText(mContext.getResources().getString( + R.string.media_output_dialog_volume_percentage, percentage)); + mVolumeValueText.setVisibility(View.VISIBLE); + } + if (progressToVolume != deviceVolume) { + mController.adjustVolume(device, progressToVolume); + if (mController.isAdvancedLayoutSupported() && deviceVolume == 0) { + updateUnmutedVolumeIcon(); + } } } @Override public void onStartTrackingTouch(SeekBar seekBar) { + if (mController.isAdvancedLayoutSupported()) { + mTitleIcon.setVisibility(View.INVISIBLE); + mVolumeValueText.setVisibility(View.VISIBLE); + } mIsDragging = true; } @Override public void onStopTrackingTouch(SeekBar seekBar) { + if (mController.isAdvancedLayoutSupported()) { + int currentVolume = MediaOutputSeekbar.scaleProgressToVolume( + seekBar.getProgress()); + int percentage = + (int) ((double) currentVolume * VOLUME_PERCENTAGE_SCALE_SIZE + / (double) seekBar.getMax()); + if (percentage == 0) { + seekBar.setProgress(0); + updateMutedVolumeIcon(); + } else { + updateUnmutedVolumeIcon(); + } + mTitleIcon.setVisibility(View.VISIBLE); + mVolumeValueText.setVisibility(View.GONE); + } mIsDragging = false; } }); } + void updateMutedVolumeIcon() { + updateTitleIcon(R.drawable.media_output_icon_volume_off, + mController.getColorItemContent()); + final GradientDrawable iconAreaBackgroundDrawable = + (GradientDrawable) mIconAreaLayout.getBackground(); + iconAreaBackgroundDrawable.setCornerRadius(mController.getActiveRadius()); + } + + void updateUnmutedVolumeIcon() { + updateTitleIcon(R.drawable.media_output_icon_volume, + mController.getColorItemContent()); + final GradientDrawable iconAreaBackgroundDrawable = + (GradientDrawable) mIconAreaLayout.getBackground(); + iconAreaBackgroundDrawable.setCornerRadii(new float[]{ + mController.getActiveRadius(), + mController.getActiveRadius(), + 0, 0, 0, 0, mController.getActiveRadius(), mController.getActiveRadius() + }); + } + + void updateTitleIcon(@DrawableRes int id, int color) { + mTitleIcon.setImageDrawable(mContext.getDrawable(id)); + mTitleIcon.setColorFilter(color); + if (mController.isAdvancedLayoutSupported()) { + mIconAreaLayout.getBackground().setColorFilter( + new PorterDuffColorFilter(mController.getColorSeekbarProgress(), + PorterDuff.Mode.SRC_IN)); + } + } + + void updateIconAreaClickListener(View.OnClickListener listener) { + if (mController.isAdvancedLayoutSupported()) { + mIconAreaLayout.setOnClickListener(listener); + } + mTitleIcon.setOnClickListener(listener); + } + void initMutingExpectedDevice() { disableSeekBar(); final Drawable backgroundDrawable = mContext.getDrawable( @@ -316,11 +441,26 @@ public abstract class MediaOutputBaseAdapter extends final ClipDrawable clipDrawable = (ClipDrawable) ((LayerDrawable) mSeekBar.getProgressDrawable()) .findDrawableByLayerId(android.R.id.progress); - final GradientDrawable progressDrawable = (GradientDrawable) clipDrawable.getDrawable(); + final GradientDrawable targetBackgroundDrawable = + (GradientDrawable) (mController.isAdvancedLayoutSupported() + ? mIconAreaLayout.getBackground() + : clipDrawable.getDrawable()); mCornerAnimator.addUpdateListener(animation -> { float value = (float) animation.getAnimatedValue(); layoutBackgroundDrawable.setCornerRadius(value); - progressDrawable.setCornerRadius(value); + if (mController.isAdvancedLayoutSupported()) { + if (toProgress == 0) { + targetBackgroundDrawable.setCornerRadius(value); + } else { + targetBackgroundDrawable.setCornerRadii(new float[]{ + value, + value, + 0, 0, 0, 0, value, value + }); + } + } else { + targetBackgroundDrawable.setCornerRadius(value); + } }); mVolumeAnimator.setIntValues(fromProgress, toProgress); mVolumeAnimator.start(); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt index 2b5d6fd63995..cdd00f99fa29 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt @@ -26,6 +26,7 @@ import com.android.internal.logging.UiEventLogger import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.broadcast.BroadcastSender +import com.android.systemui.flags.FeatureFlags import com.android.systemui.media.nearby.NearbyMediaDevicesManager import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection @@ -47,7 +48,8 @@ class MediaOutputBroadcastDialogFactory @Inject constructor( private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>, private val audioManager: AudioManager, private val powerExemptionManager: PowerExemptionManager, - private val keyGuardManager: KeyguardManager + private val keyGuardManager: KeyguardManager, + private val featureFlags: FeatureFlags ) { var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null @@ -59,7 +61,7 @@ class MediaOutputBroadcastDialogFactory @Inject constructor( val controller = MediaOutputController(context, packageName, mediaSessionManager, lbm, starter, notifCollection, dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager, - powerExemptionManager, keyGuardManager) + powerExemptionManager, keyGuardManager, featureFlags) val dialog = MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller) mediaOutputBroadcastDialog = dialog 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 19b401d80600..ecfc0e6d7574 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -73,6 +73,8 @@ import com.android.systemui.R; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastSender; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.monet.ColorScheme; import com.android.systemui.plugins.ActivityStarter; @@ -147,6 +149,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, private int mColorDialogBackground; private float mInactiveRadius; private float mActiveRadius; + private FeatureFlags mFeatureFlags; public enum BroadcastNotifyDialog { ACTION_FIRST_LAUNCH, @@ -162,7 +165,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, Optional<NearbyMediaDevicesManager> nearbyMediaDevicesManagerOptional, AudioManager audioManager, PowerExemptionManager powerExemptionManager, - KeyguardManager keyGuardManager) { + KeyguardManager keyGuardManager, + FeatureFlags featureFlags) { mContext = context; mPackageName = packageName; mMediaSessionManager = mediaSessionManager; @@ -172,6 +176,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, mAudioManager = audioManager; mPowerExemptionManager = powerExemptionManager; mKeyGuardManager = keyGuardManager; + mFeatureFlags = featureFlags; InfoMediaManager imm = new InfoMediaManager(mContext, packageName, null, lbm); mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName); mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName); @@ -599,6 +604,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, currentConnectedMediaDevice); } + public boolean isAdvancedLayoutSupported() { + return mFeatureFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT); + } + List<MediaDevice> getGroupMediaDevices() { final List<MediaDevice> selectedDevices = getSelectedMediaDevice(); final List<MediaDevice> selectableDevices = getSelectableMediaDevice(); @@ -792,7 +801,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, MediaOutputController controller = new MediaOutputController(mContext, mPackageName, mMediaSessionManager, mLocalBluetoothManager, mActivityStarter, mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager), - mAudioManager, mPowerExemptionManager, mKeyGuardManager); + mAudioManager, mPowerExemptionManager, mKeyGuardManager, mFeatureFlags); MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true, broadcastSender, controller); mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt index 543efed8378e..7dbf876bb377 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt @@ -31,6 +31,7 @@ import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.media.nearby.NearbyMediaDevicesManager import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection +import com.android.systemui.flags.FeatureFlags import java.util.Optional import javax.inject.Inject @@ -49,7 +50,8 @@ class MediaOutputDialogFactory @Inject constructor( private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>, private val audioManager: AudioManager, private val powerExemptionManager: PowerExemptionManager, - private val keyGuardManager: KeyguardManager + private val keyGuardManager: KeyguardManager, + private val featureFlags: FeatureFlags ) { companion object { private const val INTERACTION_JANK_TAG = "media_output" @@ -65,7 +67,7 @@ class MediaOutputDialogFactory @Inject constructor( context, packageName, mediaSessionManager, lbm, starter, notifCollection, dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager, - powerExemptionManager, keyGuardManager) + powerExemptionManager, keyGuardManager, featureFlags) val dialog = MediaOutputDialog(context, aboveStatusBar, broadcastSender, controller, uiEventLogger) mediaOutputDialog = dialog diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java index 4ff79d689037..253c3c713485 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java @@ -26,6 +26,7 @@ import android.widget.SeekBar; */ public class MediaOutputSeekbar extends SeekBar { private static final int SCALE_SIZE = 1000; + public static final int VOLUME_PERCENTAGE_SCALE_SIZE = 100000; public MediaOutputSeekbar(Context context, AttributeSet attrs) { super(context, attrs); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java index 9be201e99b2b..094d69a9d392 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java @@ -51,6 +51,7 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastSender; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; @@ -89,6 +90,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { private final AudioManager mAudioManager = mock(AudioManager.class); private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class); private KeyguardManager mKeyguardManager = mock(KeyguardManager.class); + private FeatureFlags mFlags = mock(FeatureFlags.class); private List<MediaController> mMediaControllers = new ArrayList<>(); private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl; @@ -121,7 +123,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager, - mKeyguardManager); + mKeyguardManager, mFlags); mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender, mMediaOutputController); mMediaOutputBaseDialogImpl.onCreate(new Bundle()); 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 cb31fde26bf2..c544c0e02d34 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 @@ -62,6 +62,7 @@ import com.android.systemui.R; 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.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -110,6 +111,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class); private CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class); private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class); + private FeatureFlags mFlags = mock(FeatureFlags.class); private final ActivityLaunchAnimator.Controller mActivityLaunchAnimatorController = mock( ActivityLaunchAnimator.Controller.class); private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock( @@ -141,7 +143,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager, - mKeyguardManager); + mKeyguardManager, mFlags); mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; MediaDescription.Builder builder = new MediaDescription.Builder(); @@ -194,7 +196,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager, - mKeyguardManager); + mKeyguardManager, mFlags); mMediaOutputController.start(mCb); @@ -224,7 +226,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager, - mKeyguardManager); + mKeyguardManager, mFlags); mMediaOutputController.start(mCb); @@ -318,7 +320,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager, - mKeyguardManager); + mKeyguardManager, mFlags); testMediaOutputController.start(mCb); reset(mCb); @@ -341,7 +343,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager, - mKeyguardManager); + mKeyguardManager, mFlags); testMediaOutputController.start(mCb); reset(mCb); @@ -377,7 +379,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager, - mKeyguardManager); + mKeyguardManager, mFlags); LocalMediaManager testLocalMediaManager = spy(testMediaOutputController.mLocalMediaManager); testMediaOutputController.mLocalMediaManager = testLocalMediaManager; @@ -394,7 +396,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager, - mKeyguardManager); + mKeyguardManager, mFlags); LocalMediaManager testLocalMediaManager = spy(testMediaOutputController.mLocalMediaManager); testMediaOutputController.mLocalMediaManager = testLocalMediaManager; @@ -671,7 +673,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager, - mKeyguardManager); + mKeyguardManager, mFlags); assertThat(mMediaOutputController.getNotificationIcon()).isNull(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java index bae3569cdc93..31866a8df7e5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java @@ -50,6 +50,7 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastSender; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; @@ -93,6 +94,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { private final AudioManager mAudioManager = mock(AudioManager.class); private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class); private KeyguardManager mKeyguardManager = mock(KeyguardManager.class); + private FeatureFlags mFlags = mock(FeatureFlags.class); private List<MediaController> mMediaControllers = new ArrayList<>(); private MediaOutputDialog mMediaOutputDialog; @@ -115,7 +117,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager, - mKeyguardManager); + mKeyguardManager, mFlags); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; mMediaOutputDialog = new MediaOutputDialog(mContext, false, mBroadcastSender, mMediaOutputController, mUiEventLogger); |