blob: e21824e0647daf41c0663980f171888f69dd527a [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.view;
18
19import android.bluetooth.HeadsetBase;
20import android.content.Context;
21import android.content.Intent;
22import android.content.res.Resources;
23import android.media.AudioManager;
24import android.media.AudioService;
25import android.media.AudioSystem;
Marco Nelissen69f593c2009-07-28 09:55:04 -070026import android.media.RingtoneManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.media.ToneGenerator;
Marco Nelissen69f593c2009-07-28 09:55:04 -070028import android.net.Uri;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.os.Handler;
30import android.os.Message;
31import android.os.Vibrator;
32import android.util.Config;
33import android.util.Log;
34import android.widget.ImageView;
35import android.widget.ProgressBar;
36import android.widget.TextView;
37import 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 */
46public class VolumePanel extends Handler
47{
48 private static final String TAG = "VolumePanel";
Marco Nelissen69f593c2009-07-28 09:55:04 -070049 private static boolean LOGD = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050
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;
Marco Nelissen69f593c2009-07-28 09:55:04 -070091 private boolean mRingIsSilent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080092
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
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
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 */
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
Marco Nelissen69f593c2009-07-28 09:55:04 -0700144 if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800145 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
159 protected void onShowVolumeChanged(int streamType, int flags) {
160 int index = mAudioService.getStreamVolume(streamType);
161 int message = UNKNOWN_VOLUME_TEXT;
162 int additionalMessage = 0;
Marco Nelissen69f593c2009-07-28 09:55:04 -0700163 mRingIsSilent = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800164
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: {
Marco Nelissen69f593c2009-07-28 09:55:04 -0700176 setRingerIcon();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800177 message = RINGTONE_VOLUME_TEXT;
Marco Nelissen69f593c2009-07-28 09:55:04 -0700178 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 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800185 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);
Marco Nelissen69f593c2009-07-28 09:55:04 -0700222 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 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800229 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 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800275 }
276
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
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
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 */
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 */
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 */
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.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800357 */
Marco Nelissen69f593c2009-07-28 09:55:04 -0700358 private void setRingerIcon() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800359 mSmallStreamIcon.setVisibility(View.GONE);
360 mLargeStreamIcon.setVisibility(View.VISIBLE);
361
362 int ringerMode = mAudioService.getRingerMode();
363 int icon;
364
Marco Nelissen69f593c2009-07-28 09:55:04 -0700365 if (LOGD) Log.d(TAG, "setRingerIcon(), ringerMode: " + ringerMode);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800366
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
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
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}