OSDN Git Service

Fix the "Alarm volume" icon is displayed incorrectly
[android-x86/frameworks-base.git] / core / java / android / preference / SeekBarVolumizer.java
1 /*
2  * Copyright (C) 2014 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.preference;
18
19 import android.app.NotificationManager;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.database.ContentObserver;
25 import android.media.AudioAttributes;
26 import android.media.AudioManager;
27 import android.media.Ringtone;
28 import android.media.RingtoneManager;
29 import android.net.Uri;
30 import android.os.Handler;
31 import android.os.HandlerThread;
32 import android.os.Message;
33 import android.preference.VolumePreference.VolumeStore;
34 import android.provider.Settings;
35 import android.provider.Settings.Global;
36 import android.provider.Settings.System;
37 import android.util.Log;
38 import android.widget.SeekBar;
39 import android.widget.SeekBar.OnSeekBarChangeListener;
40
41 import com.android.internal.annotations.GuardedBy;
42
43 /**
44  * Turns a {@link SeekBar} into a volume control.
45  * @hide
46  */
47 public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callback {
48     private static final String TAG = "SeekBarVolumizer";
49
50     public interface Callback {
51         void onSampleStarting(SeekBarVolumizer sbv);
52         void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch);
53         void onMuted(boolean muted, boolean zenMuted);
54     }
55
56     private final Context mContext;
57     private final H mUiHandler = new H();
58     private final Callback mCallback;
59     private final Uri mDefaultUri;
60     private final AudioManager mAudioManager;
61     private final NotificationManager mNotificationManager;
62     private final int mStreamType;
63     private final int mMaxStreamVolume;
64     private final boolean mVoiceCapable;
65     private boolean mAffectedByRingerMode;
66     private boolean mNotificationOrRing;
67     private final Receiver mReceiver = new Receiver();
68
69     private Handler mHandler;
70     private Observer mVolumeObserver;
71     private int mOriginalStreamVolume;
72     private int mLastAudibleStreamVolume;
73     // When the old handler is destroyed and a new one is created, there could be a situation where
74     // this is accessed at the same time in different handlers. So, access to this field needs to be
75     // synchronized.
76     @GuardedBy("this")
77     private Ringtone mRingtone;
78     private int mLastProgress = -1;
79     private boolean mMuted;
80     private SeekBar mSeekBar;
81     private int mVolumeBeforeMute = -1;
82     private int mRingerMode;
83     private int mZenMode;
84
85     private static final int MSG_SET_STREAM_VOLUME = 0;
86     private static final int MSG_START_SAMPLE = 1;
87     private static final int MSG_STOP_SAMPLE = 2;
88     private static final int MSG_INIT_SAMPLE = 3;
89     private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;
90
91     public SeekBarVolumizer(Context context, int streamType, Uri defaultUri, Callback callback) {
92         mContext = context;
93         mAudioManager = context.getSystemService(AudioManager.class);
94         mNotificationManager = context.getSystemService(NotificationManager.class);
95         mStreamType = streamType;
96         mAffectedByRingerMode = mAudioManager.isStreamAffectedByRingerMode(mStreamType);
97         mNotificationOrRing = isNotificationOrRing(mStreamType);
98         if (mNotificationOrRing) {
99             mRingerMode = mAudioManager.getRingerModeInternal();
100         }
101         mZenMode = mNotificationManager.getZenMode();
102         mMaxStreamVolume = mAudioManager.getStreamMaxVolume(mStreamType);
103         mCallback = callback;
104         mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType);
105         mLastAudibleStreamVolume = mAudioManager.getLastAudibleStreamVolume(mStreamType);
106         mMuted = mAudioManager.isStreamMute(mStreamType);
107         if (mCallback != null) {
108             mCallback.onMuted(mMuted, isZenMuted());
109         }
110         if (defaultUri == null) {
111             if (mStreamType == AudioManager.STREAM_RING) {
112                 defaultUri = Settings.System.DEFAULT_RINGTONE_URI;
113             } else if (mStreamType == AudioManager.STREAM_NOTIFICATION) {
114                 defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI;
115             } else {
116                 defaultUri = Settings.System.DEFAULT_ALARM_ALERT_URI;
117             }
118         }
119         mDefaultUri = defaultUri;
120         mVoiceCapable = context.getResources().getBoolean(
121                 com.android.internal.R.bool.config_voice_capable);
122     }
123
124     private static boolean isNotificationOrRing(int stream) {
125         return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION;
126     }
127
128     private boolean isNotificationStreamLinked() {
129         return mVoiceCapable && Settings.Secure.getInt(mContext.getContentResolver(),
130                 Settings.Secure.VOLUME_LINK_NOTIFICATION, 1) == 1;
131     }
132
133     public void setSeekBar(SeekBar seekBar) {
134         if (mSeekBar != null) {
135             mSeekBar.setOnSeekBarChangeListener(null);
136         }
137         mSeekBar = seekBar;
138         mSeekBar.setOnSeekBarChangeListener(null);
139         mSeekBar.setMax(mMaxStreamVolume);
140         updateSeekBar();
141         mSeekBar.setOnSeekBarChangeListener(this);
142     }
143
144     private boolean isZenMuted() {
145         return mNotificationOrRing && mZenMode == Global.ZEN_MODE_ALARMS
146                 || mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
147     }
148
149     protected void updateSeekBar() {
150         final boolean zenMuted = isZenMuted();
151         mSeekBar.setEnabled(!zenMuted);
152         if (zenMuted) {
153             mSeekBar.setProgress(mLastAudibleStreamVolume);
154         } else if (mNotificationOrRing && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
155             mSeekBar.setProgress(0);
156             mSeekBar.setEnabled(mStreamType == AudioManager.STREAM_RING);
157         } else if (mMuted) {
158             mSeekBar.setProgress(0);
159         } else {
160             mSeekBar.setEnabled(enableSeekBar());
161             mSeekBar.setProgress(mLastProgress > -1 ? mLastProgress : mOriginalStreamVolume);
162         }
163     }
164
165     private boolean enableSeekBar() {
166         return !(mStreamType == AudioManager.STREAM_NOTIFICATION && isNotificationStreamLinked());
167     }
168
169     @Override
170     public boolean handleMessage(Message msg) {
171         switch (msg.what) {
172             case MSG_SET_STREAM_VOLUME:
173                 if (mMuted && mLastProgress > 0) {
174                     mAudioManager.adjustStreamVolume(mStreamType, AudioManager.ADJUST_UNMUTE, 0);
175                 } else if (!mMuted && mLastProgress == 0) {
176                     mAudioManager.adjustStreamVolume(mStreamType, AudioManager.ADJUST_MUTE, 0);
177                 }
178                 mAudioManager.setStreamVolume(mStreamType, mLastProgress,
179                         AudioManager.FLAG_SHOW_UI_WARNINGS);
180                 break;
181             case MSG_START_SAMPLE:
182                 onStartSample();
183                 break;
184             case MSG_STOP_SAMPLE:
185                 onStopSample();
186                 break;
187             case MSG_INIT_SAMPLE:
188                 onInitSample();
189                 break;
190             default:
191                 Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what);
192         }
193         return true;
194     }
195
196     private void onInitSample() {
197         synchronized (this) {
198             mRingtone = RingtoneManager.getRingtone(mContext, mDefaultUri);
199             if (mRingtone != null) {
200                 mRingtone.setStreamType(mStreamType);
201             }
202         }
203     }
204
205     private void postStartSample() {
206         if (mHandler == null) return;
207         mHandler.removeMessages(MSG_START_SAMPLE);
208         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE),
209                 isSamplePlaying() ? CHECK_RINGTONE_PLAYBACK_DELAY_MS : 0);
210     }
211
212     private void onStartSample() {
213         if (!isSamplePlaying()) {
214             if (mCallback != null) {
215                 mCallback.onSampleStarting(this);
216             }
217
218             synchronized (this) {
219                 if (mRingtone != null) {
220                     try {
221                         mRingtone.setAudioAttributes(new AudioAttributes.Builder(mRingtone
222                                 .getAudioAttributes())
223                                 .setFlags(AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY |
224                                         AudioAttributes.FLAG_BYPASS_MUTE)
225                                 .build());
226                         mRingtone.play();
227                     } catch (Throwable e) {
228                         Log.w(TAG, "Error playing ringtone, stream " + mStreamType, e);
229                     }
230                 }
231             }
232         }
233     }
234
235     private void postStopSample() {
236         if (mHandler == null) return;
237         // remove pending delayed start messages
238         mHandler.removeMessages(MSG_START_SAMPLE);
239         mHandler.removeMessages(MSG_STOP_SAMPLE);
240         mHandler.sendMessage(mHandler.obtainMessage(MSG_STOP_SAMPLE));
241     }
242
243     private void onStopSample() {
244         synchronized (this) {
245             if (mRingtone != null) {
246                 mRingtone.stop();
247             }
248         }
249     }
250
251     public void stop() {
252         if (mHandler == null) return;  // already stopped
253         postStopSample();
254         mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
255         mReceiver.setListening(false);
256         mSeekBar.setOnSeekBarChangeListener(null);
257         mHandler.getLooper().quitSafely();
258         mHandler = null;
259         mVolumeObserver = null;
260     }
261
262     public void start() {
263         if (mHandler != null) return;  // already started
264         HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
265         thread.start();
266         mHandler = new Handler(thread.getLooper(), this);
267         mHandler.sendEmptyMessage(MSG_INIT_SAMPLE);
268         mVolumeObserver = new Observer(mHandler);
269         mContext.getContentResolver().registerContentObserver(
270                 System.getUriFor(System.VOLUME_SETTINGS[mStreamType]),
271                 false, mVolumeObserver);
272         mReceiver.setListening(true);
273     }
274
275     public void revertVolume() {
276         mAudioManager.setStreamVolume(mStreamType, mOriginalStreamVolume, 0);
277     }
278
279     public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {
280         if (fromTouch && enableSeekBar()) {
281             postSetVolume(progress);
282         }
283         if (mCallback != null) {
284             mCallback.onProgressChanged(seekBar, progress, fromTouch);
285         }
286     }
287
288     private void postSetVolume(int progress) {
289         if (mHandler == null) return;
290         // Do the volume changing separately to give responsive UI
291         mLastProgress = progress;
292         mHandler.removeMessages(MSG_SET_STREAM_VOLUME);
293         mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME));
294     }
295
296     public void onStartTrackingTouch(SeekBar seekBar) {
297     }
298
299     public void onStopTrackingTouch(SeekBar seekBar) {
300         postStartSample();
301     }
302
303     public boolean isSamplePlaying() {
304         synchronized (this) {
305             return mRingtone != null && mRingtone.isPlaying();
306         }
307     }
308
309     public void startSample() {
310         postStartSample();
311     }
312
313     public void stopSample() {
314         postStopSample();
315     }
316
317     public SeekBar getSeekBar() {
318         return mSeekBar;
319     }
320
321     public void changeVolumeBy(int amount) {
322         mSeekBar.incrementProgressBy(amount);
323         postSetVolume(mSeekBar.getProgress());
324         postStartSample();
325         mVolumeBeforeMute = -1;
326     }
327
328     public void muteVolume() {
329         if (mVolumeBeforeMute != -1) {
330             mSeekBar.setProgress(mVolumeBeforeMute);
331             postSetVolume(mVolumeBeforeMute);
332             postStartSample();
333             mVolumeBeforeMute = -1;
334         } else {
335             mVolumeBeforeMute = mSeekBar.getProgress();
336             mSeekBar.setProgress(0);
337             postStopSample();
338             postSetVolume(0);
339         }
340     }
341
342     public void onSaveInstanceState(VolumeStore volumeStore) {
343         if (mLastProgress >= 0) {
344             volumeStore.volume = mLastProgress;
345             volumeStore.originalVolume = mOriginalStreamVolume;
346         }
347     }
348
349     public void onRestoreInstanceState(VolumeStore volumeStore) {
350         if (volumeStore.volume != -1) {
351             mOriginalStreamVolume = volumeStore.originalVolume;
352             mLastProgress = volumeStore.volume;
353             postSetVolume(mLastProgress);
354         }
355     }
356
357     private final class H extends Handler {
358         private static final int UPDATE_SLIDER = 1;
359
360         @Override
361         public void handleMessage(Message msg) {
362             if (msg.what == UPDATE_SLIDER) {
363                 if (mSeekBar != null) {
364                     mLastProgress = msg.arg1;
365                     mLastAudibleStreamVolume = msg.arg2;
366                     final boolean muted = ((Boolean)msg.obj).booleanValue();
367                     if (muted != mMuted) {
368                         mMuted = muted;
369                         if (mCallback != null) {
370                             mCallback.onMuted(mMuted, isZenMuted());
371                         }
372                     }
373                     updateSeekBar();
374                 }
375             }
376         }
377
378         public void postUpdateSlider(int volume, int lastAudibleVolume, boolean mute) {
379             obtainMessage(UPDATE_SLIDER, volume, lastAudibleVolume, new Boolean(mute)).sendToTarget();
380         }
381     }
382
383     private void updateSlider() {
384         if (mSeekBar != null && mAudioManager != null) {
385             final int volume = mAudioManager.getStreamVolume(mStreamType);
386             final int lastAudibleVolume = mAudioManager.getLastAudibleStreamVolume(mStreamType);
387             final boolean mute = mAudioManager.isStreamMute(mStreamType);
388             mUiHandler.postUpdateSlider(volume, lastAudibleVolume, mute);
389         }
390     }
391
392     private final class Observer extends ContentObserver {
393         public Observer(Handler handler) {
394             super(handler);
395         }
396
397         @Override
398         public void onChange(boolean selfChange) {
399             super.onChange(selfChange);
400             updateSlider();
401         }
402     }
403
404     private final class Receiver extends BroadcastReceiver {
405         private boolean mListening;
406
407         public void setListening(boolean listening) {
408             if (mListening == listening) return;
409             mListening = listening;
410             if (listening) {
411                 final IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
412                 filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
413                 filter.addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED);
414                 filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
415                 mContext.registerReceiver(this, filter);
416             } else {
417                 mContext.unregisterReceiver(this);
418             }
419         }
420
421         @Override
422         public void onReceive(Context context, Intent intent) {
423             final String action = intent.getAction();
424             if (AudioManager.VOLUME_CHANGED_ACTION.equals(action)) {
425                 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
426                 int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
427                 updateVolumeSlider(streamType, streamValue);
428             } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
429                 if (mNotificationOrRing) {
430                     mRingerMode = mAudioManager.getRingerModeInternal();
431                 }
432                 if (mAffectedByRingerMode) {
433                     updateSlider();
434                 }
435             } else if (AudioManager.STREAM_DEVICES_CHANGED_ACTION.equals(action)) {
436                 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
437                 int streamVolume = mAudioManager.getStreamVolume(streamType);
438                 updateVolumeSlider(streamType, streamVolume);
439             } else if (NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED.equals(action)) {
440                 mZenMode = mNotificationManager.getZenMode();
441                 updateSlider();
442             }
443         }
444
445         private void updateVolumeSlider(int streamType, int streamValue) {
446             final boolean streamMatch = mNotificationOrRing && isNotificationStreamLinked()
447                     ? isNotificationOrRing(streamType)
448                     : (streamType == mStreamType);
449             if (mSeekBar != null && streamMatch && streamValue != -1) {
450                 final boolean muted = mAudioManager.isStreamMute(mStreamType)
451                         || streamValue == 0;
452                 mUiHandler.postUpdateSlider(streamValue, mLastAudibleStreamVolume, muted);
453             }
454         }
455     }
456 }