• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 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 android.view;
18 
19 import android.bluetooth.HeadsetBase;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.res.Resources;
23 import android.media.AudioManager;
24 import android.media.AudioService;
25 import android.media.AudioSystem;
26 import android.media.RingtoneManager;
27 import android.media.ToneGenerator;
28 import android.net.Uri;
29 import android.os.Handler;
30 import android.os.Message;
31 import android.os.Vibrator;
32 import android.util.Config;
33 import android.util.Log;
34 import android.widget.ImageView;
35 import android.widget.ProgressBar;
36 import android.widget.TextView;
37 import android.widget.Toast;
38 
39 /**
40  * Handle the volume up and down keys.
41  *
42  * This code really should be moved elsewhere.
43  *
44  * @hide
45  */
46 public class VolumePanel extends Handler
47 {
48     private static final String TAG = "VolumePanel";
49     private static boolean LOGD = false;
50 
51     /**
52      * The delay before playing a sound. This small period exists so the user
53      * can press another key (non-volume keys, too) to have it NOT be audible.
54      * <p>
55      * PhoneWindow will implement this part.
56      */
57     public static final int PLAY_SOUND_DELAY = 300;
58 
59     /**
60      * The delay before vibrating. This small period exists so if the user is
61      * moving to silent mode, it will not emit a short vibrate (it normally
62      * would since vibrate is between normal mode and silent mode using hardware
63      * keys).
64      */
65     public static final int VIBRATE_DELAY = 300;
66 
67     private static final int VIBRATE_DURATION = 300;
68     private static final int BEEP_DURATION = 150;
69     private static final int MAX_VOLUME = 100;
70     private static final int FREE_DELAY = 10000;
71 
72     private static final int MSG_VOLUME_CHANGED = 0;
73     private static final int MSG_FREE_RESOURCES = 1;
74     private static final int MSG_PLAY_SOUND = 2;
75     private static final int MSG_STOP_SOUNDS = 3;
76     private static final int MSG_VIBRATE = 4;
77 
78     private static final int RINGTONE_VOLUME_TEXT = com.android.internal.R.string.volume_ringtone;
79     private static final int MUSIC_VOLUME_TEXT = com.android.internal.R.string.volume_music;
80     private static final int INCALL_VOLUME_TEXT = com.android.internal.R.string.volume_call;
81     private static final int ALARM_VOLUME_TEXT = com.android.internal.R.string.volume_alarm;
82     private static final int UNKNOWN_VOLUME_TEXT = com.android.internal.R.string.volume_unknown;
83     private static final int NOTIFICATION_VOLUME_TEXT =
84             com.android.internal.R.string.volume_notification;
85     private static final int BLUETOOTH_INCALL_VOLUME_TEXT =
86             com.android.internal.R.string.volume_bluetooth_call;
87 
88     protected Context mContext;
89     private AudioManager mAudioManager;
90     protected AudioService mAudioService;
91     private boolean mRingIsSilent;
92 
93     private final Toast mToast;
94     private final View mView;
95     private final TextView mMessage;
96     private final TextView mAdditionalMessage;
97     private final ImageView mSmallStreamIcon;
98     private final ImageView mLargeStreamIcon;
99     private final ProgressBar mLevel;
100 
101     // Synchronize when accessing this
102     private ToneGenerator mToneGenerators[];
103     private Vibrator mVibrator;
104 
VolumePanel(Context context, AudioService volumeService)105     public VolumePanel(Context context, AudioService volumeService) {
106         mContext = context;
107         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
108         mAudioService = volumeService;
109         mToast = new Toast(context);
110 
111         LayoutInflater inflater = (LayoutInflater) context
112                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
113         View view = mView = inflater.inflate(com.android.internal.R.layout.volume_adjust, null);
114         mMessage = (TextView) view.findViewById(com.android.internal.R.id.message);
115         mAdditionalMessage =
116                 (TextView) view.findViewById(com.android.internal.R.id.additional_message);
117         mSmallStreamIcon = (ImageView) view.findViewById(com.android.internal.R.id.other_stream_icon);
118         mLargeStreamIcon = (ImageView) view.findViewById(com.android.internal.R.id.ringer_stream_icon);
119         mLevel = (ProgressBar) view.findViewById(com.android.internal.R.id.level);
120 
121         mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()];
122         mVibrator = new Vibrator();
123     }
124 
postVolumeChanged(int streamType, int flags)125     public void postVolumeChanged(int streamType, int flags) {
126         if (hasMessages(MSG_VOLUME_CHANGED)) return;
127         removeMessages(MSG_FREE_RESOURCES);
128         obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget();
129     }
130 
131     /**
132      * Override this if you have other work to do when the volume changes (for
133      * example, vibrating, playing a sound, etc.). Make sure to call through to
134      * the superclass implementation.
135      */
onVolumeChanged(int streamType, int flags)136     protected void onVolumeChanged(int streamType, int flags) {
137 
138         if (LOGD) Log.d(TAG, "onVolumeChanged(streamType: " + streamType + ", flags: " + flags + ")");
139 
140         if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
141             onShowVolumeChanged(streamType, flags);
142         }
143 
144         if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) {
145             removeMessages(MSG_PLAY_SOUND);
146             sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY);
147         }
148 
149         if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) {
150             removeMessages(MSG_PLAY_SOUND);
151             removeMessages(MSG_VIBRATE);
152             onStopSounds();
153         }
154 
155         removeMessages(MSG_FREE_RESOURCES);
156         sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
157     }
158 
onShowVolumeChanged(int streamType, int flags)159     protected void onShowVolumeChanged(int streamType, int flags) {
160         int index = mAudioService.getStreamVolume(streamType);
161         int message = UNKNOWN_VOLUME_TEXT;
162         int additionalMessage = 0;
163         mRingIsSilent = false;
164 
165         if (LOGD) {
166             Log.d(TAG, "onShowVolumeChanged(streamType: " + streamType
167                     + ", flags: " + flags + "), index: " + index);
168         }
169 
170         // get max volume for progress bar
171         int max = mAudioService.getStreamMaxVolume(streamType);
172 
173         switch (streamType) {
174 
175             case AudioManager.STREAM_RING: {
176                 setRingerIcon();
177                 message = RINGTONE_VOLUME_TEXT;
178                 Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri(
179                         mContext, RingtoneManager.TYPE_RINGTONE);
180                 if (ringuri == null) {
181                     additionalMessage =
182                         com.android.internal.R.string.volume_music_hint_silent_ringtone_selected;
183                     mRingIsSilent = true;
184                 }
185                 break;
186             }
187 
188             case AudioManager.STREAM_MUSIC: {
189                 message = MUSIC_VOLUME_TEXT;
190                 if (mAudioManager.isBluetoothA2dpOn()) {
191                     additionalMessage =
192                         com.android.internal.R.string.volume_music_hint_playing_through_bluetooth;
193                     setLargeIcon(com.android.internal.R.drawable.ic_volume_bluetooth_ad2p);
194                 } else {
195                     setSmallIcon(index);
196                 }
197                 break;
198             }
199 
200             case AudioManager.STREAM_VOICE_CALL: {
201                 /*
202                  * For in-call voice call volume, there is no inaudible volume.
203                  * Rescale the UI control so the progress bar doesn't go all
204                  * the way to zero and don't show the mute icon.
205                  */
206                 index++;
207                 max++;
208                 message = INCALL_VOLUME_TEXT;
209                 setSmallIcon(index);
210                 break;
211             }
212 
213             case AudioManager.STREAM_ALARM: {
214                 message = ALARM_VOLUME_TEXT;
215                 setSmallIcon(index);
216                 break;
217             }
218 
219             case AudioManager.STREAM_NOTIFICATION: {
220                 message = NOTIFICATION_VOLUME_TEXT;
221                 setSmallIcon(index);
222                 Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri(
223                         mContext, RingtoneManager.TYPE_NOTIFICATION);
224                 if (ringuri == null) {
225                     additionalMessage =
226                         com.android.internal.R.string.volume_music_hint_silent_ringtone_selected;
227                     mRingIsSilent = true;
228                 }
229                 break;
230             }
231 
232             case AudioManager.STREAM_BLUETOOTH_SCO: {
233                 /*
234                  * For in-call voice call volume, there is no inaudible volume.
235                  * Rescale the UI control so the progress bar doesn't go all
236                  * the way to zero and don't show the mute icon.
237                  */
238                 index++;
239                 max++;
240                 message = BLUETOOTH_INCALL_VOLUME_TEXT;
241                 setLargeIcon(com.android.internal.R.drawable.ic_volume_bluetooth_in_call);
242                 break;
243             }
244         }
245 
246         String messageString = Resources.getSystem().getString(message);
247         if (!mMessage.getText().equals(messageString)) {
248             mMessage.setText(messageString);
249         }
250 
251         if (additionalMessage == 0) {
252             mAdditionalMessage.setVisibility(View.GONE);
253         } else {
254             mAdditionalMessage.setVisibility(View.VISIBLE);
255             mAdditionalMessage.setText(Resources.getSystem().getString(additionalMessage));
256         }
257 
258         if (max != mLevel.getMax()) {
259             mLevel.setMax(max);
260         }
261         mLevel.setProgress(index);
262 
263         mToast.setView(mView);
264         mToast.setDuration(Toast.LENGTH_SHORT);
265         mToast.setGravity(Gravity.TOP, 0, 0);
266         mToast.show();
267 
268         // Do a little vibrate if applicable (only when going into vibrate mode)
269         if ((flags & AudioManager.FLAG_VIBRATE) != 0 &&
270                 mAudioService.isStreamAffectedByRingerMode(streamType) &&
271                 mAudioService.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE &&
272                 mAudioService.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER)) {
273             sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY);
274         }
275     }
276 
onPlaySound(int streamType, int flags)277     protected void onPlaySound(int streamType, int flags) {
278 
279         if (hasMessages(MSG_STOP_SOUNDS)) {
280             removeMessages(MSG_STOP_SOUNDS);
281             // Force stop right now
282             onStopSounds();
283         }
284 
285         synchronized (this) {
286             ToneGenerator toneGen = getOrCreateToneGenerator(streamType);
287             toneGen.startTone(ToneGenerator.TONE_PROP_BEEP);
288         }
289 
290         sendMessageDelayed(obtainMessage(MSG_STOP_SOUNDS), BEEP_DURATION);
291     }
292 
onStopSounds()293     protected void onStopSounds() {
294 
295         synchronized (this) {
296             int numStreamTypes = AudioSystem.getNumStreamTypes();
297             for (int i = numStreamTypes - 1; i >= 0; i--) {
298                 ToneGenerator toneGen = mToneGenerators[i];
299                 if (toneGen != null) {
300                     toneGen.stopTone();
301                 }
302             }
303         }
304     }
305 
onVibrate()306     protected void onVibrate() {
307 
308         // Make sure we ended up in vibrate ringer mode
309         if (mAudioService.getRingerMode() != AudioManager.RINGER_MODE_VIBRATE) {
310             return;
311         }
312 
313         mVibrator.vibrate(VIBRATE_DURATION);
314     }
315 
316     /**
317      * Lock on this VolumePanel instance as long as you use the returned ToneGenerator.
318      */
getOrCreateToneGenerator(int streamType)319     private ToneGenerator getOrCreateToneGenerator(int streamType) {
320         synchronized (this) {
321             if (mToneGenerators[streamType] == null) {
322                 return mToneGenerators[streamType] = new ToneGenerator(streamType, MAX_VOLUME);
323             } else {
324                 return mToneGenerators[streamType];
325             }
326         }
327     }
328 
329     /**
330      * Makes the small icon visible, and hides the large icon.
331      *
332      * @param index The volume index, where 0 means muted.
333      */
setSmallIcon(int index)334     private void setSmallIcon(int index) {
335         mLargeStreamIcon.setVisibility(View.GONE);
336         mSmallStreamIcon.setVisibility(View.VISIBLE);
337 
338         mSmallStreamIcon.setImageResource(index == 0
339                 ? com.android.internal.R.drawable.ic_volume_off_small
340                 : com.android.internal.R.drawable.ic_volume_small);
341     }
342 
343     /**
344      * Makes the large image view visible with the given icon.
345      *
346      * @param resId The icon to display.
347      */
setLargeIcon(int resId)348     private void setLargeIcon(int resId) {
349         mSmallStreamIcon.setVisibility(View.GONE);
350         mLargeStreamIcon.setVisibility(View.VISIBLE);
351         mLargeStreamIcon.setImageResource(resId);
352     }
353 
354     /**
355      * Makes the ringer icon visible with an icon that is chosen
356      * based on the current ringer mode.
357      */
setRingerIcon()358     private void setRingerIcon() {
359         mSmallStreamIcon.setVisibility(View.GONE);
360         mLargeStreamIcon.setVisibility(View.VISIBLE);
361 
362         int ringerMode = mAudioService.getRingerMode();
363         int icon;
364 
365         if (LOGD) Log.d(TAG, "setRingerIcon(), ringerMode: " + ringerMode);
366 
367         if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
368             icon = com.android.internal.R.drawable.ic_volume_off;
369         } else if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) {
370             icon = com.android.internal.R.drawable.ic_vibrate;
371         } else {
372             icon = com.android.internal.R.drawable.ic_volume;
373         }
374         mLargeStreamIcon.setImageResource(icon);
375     }
376 
onFreeResources()377     protected void onFreeResources() {
378         // We'll keep the views, just ditch the cached drawable and hence
379         // bitmaps
380         mSmallStreamIcon.setImageDrawable(null);
381         mLargeStreamIcon.setImageDrawable(null);
382 
383         synchronized (this) {
384             for (int i = mToneGenerators.length - 1; i >= 0; i--) {
385                 if (mToneGenerators[i] != null) {
386                     mToneGenerators[i].release();
387                 }
388                 mToneGenerators[i] = null;
389             }
390         }
391     }
392 
393     @Override
handleMessage(Message msg)394     public void handleMessage(Message msg) {
395         switch (msg.what) {
396 
397             case MSG_VOLUME_CHANGED: {
398                 onVolumeChanged(msg.arg1, msg.arg2);
399                 break;
400             }
401 
402             case MSG_FREE_RESOURCES: {
403                 onFreeResources();
404                 break;
405             }
406 
407             case MSG_STOP_SOUNDS: {
408                 onStopSounds();
409                 break;
410             }
411 
412             case MSG_PLAY_SOUND: {
413                 onPlaySound(msg.arg1, msg.arg2);
414                 break;
415             }
416 
417             case MSG_VIBRATE: {
418                 onVibrate();
419                 break;
420             }
421 
422         }
423     }
424 
425 }
426