1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.notification; 18 19 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SETTINGS_SLIDER; 20 21 import android.content.ContentResolver; 22 import android.content.Context; 23 import android.media.AudioManager; 24 import android.net.Uri; 25 import android.preference.SeekBarVolumizer; 26 import android.text.TextUtils; 27 import android.util.AttributeSet; 28 import android.view.View; 29 import android.widget.ImageView; 30 import android.widget.SeekBar; 31 import android.widget.TextView; 32 33 import androidx.annotation.VisibleForTesting; 34 import androidx.preference.PreferenceViewHolder; 35 36 import com.android.internal.jank.InteractionJankMonitor; 37 import com.android.settings.R; 38 import com.android.settings.widget.SeekBarPreference; 39 40 import java.util.Objects; 41 42 /** A slider preference that directly controls an audio stream volume (no dialog) **/ 43 public class VolumeSeekBarPreference extends SeekBarPreference { 44 private static final String TAG = "VolumeSeekBarPreference"; 45 46 private final InteractionJankMonitor mJankMonitor = InteractionJankMonitor.getInstance(); 47 48 protected SeekBar mSeekBar; 49 private int mStream; 50 private SeekBarVolumizer mVolumizer; 51 private Callback mCallback; 52 private ImageView mIconView; 53 private TextView mSuppressionTextView; 54 private String mSuppressionText; 55 private boolean mMuted; 56 private boolean mZenMuted; 57 private int mIconResId; 58 private int mMuteIconResId; 59 private boolean mStopped; 60 @VisibleForTesting 61 AudioManager mAudioManager; 62 VolumeSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)63 public VolumeSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr, 64 int defStyleRes) { 65 super(context, attrs, defStyleAttr, defStyleRes); 66 setLayoutResource(R.layout.preference_volume_slider); 67 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 68 } 69 VolumeSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr)70 public VolumeSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) { 71 super(context, attrs, defStyleAttr); 72 setLayoutResource(R.layout.preference_volume_slider); 73 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 74 } 75 VolumeSeekBarPreference(Context context, AttributeSet attrs)76 public VolumeSeekBarPreference(Context context, AttributeSet attrs) { 77 super(context, attrs); 78 setLayoutResource(R.layout.preference_volume_slider); 79 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 80 } 81 VolumeSeekBarPreference(Context context)82 public VolumeSeekBarPreference(Context context) { 83 super(context); 84 setLayoutResource(R.layout.preference_volume_slider); 85 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 86 } 87 setStream(int stream)88 public void setStream(int stream) { 89 mStream = stream; 90 setMax(mAudioManager.getStreamMaxVolume(mStream)); 91 // Use getStreamMinVolumeInt for non-public stream type 92 // eg: AudioManager.STREAM_BLUETOOTH_SCO 93 setMin(mAudioManager.getStreamMinVolumeInt(mStream)); 94 setProgress(mAudioManager.getStreamVolume(mStream)); 95 } 96 setCallback(Callback callback)97 public void setCallback(Callback callback) { 98 mCallback = callback; 99 } 100 onActivityResume()101 public void onActivityResume() { 102 if (mStopped) { 103 init(); 104 } 105 } 106 onActivityPause()107 public void onActivityPause() { 108 mStopped = true; 109 if (mVolumizer != null) { 110 mVolumizer.stop(); 111 mVolumizer = null; 112 } 113 } 114 115 @Override onBindViewHolder(PreferenceViewHolder view)116 public void onBindViewHolder(PreferenceViewHolder view) { 117 super.onBindViewHolder(view); 118 mSeekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar); 119 mIconView = (ImageView) view.findViewById(com.android.internal.R.id.icon); 120 mSuppressionTextView = (TextView) view.findViewById(R.id.suppression_text); 121 init(); 122 } 123 init()124 protected void init() { 125 if (mSeekBar == null) return; 126 final SeekBarVolumizer.Callback sbvc = new SeekBarVolumizer.Callback() { 127 @Override 128 public void onSampleStarting(SeekBarVolumizer sbv) { 129 if (mCallback != null) { 130 mCallback.onSampleStarting(sbv); 131 } 132 } 133 @Override 134 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) { 135 if (mCallback != null) { 136 mCallback.onStreamValueChanged(mStream, progress); 137 } 138 } 139 @Override 140 public void onMuted(boolean muted, boolean zenMuted) { 141 if (mMuted == muted && mZenMuted == zenMuted) return; 142 mMuted = muted; 143 mZenMuted = zenMuted; 144 updateIconView(); 145 } 146 @Override 147 public void onStartTrackingTouch(SeekBarVolumizer sbv) { 148 if (mCallback != null) { 149 mCallback.onStartTrackingTouch(sbv); 150 } 151 mJankMonitor.begin(InteractionJankMonitor.Configuration.Builder 152 .withView(CUJ_SETTINGS_SLIDER, mSeekBar) 153 .setTag(getKey())); 154 } 155 @Override 156 public void onStopTrackingTouch(SeekBarVolumizer sbv) { 157 mJankMonitor.end(CUJ_SETTINGS_SLIDER); 158 } 159 }; 160 final Uri sampleUri = mStream == AudioManager.STREAM_MUSIC ? getMediaVolumeUri() : null; 161 if (mVolumizer == null) { 162 mVolumizer = new SeekBarVolumizer(getContext(), mStream, sampleUri, sbvc); 163 } 164 mVolumizer.start(); 165 mVolumizer.setSeekBar(mSeekBar); 166 updateIconView(); 167 updateSuppressionText(); 168 if (!isEnabled()) { 169 mSeekBar.setEnabled(false); 170 mVolumizer.stop(); 171 } 172 } 173 updateIconView()174 protected void updateIconView() { 175 if (mIconView == null) return; 176 if (mIconResId != 0) { 177 mIconView.setImageResource(mIconResId); 178 } else if (mMuteIconResId != 0 && mMuted && !mZenMuted) { 179 mIconView.setImageResource(mMuteIconResId); 180 } else { 181 mIconView.setImageDrawable(getIcon()); 182 } 183 } 184 showIcon(int resId)185 public void showIcon(int resId) { 186 // Instead of using setIcon, which will trigger listeners, this just decorates the 187 // preference temporarily with a new icon. 188 if (mIconResId == resId) return; 189 mIconResId = resId; 190 updateIconView(); 191 } 192 setMuteIcon(int resId)193 public void setMuteIcon(int resId) { 194 if (mMuteIconResId == resId) return; 195 mMuteIconResId = resId; 196 updateIconView(); 197 } 198 getMediaVolumeUri()199 private Uri getMediaVolumeUri() { 200 return Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" 201 + getContext().getPackageName() 202 + "/" + R.raw.media_volume); 203 } 204 setSuppressionText(String text)205 public void setSuppressionText(String text) { 206 if (Objects.equals(text, mSuppressionText)) return; 207 mSuppressionText = text; 208 updateSuppressionText(); 209 } 210 updateSuppressionText()211 protected void updateSuppressionText() { 212 if (mSuppressionTextView != null && mSeekBar != null) { 213 mSuppressionTextView.setText(mSuppressionText); 214 final boolean showSuppression = !TextUtils.isEmpty(mSuppressionText); 215 mSuppressionTextView.setVisibility(showSuppression ? View.VISIBLE : View.GONE); 216 } 217 } 218 219 public interface Callback { onSampleStarting(SeekBarVolumizer sbv)220 void onSampleStarting(SeekBarVolumizer sbv); onStreamValueChanged(int stream, int progress)221 void onStreamValueChanged(int stream, int progress); 222 223 /** 224 * Callback reporting that the seek bar is start tracking. 225 */ onStartTrackingTouch(SeekBarVolumizer sbv)226 void onStartTrackingTouch(SeekBarVolumizer sbv); 227 } 228 } 229