From 75cec37e4c0c34227a88ae6e884aa8737f8c34ad Mon Sep 17 00:00:00 2001 From: Josh Tsuji Date: Tue, 23 Feb 2021 17:49:53 -0500 Subject: [PATCH] Redesigns the volume panel, take 2! Now that ag/13665338 is in, we shouldn't see crashes related to the AlphaTintDrawableWrapper anymore, so we can try this again. This is nearly identical to ag/13535305, with two changes: 1) we pass the Theme to getDrawable() so we don't see warnings in logcat about undefined theme attributes 2) we use a placeholder drawable in the XML drawables instead of @android:color/transparent, which caused issues with the first tint not being applied Bug: 171441100 Test: atest SystemUITests Test: manual Change-Id: I91c6e6430bb15e4f921aa0bd45f505c26db5aca4 --- .../res/drawable/horizontal_ellipsis.xml | 27 + .../res/drawable/volume_drawer_bg.xml | 22 + .../drawable/volume_drawer_selection_bg.xml | 24 + .../res/drawable/volume_row_seekbar.xml | 56 ++ .../drawable/volume_row_seekbar_progress.xml | 44 ++ .../res/layout-land/volume_dialog.xml | 199 ++++--- .../SystemUI/res/layout/volume_dialog.xml | 27 +- .../SystemUI/res/layout/volume_dialog_row.xml | 21 +- .../res/layout/volume_ringer_drawer.xml | 126 +++++ packages/SystemUI/res/values/dimens.xml | 13 +- packages/SystemUI/res/values/strings.xml | 2 + .../systemui/volume/VolumeDialogImpl.java | 519 +++++++++++++++--- .../systemui/volume/VolumeDialogImplTest.java | 61 ++ 13 files changed, 953 insertions(+), 188 deletions(-) create mode 100644 packages/SystemUI/res/drawable/horizontal_ellipsis.xml create mode 100644 packages/SystemUI/res/drawable/volume_drawer_bg.xml create mode 100644 packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml create mode 100644 packages/SystemUI/res/drawable/volume_row_seekbar.xml create mode 100644 packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml create mode 100644 packages/SystemUI/res/layout/volume_ringer_drawer.xml diff --git a/packages/SystemUI/res/drawable/horizontal_ellipsis.xml b/packages/SystemUI/res/drawable/horizontal_ellipsis.xml new file mode 100644 index 0000000000000..1800857a826c8 --- /dev/null +++ b/packages/SystemUI/res/drawable/horizontal_ellipsis.xml @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/volume_drawer_bg.xml b/packages/SystemUI/res/drawable/volume_drawer_bg.xml new file mode 100644 index 0000000000000..f0e22926d07a7 --- /dev/null +++ b/packages/SystemUI/res/drawable/volume_drawer_bg.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml b/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml new file mode 100644 index 0000000000000..5e7cb12d1c5f0 --- /dev/null +++ b/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml @@ -0,0 +1,24 @@ + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/volume_row_seekbar.xml b/packages/SystemUI/res/drawable/volume_row_seekbar.xml new file mode 100644 index 0000000000000..b0e0ed5079e62 --- /dev/null +++ b/packages/SystemUI/res/drawable/volume_row_seekbar.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml b/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml new file mode 100644 index 0000000000000..ef202360b1ce4 --- /dev/null +++ b/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/layout-land/volume_dialog.xml b/packages/SystemUI/res/layout-land/volume_dialog.xml index c420117073c51..237dc02e5d8c3 100644 --- a/packages/SystemUI/res/layout-land/volume_dialog.xml +++ b/packages/SystemUI/res/layout-land/volume_dialog.xml @@ -32,105 +32,124 @@ android:gravity="right" android:layout_gravity="right" android:background="@android:color/transparent" - android:paddingRight="@dimen/volume_dialog_panel_transparent_padding_right" - android:paddingTop="@dimen/volume_dialog_panel_transparent_padding" - android:paddingBottom="@dimen/volume_dialog_panel_transparent_padding" + android:paddingRight="@dimen/volume_dialog_stream_padding" android:paddingLeft="@dimen/volume_dialog_panel_transparent_padding" android:clipToPadding="false"> - - - - - - + - - - - - - - + android:clipToPadding="false"> - - + - + android:paddingRight="@dimen/volume_dialog_stream_padding" + android:clipChildren="false" + android:clipToPadding="false" + android:orientation="vertical"> + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -34,13 +36,15 @@ android:layout_gravity="right" android:background="@android:color/transparent" android:paddingRight="@dimen/volume_dialog_panel_transparent_padding_right" - android:paddingTop="@dimen/volume_dialog_panel_transparent_padding" - android:paddingBottom="@dimen/volume_dialog_panel_transparent_padding" android:paddingLeft="@dimen/volume_dialog_panel_transparent_padding" android:orientation="vertical" - android:clipToPadding="false"> + android:clipToPadding="false" + android:clipChildren="false"> + + + android:clipToPadding="false" > + android:layout_marginTop="@dimen/volume_row_slider_padding_start"> + android:layout_height="wrap_content"> @@ -118,7 +118,6 @@ android:gravity="right" android:layout_gravity="right" android:clipToPadding="false" - android:translationZ="@dimen/volume_dialog_elevation" android:background="@drawable/rounded_bg_full"> diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml index b9efc5be70c1d..fda59b50104a3 100644 --- a/packages/SystemUI/res/layout/volume_dialog_row.xml +++ b/packages/SystemUI/res/layout/volume_dialog_row.xml @@ -20,11 +20,12 @@ android:layout_width="@dimen/volume_dialog_panel_width" android:clipChildren="false" android:clipToPadding="false" + android:translationZ="@dimen/volume_dialog_elevation" android:theme="@style/volume_dialog_theme"> @@ -41,21 +42,23 @@ + android:layout_height="@dimen/volume_row_slider_height"> + + android:rotation="270" /> - - diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml new file mode 100644 index 0000000000000..d6e1782382fac --- /dev/null +++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 4bc5a300e833b..3b4a96aa72930 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -456,10 +456,12 @@ 20dp - 8dp + 12dp 64dp + 32dp + 116dp 64dp @@ -486,6 +488,13 @@ 2dp + + 64dp + 32dp + + + 24dp + 0x31 @@ -966,7 +975,7 @@ 4dp 16dp 64dp - 48dp + 192dp 12dp 2dp diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 3b42600076acd..2a35e17f5b124 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1558,6 +1558,8 @@ %1$s. Tap to set to vibrate. %1$s. Tap to mute. + Tap to change ringer mode + mute diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index df54eabca8e7c..25345d5c4b4ca 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -33,7 +33,11 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.Dialog; @@ -51,6 +55,9 @@ import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.Region; import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.RotateDrawable; import android.media.AudioManager; import android.media.AudioSystem; import android.os.Debug; @@ -81,6 +88,8 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; @@ -88,6 +97,7 @@ import android.widget.Toast; import com.android.settingslib.Utils; import com.android.systemui.Dependency; +import com.android.systemui.Interpolators; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.media.dialog.MediaOutputDialogFactory; @@ -99,6 +109,8 @@ import com.android.systemui.plugins.VolumeDialogController.StreamState; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.util.AlphaTintDrawableWrapper; +import com.android.systemui.util.RoundedCornerProgressDrawable; import java.io.PrintWriter; import java.util.ArrayList; @@ -124,8 +136,14 @@ public class VolumeDialogImpl implements VolumeDialog, static final int DIALOG_ODI_CAPTIONS_TOOLTIP_TIMEOUT_MILLIS = 5000; static final int DIALOG_HOVERING_TIMEOUT_MILLIS = 16000; + private static final int DRAWER_ANIMATION_DURATION_SHORT = 175; + private static final int DRAWER_ANIMATION_DURATION = 250; + private final int mDialogShowAnimationDurationMs; private final int mDialogHideAnimationDurationMs; + private final int mRingerDrawerItemSize; + private final boolean mShowVibrate; + private final int mRingerCount; private final boolean mShowLowMediaVolumeIcon; private final boolean mChangeVolumeRowTintWhenInactive; @@ -140,6 +158,30 @@ public class VolumeDialogImpl implements VolumeDialog, private ViewGroup mDialogView; private ViewGroup mDialogRowsView; private ViewGroup mRinger; + + private ViewGroup mSelectedRingerContainer; + private ImageView mSelectedRingerIcon; + + private ViewGroup mRingerDrawerContainer; + private ViewGroup mRingerDrawerMute; + private ViewGroup mRingerDrawerVibrate; + private ViewGroup mRingerDrawerNormal; + private ImageView mRingerDrawerMuteIcon; + private ImageView mRingerDrawerVibrateIcon; + private ImageView mRingerDrawerNormalIcon; + + /** + * View that draws the 'selected' background behind one of the three ringer choices in the + * drawer. + */ + private ViewGroup mRingerDrawerNewSelectionBg; + + private final ValueAnimator mRingerDrawerIconColorAnimator = ValueAnimator.ofFloat(0f, 1f); + private ImageView mRingerDrawerIconAnimatingSelected; + private ImageView mRingerDrawerIconAnimatingDeselected; + + private boolean mIsRingerDrawerOpen = false; + private ImageButton mRingerIcon; private ViewGroup mODICaptionsView; private CaptionsToggleImageButton mODICaptionsIcon; @@ -191,6 +233,12 @@ public class VolumeDialogImpl implements VolumeDialog, mContext.getResources().getInteger(R.integer.config_dialogShowAnimationDurationMs); mDialogHideAnimationDurationMs = mContext.getResources().getInteger(R.integer.config_dialogHideAnimationDurationMs); + mRingerDrawerItemSize = mContext.getResources().getDimensionPixelSize( + R.dimen.volume_ringer_drawer_item_size); + mShowVibrate = mController.hasVibrator(); + + // Normal, mute, and possibly vibrate. + mRingerCount = mShowVibrate ? 3 : 2; } @Override @@ -314,6 +362,20 @@ public class VolumeDialogImpl implements VolumeDialog, mZenIcon = mRinger.findViewById(R.id.dnd_icon); } + mSelectedRingerIcon = mDialog.findViewById(R.id.volume_new_ringer_active_icon); + mSelectedRingerContainer = mDialog.findViewById( + R.id.volume_new_ringer_active_icon_container); + + mRingerDrawerMute = mDialog.findViewById(R.id.volume_drawer_mute); + mRingerDrawerNormal = mDialog.findViewById(R.id.volume_drawer_normal); + mRingerDrawerVibrate = mDialog.findViewById(R.id.volume_drawer_vibrate); + mRingerDrawerMuteIcon = mDialog.findViewById(R.id.volume_drawer_mute_icon); + mRingerDrawerVibrateIcon = mDialog.findViewById(R.id.volume_drawer_vibrate_icon); + mRingerDrawerNormalIcon = mDialog.findViewById(R.id.volume_drawer_normal_icon); + mRingerDrawerNewSelectionBg = mDialog.findViewById(R.id.volume_drawer_selection_background); + + setupRingerDrawer(); + mODICaptionsView = mDialog.findViewById(R.id.odi_captions); if (mODICaptionsView != null) { mODICaptionsIcon = mODICaptionsView.findViewById(R.id.odi_captions_icon); @@ -475,40 +537,275 @@ public class VolumeDialogImpl implements VolumeDialog, row.anim = null; + final LayerDrawable seekbarDrawable = + (LayerDrawable) mContext.getDrawable(R.drawable.volume_row_seekbar); + + final LayerDrawable seekbarBgDrawable = + (LayerDrawable) seekbarDrawable.findDrawableByLayerId(android.R.id.background); + + row.sliderBgSolid = seekbarBgDrawable.findDrawableByLayerId( + R.id.volume_seekbar_background_solid); + + row.sliderBgIcon = (AlphaTintDrawableWrapper) + ((RotateDrawable) seekbarBgDrawable.findDrawableByLayerId( + R.id.volume_seekbar_background_icon)).getDrawable(); + + final LayerDrawable seekbarProgressDrawable = (LayerDrawable) + ((RoundedCornerProgressDrawable) seekbarDrawable.findDrawableByLayerId( + android.R.id.progress)).getDrawable(); + + row.sliderProgressSolid = seekbarProgressDrawable.findDrawableByLayerId( + R.id.volume_seekbar_progress_solid); + + row.sliderProgressIcon = (AlphaTintDrawableWrapper) + ((RotateDrawable) seekbarProgressDrawable.findDrawableByLayerId( + R.id.volume_seekbar_progress_icon)).getDrawable(); + + row.slider.setProgressDrawable(seekbarDrawable); + row.slider.setThumb(null); + row.icon = row.view.findViewById(R.id.volume_row_icon); - row.icon.setImageResource(iconRes); - if (row.stream != AudioSystem.STREAM_ACCESSIBILITY) { - row.icon.setOnClickListener(v -> { - Events.writeEvent(Events.EVENT_ICON_CLICK, row.stream, row.iconState); - mController.setActiveStream(row.stream); - if (row.stream == AudioManager.STREAM_RING) { - final boolean hasVibrator = mController.hasVibrator(); - if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) { - if (hasVibrator) { - mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false); + + row.setIcon(iconRes, mContext.getTheme()); + + if (row.icon != null) { + if (row.stream != AudioSystem.STREAM_ACCESSIBILITY) { + row.icon.setOnClickListener(v -> { + Events.writeEvent(Events.EVENT_ICON_CLICK, row.stream, row.iconState); + mController.setActiveStream(row.stream); + if (row.stream == AudioManager.STREAM_RING) { + final boolean hasVibrator = mController.hasVibrator(); + if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) { + if (hasVibrator) { + mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false); + } else { + final boolean wasZero = row.ss.level == 0; + mController.setStreamVolume(stream, + wasZero ? row.lastAudibleLevel : 0); + } } else { - final boolean wasZero = row.ss.level == 0; - mController.setStreamVolume(stream, - wasZero ? row.lastAudibleLevel : 0); + mController.setRingerMode( + AudioManager.RINGER_MODE_NORMAL, false); + if (row.ss.level == 0) { + mController.setStreamVolume(stream, 1); + } } } else { - mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false); - if (row.ss.level == 0) { - mController.setStreamVolume(stream, 1); - } + final boolean vmute = row.ss.level == row.ss.levelMin; + mController.setStreamVolume(stream, + vmute ? row.lastAudibleLevel : row.ss.levelMin); } - } else { - final boolean vmute = row.ss.level == row.ss.levelMin; - mController.setStreamVolume(stream, - vmute ? row.lastAudibleLevel : row.ss.levelMin); - } - row.userAttempt = 0; // reset the grace period, slider updates immediately - }); - } else { - row.icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + row.userAttempt = 0; // reset the grace period, slider updates immediately + }); + } else { + row.icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + } } } + private void setRingerMode(int newRingerMode) { + Events.writeEvent(Events.EVENT_RINGER_TOGGLE, newRingerMode); + incrementManualToggleCount(); + updateRingerH(); + provideTouchFeedbackH(newRingerMode); + mController.setRingerMode(newRingerMode, false); + maybeShowToastH(newRingerMode); + } + + private void setupRingerDrawer() { + mRingerDrawerContainer = mDialog.findViewById(R.id.volume_drawer_container); + + if (mRingerDrawerContainer == null) { + return; + } + + if (!mShowVibrate) { + mRingerDrawerVibrate.setVisibility(GONE); + } + + // In portrait, add padding to the bottom to account for the height of the open ringer + // drawer. + if (!isLandscape()) { + mDialogView.setPadding( + mDialogView.getPaddingLeft(), + mDialogView.getPaddingTop(), + mDialogView.getPaddingRight(), + mDialogView.getPaddingBottom() + (mRingerCount - 1) * mRingerDrawerItemSize); + } else { + mDialogView.setPadding( + mDialogView.getPaddingLeft() + (mRingerCount - 1) * mRingerDrawerItemSize, + mDialogView.getPaddingTop(), + mDialogView.getPaddingRight(), + mDialogView.getPaddingBottom()); + } + + ((LinearLayout) mRingerDrawerContainer.findViewById(R.id.volume_drawer_options)) + .setOrientation(isLandscape() ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL); + + mSelectedRingerContainer.setOnClickListener(view -> { + if (mIsRingerDrawerOpen) { + hideRingerDrawer(); + } else { + showRingerDrawer(); + } + }); + + mRingerDrawerVibrate.setOnClickListener( + new RingerDrawerItemClickListener(RINGER_MODE_VIBRATE)); + mRingerDrawerMute.setOnClickListener( + new RingerDrawerItemClickListener(RINGER_MODE_SILENT)); + mRingerDrawerNormal.setOnClickListener( + new RingerDrawerItemClickListener(RINGER_MODE_NORMAL)); + + final int unselectedColor = Utils.getColorAccentDefaultColor(mContext); + final int selectedColor = Utils.getColorAttrDefaultColor( + mContext, android.R.attr.colorBackgroundFloating); + + // Add an update listener that animates the deselected icon to the unselected color, and the + // selected icon to the selected color. + mRingerDrawerIconColorAnimator.addUpdateListener( + anim -> { + final float currentValue = (float) anim.getAnimatedValue(); + final int curUnselectedColor = (int) ArgbEvaluator.getInstance().evaluate( + currentValue, selectedColor, unselectedColor); + final int curSelectedColor = (int) ArgbEvaluator.getInstance().evaluate( + currentValue, unselectedColor, selectedColor); + + mRingerDrawerIconAnimatingDeselected.setColorFilter(curUnselectedColor); + mRingerDrawerIconAnimatingSelected.setColorFilter(curSelectedColor); + }); + mRingerDrawerIconColorAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRingerDrawerIconAnimatingDeselected.clearColorFilter(); + mRingerDrawerIconAnimatingSelected.clearColorFilter(); + } + }); + mRingerDrawerIconColorAnimator.setDuration(DRAWER_ANIMATION_DURATION_SHORT); + } + + private ImageView getDrawerIconViewForMode(int mode) { + if (mode == RINGER_MODE_VIBRATE) { + return mRingerDrawerVibrateIcon; + } else if (mode == RINGER_MODE_SILENT) { + return mRingerDrawerMuteIcon; + } else { + return mRingerDrawerNormalIcon; + } + } + + /** + * Translation to apply form the origin (either top or left) to overlap the selection background + * with the given mode in the drawer. + */ + private float getTranslationInDrawerForRingerMode(int mode) { + return mode == RINGER_MODE_VIBRATE + ? -mRingerDrawerItemSize * 2 + : mode == RINGER_MODE_SILENT + ? -mRingerDrawerItemSize + : 0; + } + + /** Animates in the ringer drawer. */ + private void showRingerDrawer() { + // Show all ringer icons except the currently selected one, since we're going to animate the + // ringer button to that position. + mRingerDrawerVibrateIcon.setVisibility( + mState.ringerModeInternal == RINGER_MODE_VIBRATE ? INVISIBLE : VISIBLE); + mRingerDrawerMuteIcon.setVisibility( + mState.ringerModeInternal == RINGER_MODE_SILENT ? INVISIBLE : VISIBLE); + mRingerDrawerNormalIcon.setVisibility( + mState.ringerModeInternal == RINGER_MODE_NORMAL ? INVISIBLE : VISIBLE); + + // Hide the selection background - we use this to show a selection when one is + // tapped, so it should be invisible until that happens. However, position it below + // the currently selected ringer so that it's ready to animate. + mRingerDrawerNewSelectionBg.setAlpha(0f); + + if (!isLandscape()) { + mRingerDrawerNewSelectionBg.setTranslationY( + getTranslationInDrawerForRingerMode(mState.ringerModeInternal)); + } else { + mRingerDrawerNewSelectionBg.setTranslationX( + getTranslationInDrawerForRingerMode(mState.ringerModeInternal)); + } + + // Move the drawer so that the top/rightmost ringer choice overlaps with the selected ringer + // icon. + if (!isLandscape()) { + mRingerDrawerContainer.setTranslationY(mRingerDrawerItemSize * (mRingerCount - 1)); + } else { + mRingerDrawerContainer.setTranslationX(mRingerDrawerItemSize * (mRingerCount - 1)); + } + mRingerDrawerContainer.setAlpha(0f); + mRingerDrawerContainer.setVisibility(VISIBLE); + + // Animate the drawer up and visible. + mRingerDrawerContainer.animate() + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + // Vibrate is way farther up, so give the selected ringer icon a head start if + // vibrate is selected. + .setDuration(mState.ringerModeInternal == RINGER_MODE_VIBRATE + ? DRAWER_ANIMATION_DURATION_SHORT + : DRAWER_ANIMATION_DURATION) + .setStartDelay(mState.ringerModeInternal == RINGER_MODE_VIBRATE + ? DRAWER_ANIMATION_DURATION - DRAWER_ANIMATION_DURATION_SHORT + : 0) + .alpha(1f) + .translationX(0f) + .translationY(0f) + .start(); + + // Animate the selected ringer view up to that ringer's position in the drawer. + mSelectedRingerContainer.animate() + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .setDuration(DRAWER_ANIMATION_DURATION) + .withEndAction(() -> + getDrawerIconViewForMode(mState.ringerModeInternal).setVisibility(VISIBLE)); + + if (!isLandscape()) { + mSelectedRingerContainer.animate() + .translationY(getTranslationInDrawerForRingerMode(mState.ringerModeInternal)) + .start(); + } else { + mSelectedRingerContainer.animate() + .translationX(getTranslationInDrawerForRingerMode(mState.ringerModeInternal)) + .start(); + } + + mIsRingerDrawerOpen = true; + } + + /** Animates away the ringer drawer. */ + private void hideRingerDrawer() { + // Hide the drawer icon for the selected ringer - it's visible in the ringer button and we + // don't want to be able to see it while it animates away. + getDrawerIconViewForMode(mState.ringerModeInternal).setVisibility(INVISIBLE); + + mRingerDrawerContainer.animate() + .alpha(0f) + .setDuration(DRAWER_ANIMATION_DURATION) + .setStartDelay(0) + .withEndAction(() -> mRingerDrawerContainer.setVisibility(INVISIBLE)); + + if (!isLandscape()) { + mRingerDrawerContainer.animate() + .translationY(mRingerDrawerItemSize * 2) + .start(); + } else { + mRingerDrawerContainer.animate() + .translationX(mRingerDrawerItemSize * 2) + .start(); + } + + mSelectedRingerContainer.animate() + .translationX(0f) + .translationY(0f) + .start(); + + mIsRingerDrawerOpen = false; + } + public void initSettingsH() { if (mSettingsView != null) { mSettingsView.setVisibility( @@ -555,12 +852,8 @@ public class VolumeDialogImpl implements VolumeDialog, mController.setStreamVolume(AudioManager.STREAM_RING, 1); } } - Events.writeEvent(Events.EVENT_RINGER_TOGGLE, newRingerMode); - incrementManualToggleCount(); - updateRingerH(); - provideTouchFeedbackH(newRingerMode); - mController.setRingerMode(newRingerMode, false); - maybeShowToastH(newRingerMode); + + setRingerMode(newRingerMode); }); } updateRingerH(); @@ -809,6 +1102,8 @@ public class VolumeDialogImpl implements VolumeDialog, mDialog.dismiss(); tryToRemoveCaptionsTooltip(); mIsAnimatingDismiss = false; + + hideRingerDrawer(); }, 50)); if (!isLandscape()) animator.translationX(mDialogView.getWidth() / 2.0f); animator.start(); @@ -889,12 +1184,14 @@ public class VolumeDialogImpl implements VolumeDialog, switch (mState.ringerModeInternal) { case AudioManager.RINGER_MODE_VIBRATE: mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate); + mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate); addAccessibilityDescription(mRingerIcon, RINGER_MODE_VIBRATE, mContext.getString(R.string.volume_ringer_hint_mute)); mRingerIcon.setTag(Events.ICON_STATE_VIBRATE); break; case AudioManager.RINGER_MODE_SILENT: mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); + mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); mRingerIcon.setTag(Events.ICON_STATE_MUTE); addAccessibilityDescription(mRingerIcon, RINGER_MODE_SILENT, mContext.getString(R.string.volume_ringer_hint_unmute)); @@ -904,11 +1201,13 @@ public class VolumeDialogImpl implements VolumeDialog, boolean muted = (mAutomute && ss.level == 0) || ss.muted; if (!isZenMuted && muted) { mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); + mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL, mContext.getString(R.string.volume_ringer_hint_unmute)); mRingerIcon.setTag(Events.ICON_STATE_MUTE); } else { mRingerIcon.setImageResource(R.drawable.ic_volume_ringer); + mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer); if (mController.hasVibrator()) { addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL, mContext.getString(R.string.volume_ringer_hint_vibrate)); @@ -1075,8 +1374,6 @@ public class VolumeDialogImpl implements VolumeDialog, // update icon final boolean iconEnabled = (mAutomute || ss.muteSupported) && !zenMuted; - row.icon.setEnabled(iconEnabled); - row.icon.setAlpha(iconEnabled ? 1 : 0.5f); final int iconRes; if (isRingVibrate) { iconRes = R.drawable.ic_volume_ringer_vibrate; @@ -1092,7 +1389,7 @@ public class VolumeDialogImpl implements VolumeDialog, ? R.drawable.ic_volume_media_low : row.iconRes; } - row.icon.setImageResource(iconRes); + row.setIcon(iconRes, mContext.getTheme()); row.iconState = iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE : (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes) @@ -1101,18 +1398,35 @@ public class VolumeDialogImpl implements VolumeDialog, || iconRes == R.drawable.ic_volume_media_low) ? Events.ICON_STATE_UNMUTE : Events.ICON_STATE_UNKNOWN; - if (iconEnabled) { - if (isRingStream) { - if (isRingVibrate) { - row.icon.setContentDescription(mContext.getString( - R.string.volume_stream_content_description_unmute, - getStreamLabelH(ss))); - } else { - if (mController.hasVibrator()) { + + if (row.icon != null) { + if (iconEnabled) { + if (isRingStream) { + if (isRingVibrate) { row.icon.setContentDescription(mContext.getString( - mShowA11yStream - ? R.string.volume_stream_content_description_vibrate_a11y - : R.string.volume_stream_content_description_vibrate, + R.string.volume_stream_content_description_unmute, + getStreamLabelH(ss))); + } else { + if (mController.hasVibrator()) { + row.icon.setContentDescription(mContext.getString( + mShowA11yStream + ? R.string.volume_stream_content_description_vibrate_a11y + : R.string.volume_stream_content_description_vibrate, + getStreamLabelH(ss))); + } else { + row.icon.setContentDescription(mContext.getString( + mShowA11yStream + ? R.string.volume_stream_content_description_mute_a11y + : R.string.volume_stream_content_description_mute, + getStreamLabelH(ss))); + } + } + } else if (isA11yStream) { + row.icon.setContentDescription(getStreamLabelH(ss)); + } else { + if (ss.muted || mAutomute && ss.level == 0) { + row.icon.setContentDescription(mContext.getString( + R.string.volume_stream_content_description_unmute, getStreamLabelH(ss))); } else { row.icon.setContentDescription(mContext.getString( @@ -1122,23 +1436,9 @@ public class VolumeDialogImpl implements VolumeDialog, getStreamLabelH(ss))); } } - } else if (isA11yStream) { - row.icon.setContentDescription(getStreamLabelH(ss)); } else { - if (ss.muted || mAutomute && ss.level == 0) { - row.icon.setContentDescription(mContext.getString( - R.string.volume_stream_content_description_unmute, - getStreamLabelH(ss))); - } else { - row.icon.setContentDescription(mContext.getString( - mShowA11yStream - ? R.string.volume_stream_content_description_mute_a11y - : R.string.volume_stream_content_description_mute, - getStreamLabelH(ss))); - } + row.icon.setContentDescription(getStreamLabelH(ss)); } - } else { - row.icon.setContentDescription(getStreamLabelH(ss)); } // ensure tracking is disabled if zenMuted @@ -1167,22 +1467,29 @@ public class VolumeDialogImpl implements VolumeDialog, if (!useActiveColoring && !mChangeVolumeRowTintWhenInactive) { return; } - final ColorStateList tint = useActiveColoring + final ColorStateList colorTint = useActiveColoring ? Utils.getColorAccent(mContext) : Utils.getColorAttr(mContext, android.R.attr.colorForeground); final int alpha = useActiveColoring - ? Color.alpha(tint.getDefaultColor()) + ? Color.alpha(colorTint.getDefaultColor()) : getAlphaAttr(android.R.attr.secondaryContentAlpha); - if (tint == row.cachedTint) return; - row.slider.setProgressTintList(tint); - row.slider.setThumbTintList(tint); - row.slider.setProgressBackgroundTintList(tint); - row.slider.setAlpha(((float) alpha) / 255); - row.icon.setImageTintList(tint); - row.icon.setImageAlpha(alpha); - row.cachedTint = tint; + + final ColorStateList bgTint = Utils.getColorAttr( + mContext, android.R.attr.colorBackgroundFloating); + + row.sliderProgressSolid.setTintList(colorTint); + row.sliderBgIcon.setTintList(colorTint); + + row.sliderBgSolid.setTintList(bgTint); + row.sliderProgressIcon.setTintList(bgTint); + + if (row.icon != null) { + row.icon.setImageTintList(colorTint); + row.icon.setImageAlpha(alpha); + } + if (row.number != null) { - row.number.setTextColor(tint); + row.number.setTextColor(colorTint); row.number.setAlpha(alpha); } } @@ -1538,6 +1845,10 @@ public class VolumeDialogImpl implements VolumeDialog, private View view; private TextView header; private ImageButton icon; + private Drawable sliderBgSolid; + private AlphaTintDrawableWrapper sliderBgIcon; + private Drawable sliderProgressSolid; + private AlphaTintDrawableWrapper sliderProgressIcon; private SeekBar slider; private TextView number; private int stream; @@ -1555,5 +1866,69 @@ public class VolumeDialogImpl implements VolumeDialog, private int animTargetProgress; private int lastAudibleLevel = 1; private FrameLayout dndIcon; + + void setIcon(int iconRes, Resources.Theme theme) { + if (icon != null) { + icon.setImageResource(iconRes); + } + + sliderProgressIcon.setDrawable(view.getResources().getDrawable(iconRes, theme)); + sliderBgIcon.setDrawable(view.getResources().getDrawable(iconRes, theme)); + } + } + + /** + * Click listener added to each ringer option in the drawer. This will initiate the animation to + * select and then close the ringer drawer, and actually change the ringer mode. + */ + private class RingerDrawerItemClickListener implements View.OnClickListener { + private final int mClickedRingerMode; + + RingerDrawerItemClickListener(int clickedRingerMode) { + mClickedRingerMode = clickedRingerMode; + } + + @Override + public void onClick(View view) { + setRingerMode(mClickedRingerMode); + + mRingerDrawerIconAnimatingSelected = getDrawerIconViewForMode(mClickedRingerMode); + mRingerDrawerIconAnimatingDeselected = getDrawerIconViewForMode( + mState.ringerModeInternal); + + // Begin switching the selected icon and deselected icon colors since the background is + // going to animate behind the new selection. + mRingerDrawerIconColorAnimator.start(); + + mSelectedRingerContainer.setVisibility(View.INVISIBLE); + mRingerDrawerNewSelectionBg.setAlpha(1f); + mRingerDrawerNewSelectionBg.animate() + .setInterpolator(Interpolators.ACCELERATE_DECELERATE) + .setDuration(DRAWER_ANIMATION_DURATION_SHORT) + .withEndAction(() -> { + mRingerDrawerNewSelectionBg.setAlpha(0f); + + if (!isLandscape()) { + mSelectedRingerContainer.setTranslationY( + getTranslationInDrawerForRingerMode(mClickedRingerMode)); + } else { + mSelectedRingerContainer.setTranslationX( + getTranslationInDrawerForRingerMode(mClickedRingerMode)); + } + + mSelectedRingerContainer.setVisibility(VISIBLE); + hideRingerDrawer(); + }); + + if (!isLandscape()) { + mRingerDrawerNewSelectionBg.animate() + .translationY(getTranslationInDrawerForRingerMode(mClickedRingerMode)) + .start(); + } else { + mRingerDrawerNewSelectionBg.animate() + .translationX(getTranslationInDrawerForRingerMode(mClickedRingerMode)) + .start(); + } + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index c0856892dc44a..3cea175671731 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -22,6 +22,7 @@ import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.app.KeyguardManager; @@ -37,6 +38,8 @@ import android.view.accessibility.AccessibilityManager; import androidx.test.filters.SmallTest; +import com.android.systemui.Prefs; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.VolumeDialogController.State; @@ -58,6 +61,11 @@ import java.util.function.Predicate; public class VolumeDialogImplTest extends SysuiTestCase { VolumeDialogImpl mDialog; + View mActiveRinger; + View mDrawerContainer; + View mDrawerVibrate; + View mDrawerMute; + View mDrawerNormal; @Mock VolumeDialogController mController; @@ -80,6 +88,17 @@ public class VolumeDialogImplTest extends SysuiTestCase { mDialog.init(0, null); State state = createShellState(); mDialog.onStateChangedH(state); + + mActiveRinger = mDialog.getDialogView().findViewById( + R.id.volume_new_ringer_active_icon_container); + mDrawerContainer = mDialog.getDialogView().findViewById(R.id.volume_drawer_container); + mDrawerVibrate = mDrawerContainer.findViewById(R.id.volume_drawer_vibrate); + mDrawerMute = mDrawerContainer.findViewById(R.id.volume_drawer_mute); + mDrawerNormal = mDrawerContainer.findViewById(R.id.volume_drawer_normal); + + Prefs.putInt(mContext, + Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT, + VolumePrefs.SHOW_RINGER_TOAST_COUNT + 1); } private State createShellState() { @@ -207,6 +226,48 @@ public class VolumeDialogImplTest extends SysuiTestCase { verify(mController, never()).vibrate(any()); } + @Test + public void testSelectVibrateFromDrawer() { + final State initialUnsetState = new State(); + initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL; + mDialog.onStateChangedH(initialUnsetState); + + mActiveRinger.performClick(); + mDrawerVibrate.performClick(); + + // Make sure we've actually changed the ringer mode. + verify(mController, times(1)).setRingerMode( + AudioManager.RINGER_MODE_VIBRATE, false); + } + + @Test + public void testSelectMuteFromDrawer() { + final State initialUnsetState = new State(); + initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL; + mDialog.onStateChangedH(initialUnsetState); + + mActiveRinger.performClick(); + mDrawerMute.performClick(); + + // Make sure we've actually changed the ringer mode. + verify(mController, times(1)).setRingerMode( + AudioManager.RINGER_MODE_SILENT, false); + } + + @Test + public void testSelectNormalFromDrawer() { + final State initialUnsetState = new State(); + initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE; + mDialog.onStateChangedH(initialUnsetState); + + mActiveRinger.performClick(); + mDrawerNormal.performClick(); + + // Make sure we've actually changed the ringer mode. + verify(mController, times(1)).setRingerMode( + AudioManager.RINGER_MODE_NORMAL, false); + } + /* @Test public void testContentDescriptions() {