VolumePanel.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
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
17package android.view;
18
19import android.media.ToneGenerator;
20import android.media.AudioManager;
21import android.media.AudioService;
22import android.content.Context;
23import android.os.Handler;
24import android.os.Message;
25import android.os.Vibrator;
26import android.util.Config;
27import android.util.Log;
28import android.widget.ImageView;
29import android.widget.ProgressBar;
30import android.widget.TextView;
31import android.widget.Toast;
32
33/**
34 * Handle the volume up and down keys.
35 *
36 * This code really should be moved elsewhere.
37 *
38 * @hide
39 */
40public class VolumePanel extends Handler
41{
42    private static final String TAG = "VolumePanel";
43    private static boolean LOGD = false || Config.LOGD;
44
45    /**
46     * The delay before playing a sound. This small period exists so the user
47     * can press another key (non-volume keys, too) to have it NOT be audible.
48     * <p>
49     * PhoneWindow will implement this part.
50     */
51    public static final int PLAY_SOUND_DELAY = 300;
52
53    /**
54     * The delay before vibrating. This small period exists so if the user is
55     * moving to silent mode, it will not emit a short vibrate (it normally
56     * would since vibrate is between normal mode and silent mode using hardware
57     * keys).
58     */
59    public static final int VIBRATE_DELAY = 300;
60
61    private static final int VIBRATE_DURATION = 300;
62    private static final int BEEP_DURATION = 150;
63    private static final int MAX_VOLUME = 100;
64    private static final int FREE_DELAY = 10000;
65
66    private static final int MSG_VOLUME_CHANGED = 0;
67    private static final int MSG_FREE_RESOURCES = 1;
68    private static final int MSG_PLAY_SOUND = 2;
69    private static final int MSG_STOP_SOUNDS = 3;
70    private static final int MSG_VIBRATE = 4;
71
72    private final String RINGTONE_VOLUME_TEXT;
73    private final String MUSIC_VOLUME_TEXT;
74    private final String INCALL_VOLUME_TEXT;
75    private final String ALARM_VOLUME_TEXT;
76    private final String UNKNOWN_VOLUME_TEXT;
77
78    protected Context mContext;
79    protected AudioService mAudioService;
80
81    private Toast mToast;
82    private View mView;
83    private TextView mMessage;
84    private ImageView mOtherStreamIcon;
85    private ImageView mRingerStreamIcon;
86    private ProgressBar mLevel;
87
88    // Synchronize when accessing this
89    private ToneGenerator mToneGenerators[];
90    private Vibrator mVibrator;
91
92    public VolumePanel(Context context, AudioService volumeService) {
93        mContext = context;
94        mAudioService = volumeService;
95        mToast = new Toast(context);
96
97        RINGTONE_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_ringtone);
98        MUSIC_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_music);
99        INCALL_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_call);
100        ALARM_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_alarm);
101        UNKNOWN_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_unknown);
102
103        LayoutInflater inflater = (LayoutInflater) context
104                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
105        View view = mView = inflater.inflate(com.android.internal.R.layout.volume_adjust, null);
106        mMessage = (TextView) view.findViewById(com.android.internal.R.id.message);
107        mOtherStreamIcon = (ImageView) view.findViewById(com.android.internal.R.id.other_stream_icon);
108        mRingerStreamIcon = (ImageView) view.findViewById(com.android.internal.R.id.ringer_stream_icon);
109        mLevel = (ProgressBar) view.findViewById(com.android.internal.R.id.level);
110
111        mToneGenerators = new ToneGenerator[AudioManager.NUM_STREAMS];
112        mVibrator = new Vibrator();
113    }
114
115    public void postVolumeChanged(int streamType, int flags) {
116        if (hasMessages(MSG_VOLUME_CHANGED)) return;
117        removeMessages(MSG_FREE_RESOURCES);
118        obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget();
119    }
120
121    /**
122     * Override this if you have other work to do when the volume changes (for
123     * example, vibrating, playing a sound, etc.). Make sure to call through to
124     * the superclass implementation.
125     */
126    protected void onVolumeChanged(int streamType, int flags) {
127
128        if (LOGD) Log.d(TAG, "onVolumeChanged(streamType: " + streamType + ", flags: " + flags + ")");
129
130        if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
131            onShowVolumeChanged(streamType, flags);
132        }
133
134        if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0) {
135            removeMessages(MSG_PLAY_SOUND);
136            sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY);
137        }
138
139        if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) {
140            removeMessages(MSG_PLAY_SOUND);
141            removeMessages(MSG_VIBRATE);
142            onStopSounds();
143        }
144
145        removeMessages(MSG_FREE_RESOURCES);
146        sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
147    }
148
149    protected void onShowVolumeChanged(int streamType, int flags) {
150        int index = mAudioService.getStreamVolume(streamType);
151        String message = UNKNOWN_VOLUME_TEXT;
152
153        if (LOGD) {
154            Log.d(TAG, "onShowVolumeChanged(streamType: " + streamType
155                    + ", flags: " + flags + "), index: " + index);
156        }
157
158        switch (streamType) {
159
160            case AudioManager.STREAM_RING: {
161                message = RINGTONE_VOLUME_TEXT;
162                setRingerIcon(index);
163                break;
164            }
165
166            case AudioManager.STREAM_MUSIC: {
167                message = MUSIC_VOLUME_TEXT;
168                setOtherIcon(index);
169                break;
170            }
171
172            case AudioManager.STREAM_VOICE_CALL: {
173                message = INCALL_VOLUME_TEXT;
174                /*
175                 * For in-call voice call volume, there is no inaudible volume
176                 * level, so never show the mute icon
177                 */
178                setOtherIcon(index == 0 ? 1 : index);
179                break;
180            }
181
182            case AudioManager.STREAM_ALARM: {
183                message = ALARM_VOLUME_TEXT;
184                setOtherIcon(index);
185                break;
186            }
187        }
188
189        if (!mMessage.getText().equals(message)) {
190            mMessage.setText(message);
191        }
192
193        int max = mAudioService.getStreamMaxVolume(streamType);
194        if (max != mLevel.getMax()) {
195            mLevel.setMax(max);
196        }
197        mLevel.setProgress(index);
198
199        mToast.setView(mView);
200        mToast.setDuration(Toast.LENGTH_SHORT);
201        mToast.setGravity(Gravity.TOP, 0, 0);
202        mToast.show();
203
204        // Do a little vibrate if applicable (only when going into vibrate mode)
205        if ((flags & AudioManager.FLAG_VIBRATE) != 0 &&
206                mAudioService.isStreamAffectedByRingerMode(streamType) &&
207                mAudioService.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE &&
208                mAudioService.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER)) {
209            sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY);
210        }
211
212    }
213
214    protected void onPlaySound(int streamType, int flags) {
215
216        if (hasMessages(MSG_STOP_SOUNDS)) {
217            removeMessages(MSG_STOP_SOUNDS);
218            // Force stop right now
219            onStopSounds();
220        }
221
222        synchronized (this) {
223            ToneGenerator toneGen = getOrCreateToneGenerator(streamType);
224            toneGen.startTone(ToneGenerator.TONE_PROP_BEEP);
225        }
226
227        sendMessageDelayed(obtainMessage(MSG_STOP_SOUNDS), BEEP_DURATION);
228    }
229
230    protected void onStopSounds() {
231
232        synchronized (this) {
233            for (int i = AudioManager.NUM_STREAMS - 1; i >= 0; i--) {
234                ToneGenerator toneGen = mToneGenerators[i];
235                if (toneGen != null) {
236                    toneGen.stopTone();
237                }
238            }
239        }
240    }
241
242    protected void onVibrate() {
243
244        // Make sure we ended up in vibrate ringer mode
245        if (mAudioService.getRingerMode() != AudioManager.RINGER_MODE_VIBRATE) {
246            return;
247        }
248
249        mVibrator.vibrate(VIBRATE_DURATION);
250    }
251
252    /**
253     * Lock on this VolumePanel instance as long as you use the returned ToneGenerator.
254     */
255    private ToneGenerator getOrCreateToneGenerator(int streamType) {
256        synchronized (this) {
257            if (mToneGenerators[streamType] == null) {
258                return mToneGenerators[streamType] = new ToneGenerator(streamType, MAX_VOLUME);
259            } else {
260                return mToneGenerators[streamType];
261            }
262        }
263    }
264
265    private void setOtherIcon(int index) {
266        mRingerStreamIcon.setVisibility(View.GONE);
267        mOtherStreamIcon.setVisibility(View.VISIBLE);
268
269        mOtherStreamIcon.setImageResource(index == 0
270                ? com.android.internal.R.drawable.ic_volume_off_small
271                : com.android.internal.R.drawable.ic_volume_small);
272    }
273
274    private void setRingerIcon(int index) {
275        mOtherStreamIcon.setVisibility(View.GONE);
276        mRingerStreamIcon.setVisibility(View.VISIBLE);
277
278        int ringerMode = mAudioService.getRingerMode();
279        int icon;
280
281        if (LOGD) Log.d(TAG, "setRingerIcon(index: " + index+ "), ringerMode: " + ringerMode);
282
283        if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
284            icon = com.android.internal.R.drawable.ic_volume_off;
285        } else if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) {
286            icon = com.android.internal.R.drawable.ic_vibrate;
287        } else {
288            icon = com.android.internal.R.drawable.ic_volume;
289        }
290        mRingerStreamIcon.setImageResource(icon);
291    }
292
293    protected void onFreeResources() {
294        // We'll keep the views, just ditch the cached drawable and hence
295        // bitmaps
296        mOtherStreamIcon.setImageDrawable(null);
297        mRingerStreamIcon.setImageDrawable(null);
298
299        synchronized (this) {
300            for (int i = mToneGenerators.length - 1; i >= 0; i--) {
301                if (mToneGenerators[i] != null) {
302                    mToneGenerators[i].release();
303                }
304                mToneGenerators[i] = null;
305            }
306        }
307    }
308
309    @Override
310    public void handleMessage(Message msg) {
311        switch (msg.what) {
312
313            case MSG_VOLUME_CHANGED: {
314                onVolumeChanged(msg.arg1, msg.arg2);
315                break;
316            }
317
318            case MSG_FREE_RESOURCES: {
319                onFreeResources();
320                break;
321            }
322
323            case MSG_STOP_SOUNDS: {
324                onStopSounds();
325                break;
326            }
327
328            case MSG_PLAY_SOUND: {
329                onPlaySound(msg.arg1, msg.arg2);
330                break;
331            }
332
333            case MSG_VIBRATE: {
334                onVibrate();
335                break;
336            }
337
338        }
339    }
340
341}
342