1 /* 2 * Copyright (C) 2017 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.car.settings.sound; 18 19 import android.annotation.DrawableRes; 20 import android.annotation.StringRes; 21 import android.car.CarNotConnectedException; 22 import android.car.media.CarAudioManager; 23 import android.content.Context; 24 import android.media.AudioAttributes; 25 import android.media.Ringtone; 26 import android.media.RingtoneManager; 27 import android.net.Uri; 28 import android.os.Handler; 29 import android.os.Looper; 30 import android.provider.Settings; 31 import android.widget.SeekBar; 32 33 import androidx.car.widget.SeekbarListItem; 34 35 import com.android.car.settings.common.Logger; 36 37 /** 38 * Contains logic about volume controller UI. 39 */ 40 public class VolumeLineItem extends SeekbarListItem implements SeekBar.OnSeekBarChangeListener { 41 private static final Logger LOG = new Logger(VolumeLineItem.class); 42 private static final int AUDIO_FEEDBACK_DURATION_MS = 1000; 43 44 private final Handler mUiHandler; 45 private final Ringtone mRingtone; 46 private final int mVolumeGroupId; 47 private final CarAudioManager mCarAudioManager; 48 VolumeLineItem( Context context, CarAudioManager carAudioManager, int volumeGroupId, @AudioAttributes.AttributeUsage int usage, @DrawableRes int iconResId, @StringRes int titleId)49 public VolumeLineItem( 50 Context context, 51 CarAudioManager carAudioManager, 52 int volumeGroupId, 53 @AudioAttributes.AttributeUsage int usage, 54 @DrawableRes int iconResId, 55 @StringRes int titleId) throws CarNotConnectedException { 56 super(context); 57 mCarAudioManager = carAudioManager; 58 mUiHandler = new Handler(Looper.getMainLooper()); 59 mRingtone = RingtoneManager.getRingtone(context, getRingtoneUri(usage)); 60 mRingtone.setAudioAttributes(new AudioAttributes.Builder().setUsage(usage).build()); 61 mVolumeGroupId = volumeGroupId; 62 setMax(getMaxSeekbarValue()); 63 updateProgress(); 64 setOnSeekBarChangeListener(this); 65 setText(context.getString(titleId)); 66 setPrimaryActionIcon(iconResId); 67 } 68 69 @Override onStartTrackingTouch(SeekBar seekBar)70 public void onStartTrackingTouch(SeekBar seekBar) { 71 // no-op 72 } 73 74 @Override onStopTrackingTouch(SeekBar seekBar)75 public void onStopTrackingTouch(SeekBar seekBar) { 76 // no-op 77 } 78 79 @Override onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)80 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 81 if (!fromUser) { 82 // For instance, if this event is originated from AudioService, 83 // we can ignore it as it has already been handled and doesn't need to be 84 // sent back down again. 85 return; 86 } 87 try { 88 if (mCarAudioManager == null) { 89 LOG.w("Ignoring volume change event because the car isn't connected"); 90 return; 91 } 92 // AudioManager.FLAG_PLAY_SOUND does not guarantee play sound, use our own 93 // playback here instead. 94 mCarAudioManager.setGroupVolume(mVolumeGroupId, progress, 0); 95 playAudioFeedback(); 96 } catch (CarNotConnectedException e) { 97 LOG.e("Car is not connected!", e); 98 } 99 } 100 101 /** 102 * Clean ups 103 */ stop()104 public void stop() { 105 mUiHandler.removeCallbacksAndMessages(null); 106 mRingtone.stop(); 107 } 108 getVolumeGroupId()109 public int getVolumeGroupId() { 110 return mVolumeGroupId; 111 } 112 113 /** 114 * Gets the latest progress. 115 */ updateProgress()116 public void updateProgress() { 117 setProgress(getSeekbarValue()); 118 } 119 playAudioFeedback()120 private void playAudioFeedback() { 121 mUiHandler.removeCallbacksAndMessages(null); 122 mRingtone.play(); 123 mUiHandler.postDelayed(() -> { 124 if (mRingtone.isPlaying()) { 125 mRingtone.stop(); 126 } 127 }, AUDIO_FEEDBACK_DURATION_MS); 128 } 129 130 // TODO: bundle car-specific audio sample assets in res/raw by usage getRingtoneUri(@udioAttributes.AttributeUsage int usage)131 private Uri getRingtoneUri(@AudioAttributes.AttributeUsage int usage) { 132 switch (usage) { 133 case AudioAttributes.USAGE_NOTIFICATION: 134 return Settings.System.DEFAULT_NOTIFICATION_URI; 135 case AudioAttributes.USAGE_ALARM: 136 return Settings.System.DEFAULT_ALARM_ALERT_URI; 137 default: 138 return Settings.System.DEFAULT_RINGTONE_URI; 139 } 140 } 141 getSeekbarValue()142 private int getSeekbarValue() { 143 try { 144 return mCarAudioManager.getGroupVolume(mVolumeGroupId); 145 } catch (CarNotConnectedException e) { 146 LOG.e("Car is not connected!", e); 147 } 148 return 0; 149 } 150 getMaxSeekbarValue()151 private int getMaxSeekbarValue() { 152 try { 153 return mCarAudioManager.getGroupMaxVolume(mVolumeGroupId); 154 } catch (CarNotConnectedException e) { 155 LOG.e("Car is not connected!", e); 156 } 157 return 0; 158 } 159 } 160