diff options
10 files changed, 192 insertions, 107 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 e1b99ceffbb6..55dce8f7e66c 100644 --- a/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml +++ b/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml @@ -27,7 +27,7 @@ <clip> <shape> <corners - android:radius="28dp"/> + android:radius="16dp"/> <size android:height="64dp"/> <solid android:color="@color/material_dynamic_primary80" /> diff --git a/packages/SystemUI/res/layout/media_output_list_item.xml b/packages/SystemUI/res/layout/media_output_list_item.xml index dc86afaf6497..daa9c04584ae 100644 --- a/packages/SystemUI/res/layout/media_output_list_item.xml +++ b/packages/SystemUI/res/layout/media_output_list_item.xml @@ -33,7 +33,7 @@ android:layout_height="match_parent" android:background="@drawable/media_output_item_background" android:layout_gravity="center_vertical|start"> - <SeekBar + <com.android.systemui.media.dialog.MediaOutputSeekbar android:id="@+id/volume_seekbar" android:splitTrack="false" android:visibility="gone" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 4370432d85a9..f77430bae4ab 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1125,6 +1125,8 @@ <dimen name="media_output_dialog_icon_corner_radius">16dp</dimen> <dimen name="media_output_dialog_title_anim_y_delta">12.5dp</dimen> <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> <!-- Distance that the full shade transition takes in order to complete by tapping on a button like "expand". --> 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 07001ee138b8..694149a1e6e7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -112,6 +112,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { super.onBind(device, topMargin, bottomMargin, position); final boolean currentlyConnected = !mIncludeDynamicGroup && isCurrentlyConnected(device); + boolean isCurrentSeekbarInvisible = mSeekBar.getVisibility() == View.GONE; if (currentlyConnected) { mConnectedItem = mContainerLayout; } @@ -178,7 +179,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mCheckBox.setOnCheckedChangeListener( (buttonView, isChecked) -> onGroupActionTriggered(false, device)); setCheckBoxColor(mCheckBox, mController.getColorItemContent()); - initSeekbar(device); + initSeekbar(device, isCurrentSeekbarInvisible); mEndTouchArea.setVisibility(View.VISIBLE); mEndTouchArea.setOnClickListener(null); mEndTouchArea.setOnClickListener((v) -> mCheckBox.performClick()); @@ -193,7 +194,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { setSingleLineLayout(getItemTitle(device), true /* bFocused */, true /* showSeekBar */, false /* showProgressBar */, true /* showStatus */); - initSeekbar(device); + initSeekbar(device, isCurrentSeekbarInvisible); setUpContentDescriptionForView(mContainerLayout, false, device); mCurrentActivePosition = position; } else if (isDeviceIncluded(mController.getSelectableMediaDevice(), device)) { @@ -257,9 +258,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mCurrentActivePosition = -1; mController.connectDevice(device); device.setState(MediaDeviceState.STATE_CONNECTING); - if (!isAnimating()) { - notifyDataSetChanged(); - } + notifyDataSetChanged(); } private void setUpContentDescriptionForView(View view, boolean clickable, 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 9dc29bd6e090..5c2cc0b6af35 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -17,18 +17,22 @@ package com.android.systemui.media.dialog; import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.app.WallpaperColors; import android.content.Context; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.Typeface; +import android.graphics.drawable.ClipDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.Icon; +import android.graphics.drawable.LayerDrawable; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.animation.LinearInterpolator; import android.widget.CheckBox; import android.widget.FrameLayout; import android.widget.ImageView; @@ -44,7 +48,6 @@ import com.android.settingslib.Utils; import com.android.settingslib.media.MediaDevice; import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.R; -import com.android.systemui.animation.Interpolators; import java.util.List; @@ -61,7 +64,6 @@ public abstract class MediaOutputBaseAdapter extends protected final MediaOutputController mController; private int mMargin; - private boolean mIsAnimating; Context mContext; View mHolderView; @@ -114,10 +116,6 @@ public abstract class MediaOutputBaseAdapter extends return mIsDragging; } - boolean isAnimating() { - return mIsAnimating; - } - int getCurrentActivePosition() { return mCurrentActivePosition; } @@ -131,7 +129,7 @@ public abstract class MediaOutputBaseAdapter extends */ abstract class MediaDeviceBaseViewHolder extends RecyclerView.ViewHolder { - private static final int ANIM_DURATION = 200; + private static final int ANIM_DURATION = 500; final LinearLayout mContainerLayout; final FrameLayout mItemLayout; @@ -140,12 +138,14 @@ public abstract class MediaOutputBaseAdapter extends final TextView mSubTitleText; final ImageView mTitleIcon; final ProgressBar mProgressBar; - final SeekBar mSeekBar; + final MediaOutputSeekbar mSeekBar; final LinearLayout mTwoLineLayout; final ImageView mStatusIcon; final CheckBox mCheckBox; final LinearLayout mEndTouchArea; private String mDeviceId; + private ValueAnimator mCornerAnimator; + private ValueAnimator mVolumeAnimator; MediaDeviceBaseViewHolder(View view) { super(view); @@ -161,6 +161,7 @@ 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); + initAnimator(); } void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) { @@ -186,20 +187,39 @@ public abstract class MediaOutputBaseAdapter extends boolean showProgressBar, boolean showStatus) { mTwoLineLayout.setVisibility(View.GONE); boolean isActive = showSeekBar || showProgressBar; - final Drawable backgroundDrawable = - isActive - ? mContext.getDrawable(R.drawable.media_output_item_background_active) - .mutate() : mContext.getDrawable( - R.drawable.media_output_item_background) - .mutate(); - backgroundDrawable.setColorFilter(new PorterDuffColorFilter( - isActive ? mController.getColorConnectedItemBackground() - : mController.getColorItemBackground(), - PorterDuff.Mode.SRC_IN)); - mItemLayout.setBackground(backgroundDrawable); + if (!mCornerAnimator.isRunning()) { + final Drawable backgroundDrawable = + showSeekBar + ? mContext.getDrawable( + R.drawable.media_output_item_background_active) + .mutate() : mContext.getDrawable( + R.drawable.media_output_item_background) + .mutate(); + backgroundDrawable.setColorFilter(new PorterDuffColorFilter( + isActive ? mController.getColorConnectedItemBackground() + : mController.getColorItemBackground(), + PorterDuff.Mode.SRC_IN)); + mItemLayout.setBackground(backgroundDrawable); + if (showSeekBar) { + final ClipDrawable clipDrawable = + (ClipDrawable) ((LayerDrawable) mSeekBar.getProgressDrawable()) + .findDrawableByLayerId(android.R.id.progress); + final GradientDrawable progressDrawable = + (GradientDrawable) clipDrawable.getDrawable(); + progressDrawable.setCornerRadius(mController.getActiveRadius()); + } + } else { + mItemLayout.getBackground().setColorFilter(new PorterDuffColorFilter( + isActive ? 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); + if (!showSeekBar) { + mSeekBar.resetVolume(); + } mStatusIcon.setVisibility(showStatus ? View.VISIBLE : View.GONE); mTitleText.setText(title); mTitleText.setVisibility(View.VISIBLE); @@ -257,15 +277,21 @@ public abstract class MediaOutputBaseAdapter extends } } - void initSeekbar(MediaDevice device) { + void initSeekbar(MediaDevice device, boolean isCurrentSeekbarInvisible) { if (!mController.isVolumeControlEnabled(device)) { disableSeekBar(); } - mSeekBar.setMax(device.getMaxVolume()); - mSeekBar.setMin(0); + mSeekBar.setMaxVolume(device.getMaxVolume()); final int currentVolume = device.getCurrentVolume(); - if (mSeekBar.getProgress() != currentVolume) { - mSeekBar.setProgress(currentVolume, true); + if (mSeekBar.getVolume() != currentVolume) { + if (isCurrentSeekbarInvisible) { + animateCornerAndVolume(mSeekBar.getProgress(), + MediaOutputSeekbar.scaleVolumeToProgress(currentVolume)); + } else { + if (!mVolumeAnimator.isStarted()) { + mSeekBar.setVolume(currentVolume); + } + } } mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override @@ -273,7 +299,11 @@ public abstract class MediaOutputBaseAdapter extends if (device == null || !fromUser) { return; } - mController.adjustVolume(device, progress); + int currentVolume = MediaOutputSeekbar.scaleProgressToVolume(progress); + int deviceVolume = device.getCurrentVolume(); + if (currentVolume != deviceVolume) { + mController.adjustVolume(device, currentVolume); + } } @Override @@ -317,65 +347,57 @@ public abstract class MediaOutputBaseAdapter extends }); } - void playSwitchingAnim(@NonNull View from, @NonNull View to) { - final float delta = (float) (mContext.getResources().getDimensionPixelSize( - R.dimen.media_output_dialog_title_anim_y_delta)); - final SeekBar fromSeekBar = from.requireViewById(R.id.volume_seekbar); - final TextView toTitleText = to.requireViewById(R.id.title); - if (fromSeekBar.getVisibility() != View.VISIBLE || toTitleText.getVisibility() - != View.VISIBLE) { - return; - } - mIsAnimating = true; - // Animation for title text - toTitleText.setTypeface(Typeface.create(mContext.getString( - com.android.internal.R.string.config_headlineFontFamilyMedium), - Typeface.NORMAL)); - toTitleText.animate() - .setDuration(ANIM_DURATION) - .translationY(-delta) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - to.requireViewById(R.id.volume_indeterminate_progress).setVisibility( - View.VISIBLE); - // Unset the listener, otherwise this may persist for another view - // property animation - toTitleText.animate().setListener(null); - } - }); - // Animation for seek bar - fromSeekBar.animate() - .alpha(0) - .setDuration(ANIM_DURATION) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - final TextView fromTitleText = from.requireViewById( - R.id.two_line_title); - fromTitleText.setTypeface(Typeface.create(mContext.getString( - com.android.internal.R.string.config_headlineFontFamily), - Typeface.NORMAL)); - fromTitleText.animate() - .setDuration(ANIM_DURATION) - .translationY(delta) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mIsAnimating = false; - notifyDataSetChanged(); - // Unset the listener, otherwise this may persist for - // another view property animation - fromTitleText.animate().setListener(null); - } - }); - // Unset the listener, otherwise this may persist for another view - // property animation - fromSeekBar.animate().setListener(null); - } - }); + private void animateCornerAndVolume(int fromProgress, int toProgress) { + final GradientDrawable layoutBackgroundDrawable = + (GradientDrawable) mItemLayout.getBackground(); + final ClipDrawable clipDrawable = + (ClipDrawable) ((LayerDrawable) mSeekBar.getProgressDrawable()) + .findDrawableByLayerId(android.R.id.progress); + final GradientDrawable progressDrawable = (GradientDrawable) clipDrawable.getDrawable(); + mCornerAnimator.addUpdateListener(animation -> { + float value = (float) animation.getAnimatedValue(); + layoutBackgroundDrawable.setCornerRadius(value); + progressDrawable.setCornerRadius(value); + }); + mVolumeAnimator.setIntValues(fromProgress, toProgress); + mVolumeAnimator.start(); + mCornerAnimator.start(); + } + + private void initAnimator() { + mCornerAnimator = ValueAnimator.ofFloat(mController.getInactiveRadius(), + mController.getActiveRadius()); + mCornerAnimator.setDuration(ANIM_DURATION); + mCornerAnimator.setInterpolator(new LinearInterpolator()); + + mVolumeAnimator = ValueAnimator.ofInt(); + mVolumeAnimator.addUpdateListener(animation -> { + int value = (int) animation.getAnimatedValue(); + mSeekBar.setProgress(value); + }); + mVolumeAnimator.setDuration(ANIM_DURATION); + mVolumeAnimator.setInterpolator(new LinearInterpolator()); + mVolumeAnimator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + mSeekBar.setEnabled(false); + } + + @Override + public void onAnimationEnd(Animator animation) { + mSeekBar.setEnabled(true); + } + + @Override + public void onAnimationCancel(Animator animation) { + mSeekBar.setEnabled(true); + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } + }); } Drawable getSpeakerDrawable() { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index e5e7eb66630b..22bdacae1bcd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -334,7 +334,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements mHeaderSubtitle.setText(subTitle); mHeaderTitle.setGravity(Gravity.NO_GRAVITY); } - if (!mAdapter.isDragging() && !mAdapter.isAnimating()) { + if (!mAdapter.isDragging()) { int currentActivePosition = mAdapter.getCurrentActivePosition(); if (!colorSetUpdated && !deviceSetChanged && currentActivePosition >= 0 && currentActivePosition < mAdapter.getItemCount()) { 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 8723f4fd63bb..082f4bc3efe3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -127,6 +127,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, private int mColorItemBackground; private int mColorConnectedItemBackground; private int mColorPositiveButtonText; + private float mInactiveRadius; + private float mActiveRadius; public enum BroadcastNotifyDialog { ACTION_FIRST_LAUNCH, @@ -163,6 +165,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, R.color.media_dialog_connected_item_background); mColorPositiveButtonText = Utils.getColorStateListDefaultColor(mContext, R.color.media_dialog_solid_button_text); + mInactiveRadius = mContext.getResources().getDimension( + R.dimen.media_output_dialog_background_radius); + mActiveRadius = mContext.getResources().getDimension( + R.dimen.media_output_dialog_active_background_radius); } void start(@NonNull Callback cb) { @@ -417,6 +423,14 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, return mColorItemBackground; } + public float getInactiveRadius() { + return mInactiveRadius; + } + + public float getActiveRadius() { + return mActiveRadius; + } + private void buildMediaDevices(List<MediaDevice> devices) { // For the first time building list, to make sure the top device is the connected device. if (mMediaDevices.isEmpty()) { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java index 9b42b1dea9a4..ba2f006fc58e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java @@ -101,9 +101,10 @@ public class MediaOutputGroupAdapter extends MediaOutputBaseAdapter { mCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> { onCheckBoxClicked(isChecked, device); }); + boolean isCurrentSeekbarInvisible = mSeekBar.getVisibility() == View.GONE; setTwoLineLayout(device, false /* bFocused */, true /* showSeekBar */, false /* showProgressBar */, false /* showSubtitle*/); - initSeekbar(device); + initSeekbar(device, isCurrentSeekbarInvisible); final List<MediaDevice> selectedDevices = mController.getSelectedMediaDevice(); if (isDeviceIncluded(mController.getSelectableMediaDevice(), device)) { mCheckBox.setButtonDrawable(R.drawable.ic_check_box); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java new file mode 100644 index 000000000000..4ff79d689037 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.dialog; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.SeekBar; + +/** + * Customized SeekBar for MediaOutputDialog, apply scale between device volume and progress, to make + * adjustment smoother. + */ +public class MediaOutputSeekbar extends SeekBar { + private static final int SCALE_SIZE = 1000; + + public MediaOutputSeekbar(Context context, AttributeSet attrs) { + super(context, attrs); + setMin(0); + } + + static int scaleProgressToVolume(int progress) { + return progress / SCALE_SIZE; + } + + static int scaleVolumeToProgress(int volume) { + return volume * SCALE_SIZE; + } + + int getVolume() { + return getProgress() / SCALE_SIZE; + } + + void setVolume(int volume) { + setProgress(volume * SCALE_SIZE, true); + } + + void setMaxVolume(int maxVolume) { + setMax(maxVolume * SCALE_SIZE); + } + + void resetVolume() { + setProgress(getMin()); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java index cf6fd249ee33..9256cd32291f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java @@ -185,25 +185,14 @@ public class MediaOutputGroupAdapterTest extends SysuiTestCase { } @Test - public void onBindViewHolder_verifySessionVolume() { - when(mMediaOutputController.getSessionVolume()).thenReturn(TEST_VOLUME); - when(mMediaOutputController.getSessionVolumeMax()).thenReturn(TEST_MAX_VOLUME); - - mGroupAdapter.onBindViewHolder(mGroupViewHolder, 0); - - assertThat(mGroupViewHolder.mSeekBar.getProgress()).isEqualTo(TEST_VOLUME); - assertThat(mGroupViewHolder.mSeekBar.getMax()).isEqualTo(TEST_MAX_VOLUME); - } - - @Test public void onBindViewHolder_verifyDeviceVolume() { when(mMediaDevice1.getCurrentVolume()).thenReturn(TEST_VOLUME); when(mMediaDevice1.getMaxVolume()).thenReturn(TEST_MAX_VOLUME); + mGroupViewHolder.mSeekBar.setVisibility(View.VISIBLE); mGroupAdapter.onBindViewHolder(mGroupViewHolder, 1); - assertThat(mGroupViewHolder.mSeekBar.getProgress()).isEqualTo(TEST_VOLUME); - assertThat(mGroupViewHolder.mSeekBar.getMax()).isEqualTo(TEST_MAX_VOLUME); + assertThat(mGroupViewHolder.mSeekBar.getVolume()).isEqualTo(TEST_VOLUME); } @Test |