blob: 3b4ca48046eb047cd0173b5b2b2da2b9db25c9b3 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.animation.Animator;
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;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.android.settingslib.Utils;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.R;
import java.util.List;
/**
* Base adapter for media output dialog.
*/
public abstract class MediaOutputBaseAdapter extends
RecyclerView.Adapter<MediaOutputBaseAdapter.MediaDeviceBaseViewHolder> {
static final int CUSTOMIZED_ITEM_PAIR_NEW = 1;
static final int CUSTOMIZED_ITEM_GROUP = 2;
static final int CUSTOMIZED_ITEM_DYNAMIC_GROUP = 3;
protected final MediaOutputController mController;
private int mMargin;
Context mContext;
View mHolderView;
boolean mIsDragging;
int mCurrentActivePosition;
private boolean mIsInitVolumeFirstTime;
public MediaOutputBaseAdapter(MediaOutputController controller) {
mController = controller;
mIsDragging = false;
mCurrentActivePosition = -1;
mIsInitVolumeFirstTime = true;
}
@Override
public MediaDeviceBaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
int viewType) {
mContext = viewGroup.getContext();
mMargin = mContext.getResources().getDimensionPixelSize(
R.dimen.media_output_dialog_list_margin);
mHolderView = LayoutInflater.from(mContext).inflate(R.layout.media_output_list_item,
viewGroup, false);
return null;
}
void updateColorScheme(WallpaperColors wallpaperColors, boolean isDarkTheme) {
mController.setCurrentColorScheme(wallpaperColors, isDarkTheme);
}
CharSequence getItemTitle(MediaDevice device) {
return device.getName();
}
boolean isCurrentlyConnected(MediaDevice device) {
return TextUtils.equals(device.getId(),
mController.getCurrentConnectedMediaDevice().getId())
|| (mController.getSelectedMediaDevice().size() == 1
&& isDeviceIncluded(mController.getSelectedMediaDevice(), device));
}
boolean isDeviceIncluded(List<MediaDevice> deviceList, MediaDevice targetDevice) {
for (MediaDevice device : deviceList) {
if (TextUtils.equals(device.getId(), targetDevice.getId())) {
return true;
}
}
return false;
}
boolean isDragging() {
return mIsDragging;
}
int getCurrentActivePosition() {
return mCurrentActivePosition;
}
public MediaOutputController getController() {
return mController;
}
/**
* ViewHolder for binding device view.
*/
abstract class MediaDeviceBaseViewHolder extends RecyclerView.ViewHolder {
private static final int ANIM_DURATION = 500;
final LinearLayout mContainerLayout;
final FrameLayout mItemLayout;
final TextView mTitleText;
final TextView mTwoLineTitleText;
final TextView mSubTitleText;
final ImageView mTitleIcon;
final ProgressBar mProgressBar;
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);
mContainerLayout = view.requireViewById(R.id.device_container);
mItemLayout = view.requireViewById(R.id.item_layout);
mTitleText = view.requireViewById(R.id.title);
mSubTitleText = view.requireViewById(R.id.subtitle);
mTwoLineLayout = view.requireViewById(R.id.two_line_layout);
mTwoLineTitleText = view.requireViewById(R.id.two_line_title);
mTitleIcon = view.requireViewById(R.id.title_icon);
mProgressBar = view.requireViewById(R.id.volume_indeterminate_progress);
mSeekBar = view.requireViewById(R.id.volume_seekbar);
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) {
mDeviceId = device.getId();
}
abstract void onBind(int customizedItem, boolean topMargin, boolean bottomMargin);
void setSingleLineLayout(CharSequence title, boolean bFocused) {
setSingleLineLayout(title, bFocused, false, false, false);
}
void setSingleLineLayout(CharSequence title, boolean bFocused, boolean showSeekBar,
boolean showProgressBar, boolean showStatus) {
mTwoLineLayout.setVisibility(View.GONE);
boolean isActive = showSeekBar || showProgressBar;
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);
}
void setTwoLineLayout(MediaDevice device, boolean bFocused, boolean showSeekBar,
boolean showProgressBar, boolean showSubtitle) {
setTwoLineLayout(device, null, bFocused, showSeekBar, showProgressBar, showSubtitle,
false);
}
void setTwoLineLayout(MediaDevice device, boolean bFocused, boolean showSeekBar,
boolean showProgressBar, boolean showSubtitle, boolean showStatus) {
setTwoLineLayout(device, null, bFocused, showSeekBar, showProgressBar, showSubtitle,
showStatus);
}
void setTwoLineLayout(CharSequence title, boolean bFocused, boolean showSeekBar,
boolean showProgressBar, boolean showSubtitle) {
setTwoLineLayout(null, title, bFocused, showSeekBar, showProgressBar, showSubtitle,
false);
}
private void setTwoLineLayout(MediaDevice device, CharSequence title, boolean bFocused,
boolean showSeekBar, boolean showProgressBar, boolean showSubtitle,
boolean showStatus) {
mTitleText.setVisibility(View.GONE);
mTwoLineLayout.setVisibility(View.VISIBLE);
mStatusIcon.setVisibility(showStatus ? View.VISIBLE : View.GONE);
mSeekBar.setAlpha(1);
mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE);
final Drawable backgroundDrawable = mContext.getDrawable(
R.drawable.media_output_item_background)
.mutate();
backgroundDrawable.setColorFilter(new PorterDuffColorFilter(
mController.getColorItemBackground(),
PorterDuff.Mode.SRC_IN));
mItemLayout.setBackground(backgroundDrawable);
mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
mSubTitleText.setVisibility(showSubtitle ? View.VISIBLE : View.GONE);
mTwoLineTitleText.setTranslationY(0);
if (device == null) {
mTwoLineTitleText.setText(title);
} else {
mTwoLineTitleText.setText(getItemTitle(device));
}
if (bFocused) {
mTwoLineTitleText.setTypeface(Typeface.create(mContext.getString(
com.android.internal.R.string.config_headlineFontFamilyMedium),
Typeface.NORMAL));
} else {
mTwoLineTitleText.setTypeface(Typeface.create(mContext.getString(
com.android.internal.R.string.config_headlineFontFamily), Typeface.NORMAL));
}
}
void initSeekbar(MediaDevice device, boolean isCurrentSeekbarInvisible) {
if (!mController.isVolumeControlEnabled(device)) {
disableSeekBar();
} else {
enableSeekBar();
}
mSeekBar.setMaxVolume(device.getMaxVolume());
final int currentVolume = device.getCurrentVolume();
if (mSeekBar.getVolume() != currentVolume) {
if (isCurrentSeekbarInvisible && !mIsInitVolumeFirstTime) {
animateCornerAndVolume(mSeekBar.getProgress(),
MediaOutputSeekbar.scaleVolumeToProgress(currentVolume));
} else {
if (!mVolumeAnimator.isStarted()) {
mSeekBar.setVolume(currentVolume);
}
}
}
if (mIsInitVolumeFirstTime) {
mIsInitVolumeFirstTime = false;
}
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 deviceVolume = device.getCurrentVolume();
if (currentVolume != deviceVolume) {
mController.adjustVolume(device, currentVolume);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
mIsDragging = true;
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
mIsDragging = false;
}
});
}
void initMutingExpectedDevice() {
disableSeekBar();
final Drawable backgroundDrawable = mContext.getDrawable(
R.drawable.media_output_item_background_active)
.mutate();
backgroundDrawable.setColorFilter(
new PorterDuffColorFilter(mController.getColorConnectedItemBackground(),
PorterDuff.Mode.SRC_IN));
mItemLayout.setBackground(backgroundDrawable);
}
void initSessionSeekbar() {
disableSeekBar();
mSeekBar.setMax(mController.getSessionVolumeMax());
mSeekBar.setMin(0);
final int currentVolume = mController.getSessionVolume();
if (mSeekBar.getProgress() != currentVolume) {
mSeekBar.setProgress(currentVolume, true);
}
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (!fromUser) {
return;
}
mController.adjustSessionVolume(progress);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
mIsDragging = true;
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
mIsDragging = false;
}
});
}
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() {
final Drawable drawable = mContext.getDrawable(R.drawable.ic_speaker_group_black_24dp)
.mutate();
drawable.setColorFilter(
new PorterDuffColorFilter(Utils.getColorStateListDefaultColor(mContext,
R.color.media_dialog_item_main_content),
PorterDuff.Mode.SRC_IN));
return drawable;
}
protected void disableSeekBar() {
mSeekBar.setEnabled(false);
mSeekBar.setOnTouchListener((v, event) -> true);
}
private void enableSeekBar() {
mSeekBar.setEnabled(true);
mSeekBar.setOnTouchListener((v, event) -> false);
}
protected void setUpDeviceIcon(MediaDevice device) {
ThreadUtils.postOnBackgroundThread(() -> {
Icon icon = mController.getDeviceIconCompat(device).toIcon(mContext);
ThreadUtils.postOnMainThread(() -> {
if (!TextUtils.equals(mDeviceId, device.getId())) {
return;
}
mTitleIcon.setImageIcon(icon);
});
});
}
}
}