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.car.Car;
20 import android.car.CarNotConnectedException;
21 import android.car.media.CarAudioManager;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.ServiceConnection;
25 import android.media.AudioManager;
26 import android.media.IVolumeController;
27 import android.media.Ringtone;
28 import android.media.RingtoneManager;
29 import android.net.Uri;
30 import android.os.Handler;
31 import android.os.IBinder;
32 import android.os.Looper;
33 import android.os.RemoteException;
34 import android.provider.Settings;
35 import android.util.Log;
36 import android.view.View;
37 import android.widget.ImageView;
38 import android.widget.SeekBar;
39 import android.widget.SeekBar.OnSeekBarChangeListener;
40 import android.widget.TextView;
41 
42 import com.android.car.settings.R;
43 
44 /**
45  * Contains logic about volume controller UI.
46  */
47 public class VolumeControllerPresenter implements OnSeekBarChangeListener {
48 
49     private static final String TAG = "SeekBarVolumizer";
50     private static final int AUDIO_FEEDBACK_DELAY_MS = 1500;
51 
52     private final Handler mHandler = new Handler(Looper.getMainLooper());
53     private final SeekBar mSeekBar;
54     private final int mStreamType;
55     private final Ringtone mRingtone;
56     private final VolumnCallback mVolumeCallback = new VolumnCallback();
57 
58     private CarAudioManager mCarAudioManager;
59 
onServiceConnected(Car car)60     public void onServiceConnected(Car car) {
61         try {
62             mCarAudioManager = (CarAudioManager) car.getCarManager(Car.AUDIO_SERVICE);
63             mCarAudioManager.setVolumeController(mVolumeCallback);
64             mSeekBar.setMax(mCarAudioManager.getStreamMaxVolume(mStreamType));
65             mSeekBar.setProgress(mCarAudioManager.getStreamVolume(mStreamType));
66             mSeekBar.setOnSeekBarChangeListener(VolumeControllerPresenter.this);
67         } catch (CarNotConnectedException e) {
68             Log.e(TAG, "Car is not connected!", e);
69         }
70     }
71 
onServiceDisconnected()72     public void onServiceDisconnected() {
73         mSeekBar.setOnSeekBarChangeListener(null);
74         mCarAudioManager = null;
75     }
76 
VolumeControllerPresenter(Context context, View volumeControllerView, int streamType, Uri sampleUri, int titleStringResId, int iconResId)77     public VolumeControllerPresenter(Context context, View volumeControllerView,
78             int streamType, Uri sampleUri, int titleStringResId, int iconResId) {
79         mSeekBar = (SeekBar) volumeControllerView.findViewById(R.id.seekbar);
80         mStreamType = streamType;
81         Uri ringtoneUri;
82 
83         if (sampleUri == null) {
84             switch (mStreamType) {
85                 case AudioManager.STREAM_RING:
86                     ringtoneUri = Settings.System.DEFAULT_RINGTONE_URI;
87                     break;
88                 case AudioManager.STREAM_NOTIFICATION:
89                     ringtoneUri = Settings.System.DEFAULT_NOTIFICATION_URI;
90                     break;
91                 default:
92                     ringtoneUri = Settings.System.DEFAULT_ALARM_ALERT_URI;
93             }
94         } else {
95             ringtoneUri = sampleUri;
96         }
97         mRingtone = RingtoneManager.getRingtone(context, ringtoneUri);
98         if (mRingtone != null) {
99             mRingtone.setStreamType(mStreamType);
100         }
101         ((ImageView) volumeControllerView.findViewById(R.id.icon)).setImageResource(iconResId);
102         ((TextView) volumeControllerView.findViewById(R.id.stream_name)).setText(titleStringResId);
103     }
104 
stop()105     public void stop() {
106         mHandler.removeCallbacksAndMessages(null);
107         mRingtone.stop();
108     }
109 
110     @Override
onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)111     public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
112         try {
113             if (mCarAudioManager == null) {
114                 Log.w(TAG, "CarAudiomanager not available, Car is not connected!");
115                 return;
116             }
117             mCarAudioManager.setStreamVolume(mStreamType, progress, AudioManager.FLAG_PLAY_SOUND);
118             playAudioFeedback();
119         } catch (CarNotConnectedException e) {
120             Log.e(TAG, "Car is not connected!", e);
121         }
122     }
123 
124     @Override
onStartTrackingTouch(SeekBar seekBar)125     public void onStartTrackingTouch(SeekBar seekBar) {
126 
127     }
128 
129     @Override
onStopTrackingTouch(SeekBar seekBar)130     public void onStopTrackingTouch(SeekBar seekBar) {
131         playAudioFeedback();
132     }
133 
playAudioFeedback()134     private void playAudioFeedback() {
135         mHandler.removeCallbacksAndMessages(null);
136         mRingtone.play();
137         mHandler.postDelayed(() -> {
138             if (mRingtone.isPlaying()) {
139                 mRingtone.stop();
140             }
141         }, AUDIO_FEEDBACK_DELAY_MS);
142     }
143 
144     /**
145      * The interface has a terrible name, it is actually a callback, so here name it accordingly.
146      */
147     private final class VolumnCallback extends IVolumeController.Stub {
148 
149         private final String TAG = VolumeControllerPresenter.TAG + ".cb";
150 
151         @Override
displaySafeVolumeWarning(int flags)152         public void displaySafeVolumeWarning(int flags) throws RemoteException {
153         }
154 
155         @Override
volumeChanged(int streamType, int flags)156         public void volumeChanged(int streamType, int flags) throws RemoteException {
157             if (streamType != mStreamType) {
158                 return;
159             }
160             try {
161                 if (mCarAudioManager == null) {
162                     Log.w(TAG, "CarAudiomanager not available, Car is not connected!");
163                     return;
164                 }
165                 int volume = mCarAudioManager.getStreamVolume(mStreamType);
166                 if (mSeekBar.getProgress() == volume) {
167                     return;
168                 }
169                 mSeekBar.setProgress(volume);
170             } catch (CarNotConnectedException e) {
171                 Log.e(TAG, "Car is not connected!", e);
172             }
173         }
174 
175         // this is not mute of this stream
176         @Override
masterMuteChanged(int flags)177         public void masterMuteChanged(int flags) throws RemoteException {
178         }
179 
180         @Override
setLayoutDirection(int layoutDirection)181         public void setLayoutDirection(int layoutDirection) throws RemoteException {
182         }
183 
184         @Override
dismiss()185         public void dismiss() throws RemoteException {
186         }
187 
188         @Override
setA11yMode(int mode)189         public void setA11yMode(int mode) {
190         }
191     }
192 }
193