blob: 8eae6296718735ab8e59221c120b940217a43c6c [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
Amith Yamasani2bbdd772011-02-02 18:54:13 -080019import com.android.internal.R;
20
Eric Laurentc34dcc12012-09-10 13:51:52 -070021import android.app.AlertDialog;
Amith Yamasani2bbdd772011-02-02 18:54:13 -080022import android.app.Dialog;
23import android.content.DialogInterface.OnDismissListener;
24import android.content.BroadcastReceiver;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import android.content.Context;
Amith Yamasani2bbdd772011-02-02 18:54:13 -080026import android.content.DialogInterface;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.content.Intent;
Amith Yamasani2bbdd772011-02-02 18:54:13 -080028import android.content.IntentFilter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.content.res.Resources;
Alan Viverette494fb7b2014-04-10 18:12:56 -070030import android.content.res.TypedArray;
31import android.graphics.drawable.Drawable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import android.media.AudioManager;
33import android.media.AudioService;
34import android.media.AudioSystem;
Marco Nelissen69f593c2009-07-28 09:55:04 -070035import android.media.RingtoneManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036import android.media.ToneGenerator;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -070037import android.media.VolumeController;
Marco Nelissen69f593c2009-07-28 09:55:04 -070038import android.net.Uri;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import android.os.Handler;
40import android.os.Message;
41import android.os.Vibrator;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042import android.util.Log;
Amith Yamasani284e6302011-09-16 18:24:47 -070043import android.view.WindowManager.LayoutParams;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044import android.widget.ImageView;
Amith Yamasani2bbdd772011-02-02 18:54:13 -080045import android.widget.SeekBar;
Amith Yamasani2bbdd772011-02-02 18:54:13 -080046import android.widget.SeekBar.OnSeekBarChangeListener;
47
48import java.util.HashMap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049
50/**
51 * Handle the volume up and down keys.
52 *
53 * This code really should be moved elsewhere.
54 *
Dianne Hackborne8ecde12011-08-03 18:55:19 -070055 * Seriously, it really really should be moved elsewhere. This is used by
56 * android.media.AudioService, which actually runs in the system process, to
57 * show the volume dialog when the user changes the volume. What a mess.
58 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080059 * @hide
60 */
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -070061public class VolumePanel extends Handler implements OnSeekBarChangeListener, View.OnClickListener,
62 VolumeController
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080063{
64 private static final String TAG = "VolumePanel";
Marco Nelissen69f593c2009-07-28 09:55:04 -070065 private static boolean LOGD = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080066
67 /**
68 * The delay before playing a sound. This small period exists so the user
69 * can press another key (non-volume keys, too) to have it NOT be audible.
70 * <p>
71 * PhoneWindow will implement this part.
72 */
73 public static final int PLAY_SOUND_DELAY = 300;
74
75 /**
76 * The delay before vibrating. This small period exists so if the user is
77 * moving to silent mode, it will not emit a short vibrate (it normally
78 * would since vibrate is between normal mode and silent mode using hardware
79 * keys).
80 */
81 public static final int VIBRATE_DELAY = 300;
82
83 private static final int VIBRATE_DURATION = 300;
84 private static final int BEEP_DURATION = 150;
85 private static final int MAX_VOLUME = 100;
86 private static final int FREE_DELAY = 10000;
Amith Yamasani2bbdd772011-02-02 18:54:13 -080087 private static final int TIMEOUT_DELAY = 3000;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088
89 private static final int MSG_VOLUME_CHANGED = 0;
90 private static final int MSG_FREE_RESOURCES = 1;
91 private static final int MSG_PLAY_SOUND = 2;
92 private static final int MSG_STOP_SOUNDS = 3;
93 private static final int MSG_VIBRATE = 4;
Amith Yamasani2bbdd772011-02-02 18:54:13 -080094 private static final int MSG_TIMEOUT = 5;
95 private static final int MSG_RINGER_MODE_CHANGED = 6;
Mike Lockwoodce952c82011-11-14 10:47:42 -080096 private static final int MSG_MUTE_CHANGED = 7;
Jean-Michel Trivi3114ce32012-06-11 15:03:52 -070097 private static final int MSG_REMOTE_VOLUME_CHANGED = 8;
98 private static final int MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN = 9;
99 private static final int MSG_SLIDER_VISIBILITY_CHANGED = 10;
Eric Laurentc34dcc12012-09-10 13:51:52 -0700100 private static final int MSG_DISPLAY_SAFE_VOLUME_WARNING = 11;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101
Mike Lockwood8dc1dab2011-10-27 09:52:41 -0400102 // Pseudo stream type for master volume
Mike Lockwood47676902011-11-08 10:31:21 -0800103 private static final int STREAM_MASTER = -100;
Jean-Michel Trivi3114ce32012-06-11 15:03:52 -0700104 // Pseudo stream type for remote volume is defined in AudioService.STREAM_REMOTE_MUSIC
Mike Lockwood8dc1dab2011-10-27 09:52:41 -0400105
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800106 protected Context mContext;
107 private AudioManager mAudioManager;
108 protected AudioService mAudioService;
Marco Nelissen69f593c2009-07-28 09:55:04 -0700109 private boolean mRingIsSilent;
Amith Yamasani42722bf2011-07-22 10:34:27 -0700110 private boolean mShowCombinedVolumes;
Amith Yamasani71def772011-10-12 12:25:24 -0700111 private boolean mVoiceCapable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112
Christopher Tatec4b78d22012-05-22 13:57:58 -0700113 // True if we want to play tones on the system stream when the master stream is specified.
114 private final boolean mPlayMasterStreamTones;
115
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800116 /** Dialog containing all the sliders */
117 private final Dialog mDialog;
118 /** Dialog's content view */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800119 private final View mView;
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800120
Amith Yamasanibaf6dbf2011-08-18 17:40:29 -0700121 /** The visible portion of the volume overlay */
122 private final ViewGroup mPanel;
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800123 /** Contains the sliders and their touchable icons */
124 private final ViewGroup mSliderGroup;
125 /** The button that expands the dialog to show all sliders */
126 private final View mMoreButton;
127 /** Dummy divider icon that needs to vanish with the more button */
128 private final View mDivider;
129
130 /** Currently active stream that shows up at the top of the list of sliders */
131 private int mActiveStreamType = -1;
132 /** All the slider controls mapped by stream type */
133 private HashMap<Integer,StreamControl> mStreamControls;
134
Amith Yamasani71def772011-10-12 12:25:24 -0700135 private enum StreamResources {
136 BluetoothSCOStream(AudioManager.STREAM_BLUETOOTH_SCO,
137 R.string.volume_icon_description_bluetooth,
138 R.drawable.ic_audio_bt,
139 R.drawable.ic_audio_bt,
140 false),
141 RingerStream(AudioManager.STREAM_RING,
142 R.string.volume_icon_description_ringer,
143 R.drawable.ic_audio_ring_notif,
144 R.drawable.ic_audio_ring_notif_mute,
145 false),
146 VoiceStream(AudioManager.STREAM_VOICE_CALL,
147 R.string.volume_icon_description_incall,
148 R.drawable.ic_audio_phone,
149 R.drawable.ic_audio_phone,
150 false),
Amith Yamasani92e1b2d2011-10-14 17:24:47 -0700151 AlarmStream(AudioManager.STREAM_ALARM,
152 R.string.volume_alarm,
153 R.drawable.ic_audio_alarm,
154 R.drawable.ic_audio_alarm_mute,
155 false),
Amith Yamasani71def772011-10-12 12:25:24 -0700156 MediaStream(AudioManager.STREAM_MUSIC,
157 R.string.volume_icon_description_media,
158 R.drawable.ic_audio_vol,
159 R.drawable.ic_audio_vol_mute,
160 true),
161 NotificationStream(AudioManager.STREAM_NOTIFICATION,
162 R.string.volume_icon_description_notification,
163 R.drawable.ic_audio_notification,
164 R.drawable.ic_audio_notification_mute,
Mike Lockwood8dc1dab2011-10-27 09:52:41 -0400165 true),
166 // for now, use media resources for master volume
167 MasterStream(STREAM_MASTER,
Jean-Michel Trivi3114ce32012-06-11 15:03:52 -0700168 R.string.volume_icon_description_media, //FIXME should have its own description
Mike Lockwood8dc1dab2011-10-27 09:52:41 -0400169 R.drawable.ic_audio_vol,
170 R.drawable.ic_audio_vol_mute,
Jean-Michel Trivi3114ce32012-06-11 15:03:52 -0700171 false),
172 RemoteStream(AudioService.STREAM_REMOTE_MUSIC,
173 R.string.volume_icon_description_media, //FIXME should have its own description
174 R.drawable.ic_media_route_on_holo_dark,
175 R.drawable.ic_media_route_disabled_holo_dark,
176 false);// will be dynamically updated
Amith Yamasani71def772011-10-12 12:25:24 -0700177
178 int streamType;
179 int descRes;
180 int iconRes;
181 int iconMuteRes;
182 // RING, VOICE_CALL & BLUETOOTH_SCO are hidden unless explicitly requested
183 boolean show;
184
185 StreamResources(int streamType, int descRes, int iconRes, int iconMuteRes, boolean show) {
186 this.streamType = streamType;
187 this.descRes = descRes;
188 this.iconRes = iconRes;
189 this.iconMuteRes = iconMuteRes;
190 this.show = show;
191 }
192 };
193
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800194 // List of stream types and their order
Amith Yamasani71def772011-10-12 12:25:24 -0700195 private static final StreamResources[] STREAMS = {
196 StreamResources.BluetoothSCOStream,
197 StreamResources.RingerStream,
198 StreamResources.VoiceStream,
199 StreamResources.MediaStream,
Amith Yamasani92e1b2d2011-10-14 17:24:47 -0700200 StreamResources.NotificationStream,
Mike Lockwood8dc1dab2011-10-27 09:52:41 -0400201 StreamResources.AlarmStream,
Jean-Michel Trivi3114ce32012-06-11 15:03:52 -0700202 StreamResources.MasterStream,
203 StreamResources.RemoteStream
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800204 };
205
206 /** Object that contains data for each slider */
207 private class StreamControl {
208 int streamType;
209 ViewGroup group;
210 ImageView icon;
211 SeekBar seekbarView;
212 int iconRes;
213 int iconMuteRes;
214 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800215
216 // Synchronize when accessing this
217 private ToneGenerator mToneGenerators[];
218 private Vibrator mVibrator;
219
Eric Laurentc34dcc12012-09-10 13:51:52 -0700220 private static AlertDialog sConfirmSafeVolumeDialog;
Eric Laurent0516a9e2012-09-19 11:53:03 -0700221 private static Object sConfirmSafeVolumeLock = new Object();
Eric Laurentc34dcc12012-09-10 13:51:52 -0700222
223 private static class WarningDialogReceiver extends BroadcastReceiver
224 implements DialogInterface.OnDismissListener {
Eric Laurentfde16d52012-12-03 14:42:39 -0800225 private final Context mContext;
226 private final Dialog mDialog;
227 private final VolumePanel mVolumePanel;
Eric Laurentc34dcc12012-09-10 13:51:52 -0700228
Eric Laurentfde16d52012-12-03 14:42:39 -0800229 WarningDialogReceiver(Context context, Dialog dialog, VolumePanel volumePanel) {
Eric Laurentc34dcc12012-09-10 13:51:52 -0700230 mContext = context;
231 mDialog = dialog;
Eric Laurentfde16d52012-12-03 14:42:39 -0800232 mVolumePanel = volumePanel;
Eric Laurentc34dcc12012-09-10 13:51:52 -0700233 IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
234 context.registerReceiver(this, filter);
235 }
236
237 @Override
238 public void onReceive(Context context, Intent intent) {
239 mDialog.cancel();
Eric Laurentfde16d52012-12-03 14:42:39 -0800240 cleanUp();
Eric Laurentc34dcc12012-09-10 13:51:52 -0700241 }
242
Alan Viverette494fb7b2014-04-10 18:12:56 -0700243 @Override
Eric Laurentc34dcc12012-09-10 13:51:52 -0700244 public void onDismiss(DialogInterface unused) {
245 mContext.unregisterReceiver(this);
Eric Laurentfde16d52012-12-03 14:42:39 -0800246 cleanUp();
247 }
248
249 private void cleanUp() {
Eric Laurent0516a9e2012-09-19 11:53:03 -0700250 synchronized (sConfirmSafeVolumeLock) {
251 sConfirmSafeVolumeDialog = null;
252 }
Eric Laurentfde16d52012-12-03 14:42:39 -0800253 mVolumePanel.forceTimeout();
254 mVolumePanel.updateStates();
Eric Laurentc34dcc12012-09-10 13:51:52 -0700255 }
256 }
257
258
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800259 public VolumePanel(final Context context, AudioService volumeService) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800260 mContext = context;
261 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
262 mAudioService = volumeService;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800263
Mike Lockwood8dc1dab2011-10-27 09:52:41 -0400264 // For now, only show master volume if master volume is supported
Alan Viverette494fb7b2014-04-10 18:12:56 -0700265 final boolean useMasterVolume = context.getResources().getBoolean(
266 R.bool.config_useMasterVolume);
Mike Lockwood8dc1dab2011-10-27 09:52:41 -0400267 if (useMasterVolume) {
268 for (int i = 0; i < STREAMS.length; i++) {
269 StreamResources streamRes = STREAMS[i];
270 streamRes.show = (streamRes.streamType == STREAM_MASTER);
271 }
272 }
273
Alan Viverette494fb7b2014-04-10 18:12:56 -0700274 final TypedArray a = context.obtainStyledAttributes(null,
275 com.android.internal.R.styleable.AlertDialog,
276 com.android.internal.R.attr.alertDialogStyle, 0);
277 final Drawable background = a.getDrawable(R.styleable.AlertDialog_fullBright);
278 a.recycle();
279
280 final LayoutInflater inflater = (LayoutInflater) context.getSystemService(
281 Context.LAYOUT_INFLATER_SERVICE);
282 mView = inflater.inflate(R.layout.volume_adjust, null);
283 mView.setBackground(background);
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800284 mView.setOnTouchListener(new View.OnTouchListener() {
Alan Viverette494fb7b2014-04-10 18:12:56 -0700285 @Override
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800286 public boolean onTouch(View v, MotionEvent event) {
287 resetTimeout();
Amith Yamasani284e6302011-09-16 18:24:47 -0700288 return false;
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800289 }
290 });
Amith Yamasanibaf6dbf2011-08-18 17:40:29 -0700291 mPanel = (ViewGroup) mView.findViewById(R.id.visible_panel);
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800292 mSliderGroup = (ViewGroup) mView.findViewById(R.id.slider_group);
Alan Viverette494fb7b2014-04-10 18:12:56 -0700293 mMoreButton = mView.findViewById(R.id.expand_button);
294 mDivider = mView.findViewById(R.id.expand_button_divider);
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800295
Amith Yamasani284e6302011-09-16 18:24:47 -0700296 mDialog = new Dialog(context, R.style.Theme_Panel_Volume) {
Alan Viverette494fb7b2014-04-10 18:12:56 -0700297 @Override
Amith Yamasani284e6302011-09-16 18:24:47 -0700298 public boolean onTouchEvent(MotionEvent event) {
Eric Laurentfde16d52012-12-03 14:42:39 -0800299 if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE &&
300 sConfirmSafeVolumeDialog == null) {
Amith Yamasani284e6302011-09-16 18:24:47 -0700301 forceTimeout();
302 return true;
303 }
304 return false;
305 }
306 };
Alan Viverette494fb7b2014-04-10 18:12:56 -0700307
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800308 mDialog.setTitle("Volume control"); // No need to localize
309 mDialog.setContentView(mView);
310 mDialog.setOnDismissListener(new OnDismissListener() {
Alan Viverette494fb7b2014-04-10 18:12:56 -0700311 @Override
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800312 public void onDismiss(DialogInterface dialog) {
313 mActiveStreamType = -1;
Eric Laurent402f7f22011-02-04 12:30:32 -0800314 mAudioManager.forceVolumeControlStream(mActiveStreamType);
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800315 }
316 });
Alan Viverette494fb7b2014-04-10 18:12:56 -0700317
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800318 // Change some window properties
Alan Viverette494fb7b2014-04-10 18:12:56 -0700319 final Window window = mDialog.getWindow();
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800320 window.setGravity(Gravity.TOP);
Alan Viverette494fb7b2014-04-10 18:12:56 -0700321
322 final LayoutParams lp = window.getAttributes();
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800323 lp.token = null;
Amith Yamasani284e6302011-09-16 18:24:47 -0700324 // Offset from the top
Alan Viverette494fb7b2014-04-10 18:12:56 -0700325 lp.y = mContext.getResources().getDimensionPixelOffset(R.dimen.volume_panel_top);
Amith Yamasani284e6302011-09-16 18:24:47 -0700326 lp.type = LayoutParams.TYPE_VOLUME_OVERLAY;
327 lp.width = LayoutParams.WRAP_CONTENT;
328 lp.height = LayoutParams.WRAP_CONTENT;
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800329 window.setAttributes(lp);
Amith Yamasani284e6302011-09-16 18:24:47 -0700330 window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCH_MODAL
331 | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800332
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800333 mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()];
Jeff Brownc2346132012-04-13 01:55:38 -0700334 mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800335
Amith Yamasani71def772011-10-12 12:25:24 -0700336 mVoiceCapable = context.getResources().getBoolean(R.bool.config_voice_capable);
Mike Lockwood8dc1dab2011-10-27 09:52:41 -0400337 mShowCombinedVolumes = !mVoiceCapable && !useMasterVolume;
Alan Viverette494fb7b2014-04-10 18:12:56 -0700338
Amith Yamasani42722bf2011-07-22 10:34:27 -0700339 // If we don't want to show multiple volumes, hide the settings button and divider
340 if (!mShowCombinedVolumes) {
341 mMoreButton.setVisibility(View.GONE);
342 mDivider.setVisibility(View.GONE);
343 } else {
344 mMoreButton.setOnClickListener(this);
345 }
346
Alan Viverette494fb7b2014-04-10 18:12:56 -0700347 final boolean masterVolumeOnly = context.getResources().getBoolean(
348 R.bool.config_useMasterVolume);
349 final boolean masterVolumeKeySounds = mContext.getResources().getBoolean(
350 R.bool.config_useVolumeKeySounds);
Christopher Tatec4b78d22012-05-22 13:57:58 -0700351
352 mPlayMasterStreamTones = masterVolumeOnly && masterVolumeKeySounds;
353
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800354 listenToRingerMode();
355 }
356
Fabrice Di Meglio8c028842013-01-09 18:20:38 -0800357 public void setLayoutDirection(int layoutDirection) {
358 mPanel.setLayoutDirection(layoutDirection);
359 updateStates();
360 }
361
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800362 private void listenToRingerMode() {
363 final IntentFilter filter = new IntentFilter();
364 filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
365 mContext.registerReceiver(new BroadcastReceiver() {
Alan Viverette494fb7b2014-04-10 18:12:56 -0700366 @Override
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800367 public void onReceive(Context context, Intent intent) {
368 final String action = intent.getAction();
369
370 if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) {
371 removeMessages(MSG_RINGER_MODE_CHANGED);
372 sendMessage(obtainMessage(MSG_RINGER_MODE_CHANGED));
373 }
374 }
375 }, filter);
376 }
377
378 private boolean isMuted(int streamType) {
Mike Lockwood8dc1dab2011-10-27 09:52:41 -0400379 if (streamType == STREAM_MASTER) {
Eric Laurent8c787522012-05-14 14:09:43 -0700380 return mAudioManager.isMasterMute();
Jean-Michel Trivi3114ce32012-06-11 15:03:52 -0700381 } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
382 return (mAudioService.getRemoteStreamVolume() <= 0);
Mike Lockwood8dc1dab2011-10-27 09:52:41 -0400383 } else {
Eric Laurent8c787522012-05-14 14:09:43 -0700384 return mAudioManager.isStreamMute(streamType);
Mike Lockwood8dc1dab2011-10-27 09:52:41 -0400385 }
386 }
387
388 private int getStreamMaxVolume(int streamType) {
Mike Lockwood8dc1dab2011-10-27 09:52:41 -0400389 if (streamType == STREAM_MASTER) {
Eric Laurent8c787522012-05-14 14:09:43 -0700390 return mAudioManager.getMasterMaxVolume();
Jean-Michel Trivi3114ce32012-06-11 15:03:52 -0700391 } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
392 return mAudioService.getRemoteStreamMaxVolume();
Mike Lockwood8dc1dab2011-10-27 09:52:41 -0400393 } else {
Eric Laurent8c787522012-05-14 14:09:43 -0700394 return mAudioManager.getStreamMaxVolume(streamType);
Mike Lockwood8dc1dab2011-10-27 09:52:41 -0400395 }
396 }
397
398 private int getStreamVolume(int streamType) {
Mike Lockwood8dc1dab2011-10-27 09:52:41 -0400399 if (streamType == STREAM_MASTER) {
Eric Laurent8c787522012-05-14 14:09:43 -0700400 return mAudioManager.getMasterVolume();
Jean-Michel Trivi3114ce32012-06-11 15:03:52 -0700401 } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
402 return mAudioService.getRemoteStreamVolume();
Mike Lockwood8dc1dab2011-10-27 09:52:41 -0400403 } else {
Eric Laurent8c787522012-05-14 14:09:43 -0700404 return mAudioManager.getStreamVolume(streamType);
Mike Lockwood8dc1dab2011-10-27 09:52:41 -0400405 }
406 }
407
408 private void setStreamVolume(int streamType, int index, int flags) {
Mike Lockwood8dc1dab2011-10-27 09:52:41 -0400409 if (streamType == STREAM_MASTER) {
Eric Laurent8c787522012-05-14 14:09:43 -0700410 mAudioManager.setMasterVolume(index, flags);
Jean-Michel Trivi3114ce32012-06-11 15:03:52 -0700411 } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
412 mAudioService.setRemoteStreamVolume(index);
Mike Lockwood8dc1dab2011-10-27 09:52:41 -0400413 } else {
Eric Laurent8c787522012-05-14 14:09:43 -0700414 mAudioManager.setStreamVolume(streamType, index, flags);
Mike Lockwood8dc1dab2011-10-27 09:52:41 -0400415 }
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800416 }
417
418 private void createSliders() {
Alan Viverette494fb7b2014-04-10 18:12:56 -0700419 final Resources res = mContext.getResources();
420 final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
421 Context.LAYOUT_INFLATER_SERVICE);
422
Amith Yamasani71def772011-10-12 12:25:24 -0700423 mStreamControls = new HashMap<Integer, StreamControl>(STREAMS.length);
Alan Viverette494fb7b2014-04-10 18:12:56 -0700424
Amith Yamasani71def772011-10-12 12:25:24 -0700425 for (int i = 0; i < STREAMS.length; i++) {
426 StreamResources streamRes = STREAMS[i];
Alan Viverette494fb7b2014-04-10 18:12:56 -0700427
428 final int streamType = streamRes.streamType;
Amith Yamasani71def772011-10-12 12:25:24 -0700429 if (mVoiceCapable && streamRes == StreamResources.NotificationStream) {
430 streamRes = StreamResources.RingerStream;
431 }
Alan Viverette494fb7b2014-04-10 18:12:56 -0700432
433 final StreamControl sc = new StreamControl();
Amith Yamasanid47a3aee2011-08-23 11:11:35 -0700434 sc.streamType = streamType;
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800435 sc.group = (ViewGroup) inflater.inflate(R.layout.volume_adjust_item, null);
436 sc.group.setTag(sc);
437 sc.icon = (ImageView) sc.group.findViewById(R.id.stream_icon);
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800438 sc.icon.setTag(sc);
Amith Yamasani71def772011-10-12 12:25:24 -0700439 sc.icon.setContentDescription(res.getString(streamRes.descRes));
440 sc.iconRes = streamRes.iconRes;
441 sc.iconMuteRes = streamRes.iconMuteRes;
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800442 sc.icon.setImageResource(sc.iconRes);
443 sc.seekbarView = (SeekBar) sc.group.findViewById(R.id.seekbar);
Alan Viverette494fb7b2014-04-10 18:12:56 -0700444 final int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO ||
Amith Yamasanid47a3aee2011-08-23 11:11:35 -0700445 streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0;
Mike Lockwood8dc1dab2011-10-27 09:52:41 -0400446 sc.seekbarView.setMax(getStreamMaxVolume(streamType) + plusOne);
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800447 sc.seekbarView.setOnSeekBarChangeListener(this);
448 sc.seekbarView.setTag(sc);
Amith Yamasanid47a3aee2011-08-23 11:11:35 -0700449 mStreamControls.put(streamType, sc);
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800450 }
451 }
452
453 private void reorderSliders(int activeStreamType) {
454 mSliderGroup.removeAllViews();
455
Alan Viverette494fb7b2014-04-10 18:12:56 -0700456 final StreamControl active = mStreamControls.get(activeStreamType);
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800457 if (active == null) {
458 Log.e("VolumePanel", "Missing stream type! - " + activeStreamType);
459 mActiveStreamType = -1;
460 } else {
461 mSliderGroup.addView(active.group);
462 mActiveStreamType = activeStreamType;
463 active.group.setVisibility(View.VISIBLE);
464 updateSlider(active);
465 }
466
Amith Yamasani42722bf2011-07-22 10:34:27 -0700467 addOtherVolumes();
468 }
469
470 private void addOtherVolumes() {
471 if (!mShowCombinedVolumes) return;
472
Amith Yamasani71def772011-10-12 12:25:24 -0700473 for (int i = 0; i < STREAMS.length; i++) {
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800474 // Skip the phone specific ones and the active one
Amith Yamasani71def772011-10-12 12:25:24 -0700475 final int streamType = STREAMS[i].streamType;
476 if (!STREAMS[i].show || streamType == mActiveStreamType) {
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800477 continue;
478 }
479 StreamControl sc = mStreamControls.get(streamType);
480 mSliderGroup.addView(sc.group);
481 updateSlider(sc);
482 }
483 }
484
485 /** Update the mute and progress state of a slider */
486 private void updateSlider(StreamControl sc) {
Eric Laurent8c787522012-05-14 14:09:43 -0700487 sc.seekbarView.setProgress(getStreamVolume(sc.streamType));
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800488 final boolean muted = isMuted(sc.streamType);
Fabrice Di Meglio8c028842013-01-09 18:20:38 -0800489 // Force reloading the image resource
490 sc.icon.setImageDrawable(null);
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800491 sc.icon.setImageResource(muted ? sc.iconMuteRes : sc.iconRes);
Eric Laurent8a51aca2013-03-25 18:36:35 -0700492 if (((sc.streamType == AudioManager.STREAM_RING) ||
493 (sc.streamType == AudioManager.STREAM_NOTIFICATION)) &&
Eric Laurent8c787522012-05-14 14:09:43 -0700494 mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
Amith Yamasanic696a532011-10-28 17:02:37 -0700495 sc.icon.setImageResource(R.drawable.ic_audio_ring_notif_vibrate);
496 }
Jean-Michel Trivi3114ce32012-06-11 15:03:52 -0700497 if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
498 // never disable touch interactions for remote playback, the muting is not tied to
499 // the state of the phone.
500 sc.seekbarView.setEnabled(true);
Eric Laurentfde16d52012-12-03 14:42:39 -0800501 } else if ((sc.streamType != mAudioManager.getMasterStreamType() && muted) ||
502 (sConfirmSafeVolumeDialog != null)) {
Eric Laurent8c787522012-05-14 14:09:43 -0700503 sc.seekbarView.setEnabled(false);
504 } else {
505 sc.seekbarView.setEnabled(true);
506 }
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800507 }
508
509 private boolean isExpanded() {
510 return mMoreButton.getVisibility() != View.VISIBLE;
511 }
512
513 private void expand() {
514 final int count = mSliderGroup.getChildCount();
515 for (int i = 0; i < count; i++) {
516 mSliderGroup.getChildAt(i).setVisibility(View.VISIBLE);
517 }
518 mMoreButton.setVisibility(View.INVISIBLE);
519 mDivider.setVisibility(View.INVISIBLE);
520 }
521
522 private void collapse() {
523 mMoreButton.setVisibility(View.VISIBLE);
524 mDivider.setVisibility(View.VISIBLE);
525 final int count = mSliderGroup.getChildCount();
526 for (int i = 1; i < count; i++) {
527 mSliderGroup.getChildAt(i).setVisibility(View.GONE);
528 }
529 }
530
Eric Laurentfde16d52012-12-03 14:42:39 -0800531 public void updateStates() {
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800532 final int count = mSliderGroup.getChildCount();
533 for (int i = 0; i < count; i++) {
534 StreamControl sc = (StreamControl) mSliderGroup.getChildAt(i).getTag();
535 updateSlider(sc);
536 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800537 }
538
539 public void postVolumeChanged(int streamType, int flags) {
540 if (hasMessages(MSG_VOLUME_CHANGED)) return;
Amith Yamasania6549862012-05-30 17:29:28 -0700541 synchronized (this) {
542 if (mStreamControls == null) {
543 createSliders();
544 }
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800545 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800546 removeMessages(MSG_FREE_RESOURCES);
547 obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget();
548 }
549
Jean-Michel Trivi3114ce32012-06-11 15:03:52 -0700550 public void postRemoteVolumeChanged(int streamType, int flags) {
551 if (hasMessages(MSG_REMOTE_VOLUME_CHANGED)) return;
552 synchronized (this) {
553 if (mStreamControls == null) {
554 createSliders();
555 }
556 }
557 removeMessages(MSG_FREE_RESOURCES);
558 obtainMessage(MSG_REMOTE_VOLUME_CHANGED, streamType, flags).sendToTarget();
559 }
560
561 public void postRemoteSliderVisibility(boolean visible) {
562 obtainMessage(MSG_SLIDER_VISIBILITY_CHANGED,
563 AudioService.STREAM_REMOTE_MUSIC, visible ? 1 : 0).sendToTarget();
564 }
565
566 /**
567 * Called by AudioService when it has received new remote playback information that
568 * would affect the VolumePanel display (mainly volumes). The difference with
569 * {@link #postRemoteVolumeChanged(int, int)} is that the handling of the posted message
570 * (MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN) will only update the volume slider if it is being
571 * displayed.
572 * This special code path is due to the fact that remote volume updates arrive to AudioService
573 * asynchronously. So after AudioService has sent the volume update (which should be treated
574 * as a request to update the volume), the application will likely set a new volume. If the UI
575 * is still up, we need to refresh the display to show this new value.
576 */
577 public void postHasNewRemotePlaybackInfo() {
578 if (hasMessages(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN)) return;
579 // don't create or prevent resources to be freed, if they disappear, this update came too
580 // late and shouldn't warrant the panel to be displayed longer
581 obtainMessage(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN).sendToTarget();
582 }
583
Mike Lockwood8dc1dab2011-10-27 09:52:41 -0400584 public void postMasterVolumeChanged(int flags) {
585 postVolumeChanged(STREAM_MASTER, flags);
586 }
587
Mike Lockwoodce952c82011-11-14 10:47:42 -0800588 public void postMuteChanged(int streamType, int flags) {
589 if (hasMessages(MSG_VOLUME_CHANGED)) return;
Amith Yamasania6549862012-05-30 17:29:28 -0700590 synchronized (this) {
591 if (mStreamControls == null) {
592 createSliders();
593 }
Mike Lockwoodce952c82011-11-14 10:47:42 -0800594 }
595 removeMessages(MSG_FREE_RESOURCES);
596 obtainMessage(MSG_MUTE_CHANGED, streamType, flags).sendToTarget();
597 }
598
599 public void postMasterMuteChanged(int flags) {
600 postMuteChanged(STREAM_MASTER, flags);
601 }
602
Eric Laurentfde16d52012-12-03 14:42:39 -0800603 public void postDisplaySafeVolumeWarning(int flags) {
Eric Laurent0516a9e2012-09-19 11:53:03 -0700604 if (hasMessages(MSG_DISPLAY_SAFE_VOLUME_WARNING)) return;
Eric Laurentfde16d52012-12-03 14:42:39 -0800605 obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, flags, 0).sendToTarget();
Eric Laurentc34dcc12012-09-10 13:51:52 -0700606 }
607
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800608 /**
609 * Override this if you have other work to do when the volume changes (for
610 * example, vibrating, playing a sound, etc.). Make sure to call through to
611 * the superclass implementation.
612 */
613 protected void onVolumeChanged(int streamType, int flags) {
614
615 if (LOGD) Log.d(TAG, "onVolumeChanged(streamType: " + streamType + ", flags: " + flags + ")");
616
617 if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
Amith Yamasania6549862012-05-30 17:29:28 -0700618 synchronized (this) {
619 if (mActiveStreamType != streamType) {
620 reorderSliders(streamType);
621 }
622 onShowVolumeChanged(streamType, flags);
Amith Yamasanie3361b82011-02-10 18:20:50 -0800623 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800624 }
625
Marco Nelissen69f593c2009-07-28 09:55:04 -0700626 if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800627 removeMessages(MSG_PLAY_SOUND);
628 sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY);
629 }
630
631 if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) {
632 removeMessages(MSG_PLAY_SOUND);
633 removeMessages(MSG_VIBRATE);
634 onStopSounds();
635 }
636
637 removeMessages(MSG_FREE_RESOURCES);
638 sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800639 resetTimeout();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800640 }
641
Mike Lockwoodce952c82011-11-14 10:47:42 -0800642 protected void onMuteChanged(int streamType, int flags) {
643
644 if (LOGD) Log.d(TAG, "onMuteChanged(streamType: " + streamType + ", flags: " + flags + ")");
645
646 StreamControl sc = mStreamControls.get(streamType);
647 if (sc != null) {
648 sc.icon.setImageResource(isMuted(sc.streamType) ? sc.iconMuteRes : sc.iconRes);
649 }
650
651 onVolumeChanged(streamType, flags);
652 }
653
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800654 protected void onShowVolumeChanged(int streamType, int flags) {
Eric Laurent8c787522012-05-14 14:09:43 -0700655 int index = getStreamVolume(streamType);
Eric Laurentd72d51c2011-02-03 18:47:47 -0800656
Marco Nelissen69f593c2009-07-28 09:55:04 -0700657 mRingIsSilent = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800658
659 if (LOGD) {
660 Log.d(TAG, "onShowVolumeChanged(streamType: " + streamType
661 + ", flags: " + flags + "), index: " + index);
662 }
663
664 // get max volume for progress bar
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800665
Mike Lockwood8dc1dab2011-10-27 09:52:41 -0400666 int max = getStreamMaxVolume(streamType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800667
668 switch (streamType) {
669
670 case AudioManager.STREAM_RING: {
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800671// setRingerIcon();
Marco Nelissen69f593c2009-07-28 09:55:04 -0700672 Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri(
673 mContext, RingtoneManager.TYPE_RINGTONE);
674 if (ringuri == null) {
Marco Nelissen69f593c2009-07-28 09:55:04 -0700675 mRingIsSilent = true;
676 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800677 break;
678 }
679
680 case AudioManager.STREAM_MUSIC: {
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800681 // Special case for when Bluetooth is active for music
Glenn Kasten8b4b97a2011-02-04 13:54:26 -0800682 if ((mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC) &
683 (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP |
684 AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
685 AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) {
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800686 setMusicIcon(R.drawable.ic_audio_bt, R.drawable.ic_audio_bt_mute);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800687 } else {
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800688 setMusicIcon(R.drawable.ic_audio_vol, R.drawable.ic_audio_vol_mute);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800689 }
690 break;
691 }
692
693 case AudioManager.STREAM_VOICE_CALL: {
694 /*
695 * For in-call voice call volume, there is no inaudible volume.
696 * Rescale the UI control so the progress bar doesn't go all
697 * the way to zero and don't show the mute icon.
698 */
699 index++;
700 max++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800701 break;
702 }
703
704 case AudioManager.STREAM_ALARM: {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800705 break;
706 }
707
708 case AudioManager.STREAM_NOTIFICATION: {
Marco Nelissen69f593c2009-07-28 09:55:04 -0700709 Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri(
710 mContext, RingtoneManager.TYPE_NOTIFICATION);
711 if (ringuri == null) {
Marco Nelissen69f593c2009-07-28 09:55:04 -0700712 mRingIsSilent = true;
713 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800714 break;
715 }
716
717 case AudioManager.STREAM_BLUETOOTH_SCO: {
718 /*
719 * For in-call voice call volume, there is no inaudible volume.
720 * Rescale the UI control so the progress bar doesn't go all
721 * the way to zero and don't show the mute icon.
722 */
723 index++;
724 max++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800725 break;
726 }
Jean-Michel Trivi3114ce32012-06-11 15:03:52 -0700727
728 case AudioService.STREAM_REMOTE_MUSIC: {
729 if (LOGD) { Log.d(TAG, "showing remote volume "+index+" over "+ max); }
730 break;
731 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800732 }
733
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800734 StreamControl sc = mStreamControls.get(streamType);
735 if (sc != null) {
Amith Yamasanid47a3aee2011-08-23 11:11:35 -0700736 if (sc.seekbarView.getMax() != max) {
737 sc.seekbarView.setMax(max);
738 }
Eric Laurent4bbcc652012-09-24 14:26:30 -0700739
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800740 sc.seekbarView.setProgress(index);
Eric Laurent4bbcc652012-09-24 14:26:30 -0700741 if (((flags & AudioManager.FLAG_FIXED_VOLUME) != 0) ||
742 (streamType != mAudioManager.getMasterStreamType() &&
743 streamType != AudioService.STREAM_REMOTE_MUSIC &&
Eric Laurentfde16d52012-12-03 14:42:39 -0800744 isMuted(streamType)) ||
745 sConfirmSafeVolumeDialog != null) {
Eric Laurent8c787522012-05-14 14:09:43 -0700746 sc.seekbarView.setEnabled(false);
747 } else {
748 sc.seekbarView.setEnabled(true);
749 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800750 }
751
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800752 if (!mDialog.isShowing()) {
Jean-Michel Trivi3114ce32012-06-11 15:03:52 -0700753 int stream = (streamType == AudioService.STREAM_REMOTE_MUSIC) ? -1 : streamType;
754 // when the stream is for remote playback, use -1 to reset the stream type evaluation
755 mAudioManager.forceVolumeControlStream(stream);
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800756 mDialog.setContentView(mView);
757 // Showing dialog - use collapsed state
Amith Yamasani42722bf2011-07-22 10:34:27 -0700758 if (mShowCombinedVolumes) {
759 collapse();
760 }
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800761 mDialog.show();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800762 }
763
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800764 // Do a little vibrate if applicable (only when going into vibrate mode)
Jean-Michel Trivi3114ce32012-06-11 15:03:52 -0700765 if ((streamType != AudioService.STREAM_REMOTE_MUSIC) &&
766 ((flags & AudioManager.FLAG_VIBRATE) != 0) &&
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800767 mAudioService.isStreamAffectedByRingerMode(streamType) &&
Eric Laurent8c787522012-05-14 14:09:43 -0700768 mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800769 sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY);
770 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800771 }
772
773 protected void onPlaySound(int streamType, int flags) {
774
775 if (hasMessages(MSG_STOP_SOUNDS)) {
776 removeMessages(MSG_STOP_SOUNDS);
777 // Force stop right now
778 onStopSounds();
779 }
780
781 synchronized (this) {
782 ToneGenerator toneGen = getOrCreateToneGenerator(streamType);
Eric Laurent733a42b2011-01-19 10:41:57 -0800783 if (toneGen != null) {
784 toneGen.startTone(ToneGenerator.TONE_PROP_BEEP);
785 sendMessageDelayed(obtainMessage(MSG_STOP_SOUNDS), BEEP_DURATION);
786 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800787 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800788 }
789
790 protected void onStopSounds() {
791
792 synchronized (this) {
793 int numStreamTypes = AudioSystem.getNumStreamTypes();
794 for (int i = numStreamTypes - 1; i >= 0; i--) {
795 ToneGenerator toneGen = mToneGenerators[i];
796 if (toneGen != null) {
797 toneGen.stopTone();
798 }
799 }
800 }
801 }
802
803 protected void onVibrate() {
804
805 // Make sure we ended up in vibrate ringer mode
Eric Laurent8c787522012-05-14 14:09:43 -0700806 if (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_VIBRATE) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800807 return;
808 }
809
John Spurlockf9e1a0b2014-03-19 22:09:06 -0400810 mVibrator.vibrate(VIBRATE_DURATION, AudioManager.STREAM_SYSTEM);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800811 }
812
Jean-Michel Trivi3114ce32012-06-11 15:03:52 -0700813 protected void onRemoteVolumeChanged(int streamType, int flags) {
814 // streamType is the real stream type being affected, but for the UI sliders, we
815 // refer to AudioService.STREAM_REMOTE_MUSIC. We still play the beeps on the real
816 // stream type.
817 if (LOGD) Log.d(TAG, "onRemoteVolumeChanged(stream:"+streamType+", flags: " + flags + ")");
818
819 if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || mDialog.isShowing()) {
820 synchronized (this) {
821 if (mActiveStreamType != AudioService.STREAM_REMOTE_MUSIC) {
822 reorderSliders(AudioService.STREAM_REMOTE_MUSIC);
823 }
824 onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, flags);
825 }
826 } else {
827 if (LOGD) Log.d(TAG, "not calling onShowVolumeChanged(), no FLAG_SHOW_UI or no UI");
828 }
829
830 if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) {
831 removeMessages(MSG_PLAY_SOUND);
832 sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY);
833 }
834
835 if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) {
836 removeMessages(MSG_PLAY_SOUND);
837 removeMessages(MSG_VIBRATE);
838 onStopSounds();
839 }
840
841 removeMessages(MSG_FREE_RESOURCES);
842 sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
Jean-Michel Trivi3114ce32012-06-11 15:03:52 -0700843 resetTimeout();
844 }
845
846 protected void onRemoteVolumeUpdateIfShown() {
847 if (LOGD) Log.d(TAG, "onRemoteVolumeUpdateIfShown()");
848 if (mDialog.isShowing()
849 && (mActiveStreamType == AudioService.STREAM_REMOTE_MUSIC)
850 && (mStreamControls != null)) {
851 onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, 0);
852 }
853 }
854
855
856 /**
857 * Handler for MSG_SLIDER_VISIBILITY_CHANGED
858 * Hide or show a slider
859 * @param streamType can be a valid stream type value, or VolumePanel.STREAM_MASTER,
860 * or AudioService.STREAM_REMOTE_MUSIC
861 * @param visible
862 */
863 synchronized protected void onSliderVisibilityChanged(int streamType, int visible) {
864 if (LOGD) Log.d(TAG, "onSliderVisibilityChanged(stream="+streamType+", visi="+visible+")");
865 boolean isVisible = (visible == 1);
866 for (int i = STREAMS.length - 1 ; i >= 0 ; i--) {
867 StreamResources streamRes = STREAMS[i];
868 if (streamRes.streamType == streamType) {
869 streamRes.show = isVisible;
870 if (!isVisible && (mActiveStreamType == streamType)) {
871 mActiveStreamType = -1;
872 }
873 break;
874 }
875 }
876 }
877
Eric Laurentfde16d52012-12-03 14:42:39 -0800878 protected void onDisplaySafeVolumeWarning(int flags) {
879 if ((flags & AudioManager.FLAG_SHOW_UI) != 0 || mDialog.isShowing()) {
880 synchronized (sConfirmSafeVolumeLock) {
881 if (sConfirmSafeVolumeDialog != null) {
882 return;
883 }
884 sConfirmSafeVolumeDialog = new AlertDialog.Builder(mContext)
885 .setMessage(com.android.internal.R.string.safe_media_volume_warning)
886 .setPositiveButton(com.android.internal.R.string.yes,
887 new DialogInterface.OnClickListener() {
888 public void onClick(DialogInterface dialog, int which) {
889 mAudioService.disableSafeMediaVolume();
890 }
891 })
892 .setNegativeButton(com.android.internal.R.string.no, null)
893 .setIconAttribute(android.R.attr.alertDialogIcon)
894 .create();
895 final WarningDialogReceiver warning = new WarningDialogReceiver(mContext,
896 sConfirmSafeVolumeDialog, this);
Eric Laurent0516a9e2012-09-19 11:53:03 -0700897
Eric Laurentfde16d52012-12-03 14:42:39 -0800898 sConfirmSafeVolumeDialog.setOnDismissListener(warning);
899 sConfirmSafeVolumeDialog.getWindow().setType(
900 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
901 sConfirmSafeVolumeDialog.show();
902 }
903 updateStates();
Eric Laurentc34dcc12012-09-10 13:51:52 -0700904 }
Eric Laurentfde16d52012-12-03 14:42:39 -0800905 resetTimeout();
Eric Laurentc34dcc12012-09-10 13:51:52 -0700906 }
907
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800908 /**
909 * Lock on this VolumePanel instance as long as you use the returned ToneGenerator.
910 */
911 private ToneGenerator getOrCreateToneGenerator(int streamType) {
Christopher Tatec4b78d22012-05-22 13:57:58 -0700912 if (streamType == STREAM_MASTER) {
913 // For devices that use the master volume setting only but still want to
914 // play a volume-changed tone, direct the master volume pseudostream to
915 // the system stream's tone generator.
916 if (mPlayMasterStreamTones) {
917 streamType = AudioManager.STREAM_SYSTEM;
918 } else {
919 return null;
920 }
921 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800922 synchronized (this) {
923 if (mToneGenerators[streamType] == null) {
Eric Laurent733a42b2011-01-19 10:41:57 -0800924 try {
925 mToneGenerators[streamType] = new ToneGenerator(streamType, MAX_VOLUME);
926 } catch (RuntimeException e) {
927 if (LOGD) {
928 Log.d(TAG, "ToneGenerator constructor failed with "
929 + "RuntimeException: " + e);
930 }
931 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800932 }
Eric Laurent733a42b2011-01-19 10:41:57 -0800933 return mToneGenerators[streamType];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800934 }
935 }
936
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800937
938 /**
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800939 * Switch between icons because Bluetooth music is same as music volume, but with
940 * different icons.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800941 */
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800942 private void setMusicIcon(int resId, int resMuteId) {
943 StreamControl sc = mStreamControls.get(AudioManager.STREAM_MUSIC);
944 if (sc != null) {
945 sc.iconRes = resId;
946 sc.iconMuteRes = resMuteId;
947 sc.icon.setImageResource(isMuted(sc.streamType) ? sc.iconMuteRes : sc.iconRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800948 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800949 }
950
951 protected void onFreeResources() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800952 synchronized (this) {
953 for (int i = mToneGenerators.length - 1; i >= 0; i--) {
954 if (mToneGenerators[i] != null) {
955 mToneGenerators[i].release();
956 }
957 mToneGenerators[i] = null;
958 }
959 }
960 }
961
962 @Override
963 public void handleMessage(Message msg) {
964 switch (msg.what) {
965
966 case MSG_VOLUME_CHANGED: {
967 onVolumeChanged(msg.arg1, msg.arg2);
968 break;
969 }
970
Mike Lockwoodce952c82011-11-14 10:47:42 -0800971 case MSG_MUTE_CHANGED: {
972 onMuteChanged(msg.arg1, msg.arg2);
973 break;
974 }
975
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800976 case MSG_FREE_RESOURCES: {
977 onFreeResources();
978 break;
979 }
980
981 case MSG_STOP_SOUNDS: {
982 onStopSounds();
983 break;
984 }
985
986 case MSG_PLAY_SOUND: {
987 onPlaySound(msg.arg1, msg.arg2);
988 break;
989 }
990
991 case MSG_VIBRATE: {
992 onVibrate();
993 break;
994 }
995
Amith Yamasani2bbdd772011-02-02 18:54:13 -0800996 case MSG_TIMEOUT: {
997 if (mDialog.isShowing()) {
998 mDialog.dismiss();
999 mActiveStreamType = -1;
1000 }
Eric Laurentfde16d52012-12-03 14:42:39 -08001001 synchronized (sConfirmSafeVolumeLock) {
1002 if (sConfirmSafeVolumeDialog != null) {
1003 sConfirmSafeVolumeDialog.dismiss();
1004 }
1005 }
Amith Yamasani2bbdd772011-02-02 18:54:13 -08001006 break;
1007 }
1008 case MSG_RINGER_MODE_CHANGED: {
1009 if (mDialog.isShowing()) {
1010 updateStates();
1011 }
1012 break;
1013 }
Jean-Michel Trivi3114ce32012-06-11 15:03:52 -07001014
1015 case MSG_REMOTE_VOLUME_CHANGED: {
1016 onRemoteVolumeChanged(msg.arg1, msg.arg2);
1017 break;
1018 }
1019
1020 case MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN:
1021 onRemoteVolumeUpdateIfShown();
1022 break;
1023
1024 case MSG_SLIDER_VISIBILITY_CHANGED:
1025 onSliderVisibilityChanged(msg.arg1, msg.arg2);
1026 break;
Eric Laurentc34dcc12012-09-10 13:51:52 -07001027
1028 case MSG_DISPLAY_SAFE_VOLUME_WARNING:
Eric Laurentfde16d52012-12-03 14:42:39 -08001029 onDisplaySafeVolumeWarning(msg.arg1);
Eric Laurentc34dcc12012-09-10 13:51:52 -07001030 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001031 }
1032 }
1033
Amith Yamasani2bbdd772011-02-02 18:54:13 -08001034 private void resetTimeout() {
1035 removeMessages(MSG_TIMEOUT);
1036 sendMessageDelayed(obtainMessage(MSG_TIMEOUT), TIMEOUT_DELAY);
1037 }
1038
Amith Yamasanibaf6dbf2011-08-18 17:40:29 -07001039 private void forceTimeout() {
1040 removeMessages(MSG_TIMEOUT);
1041 sendMessage(obtainMessage(MSG_TIMEOUT));
1042 }
1043
Amith Yamasani2bbdd772011-02-02 18:54:13 -08001044 public void onProgressChanged(SeekBar seekBar, int progress,
1045 boolean fromUser) {
1046 final Object tag = seekBar.getTag();
1047 if (fromUser && tag instanceof StreamControl) {
1048 StreamControl sc = (StreamControl) tag;
Mike Lockwood8dc1dab2011-10-27 09:52:41 -04001049 if (getStreamVolume(sc.streamType) != progress) {
1050 setStreamVolume(sc.streamType, progress, 0);
Amith Yamasani2bbdd772011-02-02 18:54:13 -08001051 }
1052 }
1053 resetTimeout();
1054 }
1055
1056 public void onStartTrackingTouch(SeekBar seekBar) {
1057 }
1058
1059 public void onStopTrackingTouch(SeekBar seekBar) {
Jean-Michel Trivi3114ce32012-06-11 15:03:52 -07001060 final Object tag = seekBar.getTag();
1061 if (tag instanceof StreamControl) {
1062 StreamControl sc = (StreamControl) tag;
1063 // because remote volume updates are asynchronous, AudioService might have received
1064 // a new remote volume value since the finger adjusted the slider. So when the
1065 // progress of the slider isn't being tracked anymore, adjust the slider to the last
1066 // "published" remote volume value, so the UI reflects the actual volume.
1067 if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
1068 seekBar.setProgress(getStreamVolume(AudioService.STREAM_REMOTE_MUSIC));
1069 }
1070 }
Amith Yamasani2bbdd772011-02-02 18:54:13 -08001071 }
1072
1073 public void onClick(View v) {
1074 if (v == mMoreButton) {
1075 expand();
Amith Yamasani2bbdd772011-02-02 18:54:13 -08001076 }
1077 resetTimeout();
1078 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001079}