/* * Copyright (C) 2007 The Android Open Source Project * Copyright (c) 2010-2011, The Linux Foundation. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view; import android.bluetooth.HeadsetBase; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.media.AudioManager; import android.media.AudioService; import android.media.AudioSystem; import android.media.RingtoneManager; import android.media.ToneGenerator; import android.net.Uri; import android.os.Handler; import android.os.Message; import android.os.Vibrator; import android.util.Config; import android.util.Log; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; /** * Handle the volume up and down keys. * * This code really should be moved elsewhere. * * @hide */ public class VolumePanel extends Handler { private static final String TAG = "VolumePanel"; private static boolean LOGD = false; /** * The delay before playing a sound. This small period exists so the user * can press another key (non-volume keys, too) to have it NOT be audible. *
* PhoneWindow will implement this part. */ public static final int PLAY_SOUND_DELAY = 300; /** * The delay before vibrating. This small period exists so if the user is * moving to silent mode, it will not emit a short vibrate (it normally * would since vibrate is between normal mode and silent mode using hardware * keys). */ public static final int VIBRATE_DELAY = 300; private static final int VIBRATE_DURATION = 300; private static final int BEEP_DURATION = 150; private static final int MAX_VOLUME = 100; private static final int FREE_DELAY = 10000; private static final int MSG_VOLUME_CHANGED = 0; private static final int MSG_FREE_RESOURCES = 1; private static final int MSG_PLAY_SOUND = 2; private static final int MSG_STOP_SOUNDS = 3; private static final int MSG_VIBRATE = 4; private static final int RINGTONE_VOLUME_TEXT = com.android.internal.R.string.volume_ringtone; private static final int MUSIC_VOLUME_TEXT = com.android.internal.R.string.volume_music; private static final int INCALL_VOLUME_TEXT = com.android.internal.R.string.volume_call; private static final int ALARM_VOLUME_TEXT = com.android.internal.R.string.volume_alarm; private static final int FM_VOLUME_TEXT = com.android.internal.R.string.volume_fm; private static final int UNKNOWN_VOLUME_TEXT = com.android.internal.R.string.volume_unknown; private static final int NOTIFICATION_VOLUME_TEXT = com.android.internal.R.string.volume_notification; private static final int BLUETOOTH_INCALL_VOLUME_TEXT = com.android.internal.R.string.volume_bluetooth_call; protected Context mContext; private AudioManager mAudioManager; protected AudioService mAudioService; private boolean mRingIsSilent; private final Toast mToast; private final View mView; private final TextView mMessage; private final TextView mAdditionalMessage; private final ImageView mSmallStreamIcon; private final ImageView mLargeStreamIcon; private final ProgressBar mLevel; // Synchronize when accessing this private ToneGenerator mToneGenerators[]; private Vibrator mVibrator; public VolumePanel(Context context, AudioService volumeService) { mContext = context; mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mAudioService = volumeService; mToast = new Toast(context); LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); View view = mView = inflater.inflate(com.android.internal.R.layout.volume_adjust, null); mMessage = (TextView) view.findViewById(com.android.internal.R.id.message); mAdditionalMessage = (TextView) view.findViewById(com.android.internal.R.id.additional_message); mSmallStreamIcon = (ImageView) view.findViewById(com.android.internal.R.id.other_stream_icon); mLargeStreamIcon = (ImageView) view.findViewById(com.android.internal.R.id.ringer_stream_icon); mLevel = (ProgressBar) view.findViewById(com.android.internal.R.id.level); mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()]; mVibrator = new Vibrator(); } public void postVolumeChanged(int streamType, int flags) { if (hasMessages(MSG_VOLUME_CHANGED)) return; removeMessages(MSG_FREE_RESOURCES); obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget(); } /** * Override this if you have other work to do when the volume changes (for * example, vibrating, playing a sound, etc.). Make sure to call through to * the superclass implementation. */ protected void onVolumeChanged(int streamType, int flags) { if (LOGD) Log.d(TAG, "onVolumeChanged(streamType: " + streamType + ", flags: " + flags + ")"); if ((flags & AudioManager.FLAG_SHOW_UI) != 0) { onShowVolumeChanged(streamType, flags); } if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) { removeMessages(MSG_PLAY_SOUND); sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY); } if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) { removeMessages(MSG_PLAY_SOUND); removeMessages(MSG_VIBRATE); onStopSounds(); } removeMessages(MSG_FREE_RESOURCES); sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY); } protected void onShowVolumeChanged(int streamType, int flags) { int index = mAudioService.getStreamVolume(streamType); int message = UNKNOWN_VOLUME_TEXT; int additionalMessage = 0; mRingIsSilent = false; if (LOGD) { Log.d(TAG, "onShowVolumeChanged(streamType: " + streamType + ", flags: " + flags + "), index: " + index); } // get max volume for progress bar int max = mAudioService.getStreamMaxVolume(streamType); switch (streamType) { case AudioManager.STREAM_RING: { setRingerIcon(); message = RINGTONE_VOLUME_TEXT; Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri( mContext, RingtoneManager.TYPE_RINGTONE); if (ringuri == null) { additionalMessage = com.android.internal.R.string.volume_music_hint_silent_ringtone_selected; mRingIsSilent = true; } break; } case AudioManager.STREAM_MUSIC: { message = MUSIC_VOLUME_TEXT; if (mAudioManager.isBluetoothA2dpOn()) { additionalMessage = com.android.internal.R.string.volume_music_hint_playing_through_bluetooth; setLargeIcon(com.android.internal.R.drawable.ic_volume_bluetooth_ad2p); } else { setSmallIcon(index); } break; } case AudioManager.STREAM_FM: { message = FM_VOLUME_TEXT; setSmallIcon(index); break; } case AudioManager.STREAM_VOICE_CALL: { /* * For in-call voice call volume, there is no inaudible volume. * Rescale the UI control so the progress bar doesn't go all * the way to zero and don't show the mute icon. */ index++; max++; message = INCALL_VOLUME_TEXT; setSmallIcon(index); break; } case AudioManager.STREAM_ALARM: { message = ALARM_VOLUME_TEXT; setSmallIcon(index); break; } case AudioManager.STREAM_NOTIFICATION: { message = NOTIFICATION_VOLUME_TEXT; setSmallIcon(index); Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri( mContext, RingtoneManager.TYPE_NOTIFICATION); if (ringuri == null) { additionalMessage = com.android.internal.R.string.volume_music_hint_silent_ringtone_selected; mRingIsSilent = true; } break; } case AudioManager.STREAM_BLUETOOTH_SCO: { /* * For in-call voice call volume, there is no inaudible volume. * Rescale the UI control so the progress bar doesn't go all * the way to zero and don't show the mute icon. */ index++; max++; message = BLUETOOTH_INCALL_VOLUME_TEXT; setLargeIcon(com.android.internal.R.drawable.ic_volume_bluetooth_in_call); break; } } String messageString = Resources.getSystem().getString(message); if (!mMessage.getText().equals(messageString)) { mMessage.setText(messageString); } if (additionalMessage == 0) { mAdditionalMessage.setVisibility(View.GONE); } else { mAdditionalMessage.setVisibility(View.VISIBLE); mAdditionalMessage.setText(Resources.getSystem().getString(additionalMessage)); } if (max != mLevel.getMax()) { mLevel.setMax(max); } mLevel.setProgress(index); mToast.setView(mView); mToast.setDuration(Toast.LENGTH_SHORT); mToast.setGravity(Gravity.TOP, 0, 0); mToast.show(); // Do a little vibrate if applicable (only when going into vibrate mode) if ((flags & AudioManager.FLAG_VIBRATE) != 0 && mAudioService.isStreamAffectedByRingerMode(streamType) && mAudioService.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE && mAudioService.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER)) { sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY); } } protected void onPlaySound(int streamType, int flags) { if (hasMessages(MSG_STOP_SOUNDS)) { removeMessages(MSG_STOP_SOUNDS); // Force stop right now onStopSounds(); } synchronized (this) { ToneGenerator toneGen = getOrCreateToneGenerator(streamType); toneGen.startTone(ToneGenerator.TONE_PROP_BEEP); } sendMessageDelayed(obtainMessage(MSG_STOP_SOUNDS), BEEP_DURATION); } protected void onStopSounds() { synchronized (this) { int numStreamTypes = AudioSystem.getNumStreamTypes(); for (int i = numStreamTypes - 1; i >= 0; i--) { ToneGenerator toneGen = mToneGenerators[i]; if (toneGen != null) { toneGen.stopTone(); } } } } protected void onVibrate() { // Make sure we ended up in vibrate ringer mode if (mAudioService.getRingerMode() != AudioManager.RINGER_MODE_VIBRATE) { return; } mVibrator.vibrate(VIBRATE_DURATION); } /** * Lock on this VolumePanel instance as long as you use the returned ToneGenerator. */ private ToneGenerator getOrCreateToneGenerator(int streamType) { synchronized (this) { if (mToneGenerators[streamType] == null) { return mToneGenerators[streamType] = new ToneGenerator(streamType, MAX_VOLUME); } else { return mToneGenerators[streamType]; } } } /** * Makes the small icon visible, and hides the large icon. * * @param index The volume index, where 0 means muted. */ private void setSmallIcon(int index) { mLargeStreamIcon.setVisibility(View.GONE); mSmallStreamIcon.setVisibility(View.VISIBLE); mSmallStreamIcon.setImageResource(index == 0 ? com.android.internal.R.drawable.ic_volume_off_small : com.android.internal.R.drawable.ic_volume_small); } /** * Makes the large image view visible with the given icon. * * @param resId The icon to display. */ private void setLargeIcon(int resId) { mSmallStreamIcon.setVisibility(View.GONE); mLargeStreamIcon.setVisibility(View.VISIBLE); mLargeStreamIcon.setImageResource(resId); } /** * Makes the ringer icon visible with an icon that is chosen * based on the current ringer mode. */ private void setRingerIcon() { mSmallStreamIcon.setVisibility(View.GONE); mLargeStreamIcon.setVisibility(View.VISIBLE); int ringerMode = mAudioService.getRingerMode(); int icon; if (LOGD) Log.d(TAG, "setRingerIcon(), ringerMode: " + ringerMode); if (ringerMode == AudioManager.RINGER_MODE_SILENT) { icon = com.android.internal.R.drawable.ic_volume_off; } else if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { icon = com.android.internal.R.drawable.ic_vibrate; } else { icon = com.android.internal.R.drawable.ic_volume; } mLargeStreamIcon.setImageResource(icon); } protected void onFreeResources() { // We'll keep the views, just ditch the cached drawable and hence // bitmaps mSmallStreamIcon.setImageDrawable(null); mLargeStreamIcon.setImageDrawable(null); synchronized (this) { for (int i = mToneGenerators.length - 1; i >= 0; i--) { if (mToneGenerators[i] != null) { mToneGenerators[i].release(); } mToneGenerators[i] = null; } } } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_VOLUME_CHANGED: { onVolumeChanged(msg.arg1, msg.arg2); break; } case MSG_FREE_RESOURCES: { onFreeResources(); break; } case MSG_STOP_SOUNDS: { onStopSounds(); break; } case MSG_PLAY_SOUND: { onPlaySound(msg.arg1, msg.arg2); break; } case MSG_VIBRATE: { onVibrate(); break; } } } }