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.Manifest;
20 import android.annotation.NonNull;
21 import android.annotation.RequiresPermission;
22 import android.app.NotificationManager;
23 import android.compat.annotation.UnsupportedAppUsage;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.database.ContentObserver;
29 import android.media.AudioAttributes;
30 import android.media.AudioManager;
31 import android.media.Ringtone;
32 import android.media.RingtoneManager;
33 import android.media.audiopolicy.AudioProductStrategy;
34 import android.media.audiopolicy.AudioVolumeGroup;
35 import android.net.Uri;
36 import android.os.Handler;
37 import android.os.HandlerThread;
38 import android.os.Message;
39 import android.preference.VolumePreference.VolumeStore;
40 import android.provider.Settings;
41 import android.provider.Settings.Global;
42 import android.provider.Settings.System;
43 import android.service.notification.ZenModeConfig;
44 import android.util.Log;
45 import android.widget.SeekBar;
46 import android.widget.SeekBar.OnSeekBarChangeListener;
47 
48 import com.android.internal.annotations.GuardedBy;
49 import com.android.internal.os.SomeArgs;
50 
51 import java.util.concurrent.TimeUnit;
52 
53 /**
54  * Turns a {@link SeekBar} into a volume control.
55  * @hide
56  *
57  * @deprecated Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
58  *      <a href="{@docRoot}reference/androidx/preference/package-summary.html">
59  *      Preference Library</a> for consistent behavior across all devices. For more information on
60  *      using the AndroidX Preference Library see
61  *      <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>.
62  */
63 @Deprecated
64 public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callback {
65     private static final String TAG = "SeekBarVolumizer";
66 
67     public interface Callback {
onSampleStarting(SeekBarVolumizer sbv)68         void onSampleStarting(SeekBarVolumizer sbv);
onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch)69         void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch);
onMuted(boolean muted, boolean zenMuted)70         void onMuted(boolean muted, boolean zenMuted);
71         /**
72          * Callback reporting that the seek bar is start tracking.
73          *
74          * @param sbv - The seek bar that start tracking
75          */
onStartTrackingTouch(SeekBarVolumizer sbv)76         void onStartTrackingTouch(SeekBarVolumizer sbv);
77 
78         /**
79          * Callback reporting that the seek bar is stop tracking.
80          *
81          * @param sbv - The seek bar that stop tracking
82          */
onStopTrackingTouch(SeekBarVolumizer sbv)83         default void onStopTrackingTouch(SeekBarVolumizer sbv) {
84         }
85     }
86 
87     private static final int MSG_GROUP_VOLUME_CHANGED = 1;
88     private static long sStopVolumeTime = 0L;
89     private final Handler mVolumeHandler = new VolumeHandler();
90     private AudioAttributes mAttributes;
91     private int mVolumeGroupId;
92 
93     private final AudioManager.VolumeGroupCallback mVolumeGroupCallback =
94             new AudioManager.VolumeGroupCallback() {
95         @Override
96         public void onAudioVolumeGroupChanged(int group, int flags) {
97             if (mHandler == null) {
98                 return;
99             }
100             SomeArgs args = SomeArgs.obtain();
101             args.arg1 = group;
102             args.arg2 = flags;
103             mVolumeHandler.sendMessage(mHandler.obtainMessage(MSG_GROUP_VOLUME_CHANGED, args));
104         }
105     };
106 
107     @UnsupportedAppUsage
108     private final Context mContext;
109     private final H mUiHandler = new H();
110     private final Callback mCallback;
111     private final Uri mDefaultUri;
112     @UnsupportedAppUsage
113     private final AudioManager mAudioManager;
114     private final NotificationManager mNotificationManager;
115     @UnsupportedAppUsage
116     private final int mStreamType;
117     private final int mMaxStreamVolume;
118     private boolean mAffectedByRingerMode;
119     private boolean mNotificationOrRing;
120     private final Receiver mReceiver = new Receiver();
121 
122     private Handler mHandler;
123     private Observer mVolumeObserver;
124     @UnsupportedAppUsage
125     private int mOriginalStreamVolume;
126     private int mLastAudibleStreamVolume;
127     // When the old handler is destroyed and a new one is created, there could be a situation where
128     // this is accessed at the same time in different handlers. So, access to this field needs to be
129     // synchronized.
130     @GuardedBy("this")
131     @UnsupportedAppUsage
132     private Ringtone mRingtone;
133     @UnsupportedAppUsage
134     private int mLastProgress = -1;
135     private boolean mMuted;
136     @UnsupportedAppUsage
137     private SeekBar mSeekBar;
138     private int mVolumeBeforeMute = -1;
139     private int mRingerMode;
140     private int mZenMode;
141     private boolean mPlaySample;
142     private final boolean mDeviceHasProductStrategies;
143 
144     private static final int MSG_SET_STREAM_VOLUME = 0;
145     private static final int MSG_START_SAMPLE = 1;
146     private static final int MSG_STOP_SAMPLE = 2;
147     private static final int MSG_INIT_SAMPLE = 3;
148     private static final int MSG_UPDATE_SLIDER_MAYBE_LATER = 4;
149     private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;
150     private static final int CHECK_UPDATE_SLIDER_LATER_MS = 500;
151     private static final long SET_STREAM_VOLUME_DELAY_MS = TimeUnit.MILLISECONDS.toMillis(500);
152     private static final long START_SAMPLE_DELAY_MS = TimeUnit.MILLISECONDS.toMillis(500);
153     private static final long DURATION_TO_START_DELAYING = TimeUnit.MILLISECONDS.toMillis(2000);
154 
155     private NotificationManager.Policy mNotificationPolicy;
156     private boolean mAllowAlarms;
157     private boolean mAllowMedia;
158     private boolean mAllowRinger;
159 
160     @UnsupportedAppUsage
SeekBarVolumizer(Context context, int streamType, Uri defaultUri, Callback callback)161     public SeekBarVolumizer(Context context, int streamType, Uri defaultUri, Callback callback) {
162         this(context, streamType, defaultUri, callback, true /* playSample */);
163     }
164 
165     @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
SeekBarVolumizer( Context context, int streamType, Uri defaultUri, Callback callback, boolean playSample)166     public SeekBarVolumizer(
167             Context context,
168             int streamType,
169             Uri defaultUri,
170             Callback callback,
171             boolean playSample) {
172         mContext = context;
173         mAudioManager = context.getSystemService(AudioManager.class);
174         mDeviceHasProductStrategies = hasAudioProductStrategies();
175         mNotificationManager = context.getSystemService(NotificationManager.class);
176         mNotificationPolicy = mNotificationManager.getConsolidatedNotificationPolicy();
177         mAllowAlarms = (mNotificationPolicy.priorityCategories & NotificationManager.Policy
178                 .PRIORITY_CATEGORY_ALARMS) != 0;
179         mAllowMedia = (mNotificationPolicy.priorityCategories & NotificationManager.Policy
180                 .PRIORITY_CATEGORY_MEDIA) != 0;
181         mAllowRinger = !ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(
182                 mNotificationPolicy);
183         mStreamType = streamType;
184         mAffectedByRingerMode = mAudioManager.isStreamAffectedByRingerMode(mStreamType);
185         mNotificationOrRing = isNotificationOrRing(mStreamType);
186         if (mNotificationOrRing) {
187             mRingerMode = mAudioManager.getRingerModeInternal();
188         }
189         mZenMode = mNotificationManager.getZenMode();
190 
191         if (mDeviceHasProductStrategies) {
192             mVolumeGroupId = getVolumeGroupIdForLegacyStreamType(mStreamType);
193             mAttributes = getAudioAttributesForLegacyStreamType(
194                     mStreamType);
195         }
196 
197         mMaxStreamVolume = mAudioManager.getStreamMaxVolume(mStreamType);
198         mCallback = callback;
199         mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType);
200         mLastAudibleStreamVolume = mAudioManager.getLastAudibleStreamVolume(mStreamType);
201         mMuted = mAudioManager.isStreamMute(mStreamType);
202         mPlaySample = playSample;
203         if (mCallback != null) {
204             mCallback.onMuted(mMuted, isZenMuted());
205         }
206         if (defaultUri == null) {
207             if (mStreamType == AudioManager.STREAM_RING) {
208                 defaultUri = Settings.System.DEFAULT_RINGTONE_URI;
209             } else if (mStreamType == AudioManager.STREAM_NOTIFICATION) {
210                 defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI;
211             } else {
212                 defaultUri = Settings.System.DEFAULT_ALARM_ALERT_URI;
213             }
214         }
215         mDefaultUri = defaultUri;
216     }
217 
218     /**
219      * DO NOT CALL every time this is needed, use once in constructor,
220      * read mDeviceHasProductStrategies instead
221      * @return true if stream types are used for volume management, false if volume groups are
222      *     used for volume management
223      */
hasAudioProductStrategies()224     private boolean hasAudioProductStrategies() {
225         return AudioManager.getAudioProductStrategies().size() > 0;
226     }
227 
getVolumeGroupIdForLegacyStreamType(int streamType)228     private int getVolumeGroupIdForLegacyStreamType(int streamType) {
229         for (final AudioProductStrategy productStrategy :
230                 AudioManager.getAudioProductStrategies()) {
231             int volumeGroupId = productStrategy.getVolumeGroupIdForLegacyStreamType(streamType);
232             if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
233                 return volumeGroupId;
234             }
235         }
236 
237         return AudioManager.getAudioProductStrategies().stream()
238                 .map(strategy -> strategy.getVolumeGroupIdForAudioAttributes(
239                         AudioProductStrategy.getDefaultAttributes()))
240                 .filter(volumeGroupId -> volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP)
241                 .findFirst()
242                 .orElse(AudioVolumeGroup.DEFAULT_VOLUME_GROUP);
243     }
244 
getAudioAttributesForLegacyStreamType(int streamType)245     private @NonNull AudioAttributes getAudioAttributesForLegacyStreamType(int streamType) {
246         for (final AudioProductStrategy productStrategy :
247                 AudioManager.getAudioProductStrategies()) {
248             AudioAttributes aa = productStrategy.getAudioAttributesForLegacyStreamType(streamType);
249             if (aa != null) {
250                 return aa;
251             }
252         }
253         return new AudioAttributes.Builder()
254                 .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
255                 .setUsage(AudioAttributes.USAGE_UNKNOWN).build();
256     }
257 
isNotificationOrRing(int stream)258     private static boolean isNotificationOrRing(int stream) {
259         return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION;
260     }
261 
isAlarmsStream(int stream)262     private static boolean isAlarmsStream(int stream) {
263         return stream == AudioManager.STREAM_ALARM;
264     }
265 
isMediaStream(int stream)266     private static boolean isMediaStream(int stream) {
267         return stream == AudioManager.STREAM_MUSIC;
268     }
269 
setSeekBar(SeekBar seekBar)270     public void setSeekBar(SeekBar seekBar) {
271         if (mSeekBar != null) {
272             mSeekBar.setOnSeekBarChangeListener(null);
273         }
274         mSeekBar = seekBar;
275         mSeekBar.setOnSeekBarChangeListener(null);
276         mSeekBar.setMax(mMaxStreamVolume);
277         updateSeekBar();
278         mSeekBar.setOnSeekBarChangeListener(this);
279     }
280 
isZenMuted()281     private boolean isZenMuted() {
282         return mNotificationOrRing && mZenMode == Global.ZEN_MODE_ALARMS
283                 || mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
284                 || (mZenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
285                     && ((!mAllowAlarms && isAlarmsStream(mStreamType))
286                         || (!mAllowMedia && isMediaStream(mStreamType))
287                         || (!mAllowRinger && isNotificationOrRing(mStreamType))));
288     }
289 
updateSeekBar()290     protected void updateSeekBar() {
291         final boolean zenMuted = isZenMuted();
292         mSeekBar.setEnabled(!zenMuted);
293         if (zenMuted) {
294             mSeekBar.setProgress(mLastAudibleStreamVolume, true);
295         } else if (mNotificationOrRing && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
296             // For ringer-mode affected streams, show volume as zero when ringermode is vibrate
297             if (mStreamType == AudioManager.STREAM_RING
298                     || (mStreamType == AudioManager.STREAM_NOTIFICATION && mMuted)) {
299                 mSeekBar.setProgress(0, true);
300             }
301         } else if (mMuted) {
302             mSeekBar.setProgress(0, true);
303         } else {
304             mSeekBar.setProgress(mLastProgress > -1 ? mLastProgress : mOriginalStreamVolume, true);
305         }
306     }
307 
308     @Override
handleMessage(Message msg)309     public boolean handleMessage(Message msg) {
310         switch (msg.what) {
311             case MSG_SET_STREAM_VOLUME:
312                 if (mMuted && mLastProgress > 0) {
313                     mAudioManager.adjustStreamVolume(mStreamType, AudioManager.ADJUST_UNMUTE, 0);
314                 } else if (!mMuted && mLastProgress == 0) {
315                     mAudioManager.adjustStreamVolume(mStreamType, AudioManager.ADJUST_MUTE, 0);
316                 }
317                 mAudioManager.setStreamVolume(mStreamType, mLastProgress,
318                         AudioManager.FLAG_SHOW_UI_WARNINGS);
319                 break;
320             case MSG_START_SAMPLE:
321                 if (mPlaySample) {
322                     onStartSample();
323                 }
324                 break;
325             case MSG_STOP_SAMPLE:
326                 if (mPlaySample) {
327                     onStopSample();
328                 }
329                 break;
330             case MSG_INIT_SAMPLE:
331                 if (mPlaySample) {
332                     onInitSample();
333                 }
334                 break;
335             case MSG_UPDATE_SLIDER_MAYBE_LATER:
336                 onUpdateSliderMaybeLater();
337                 break;
338             default:
339                 Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what);
340         }
341         return true;
342     }
343 
onInitSample()344     private void onInitSample() {
345         synchronized (this) {
346             mRingtone = RingtoneManager.getRingtone(mContext, mDefaultUri);
347             if (mRingtone != null) {
348                 mRingtone.setStreamType(mStreamType);
349             }
350         }
351     }
352 
postStartSample()353     private void postStartSample() {
354         if (mHandler == null) return;
355         mHandler.removeMessages(MSG_START_SAMPLE);
356         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE),
357                 isSamplePlaying() ? CHECK_RINGTONE_PLAYBACK_DELAY_MS
358                         : isDelay() ? START_SAMPLE_DELAY_MS : 0);
359     }
360 
onUpdateSliderMaybeLater()361     private void onUpdateSliderMaybeLater() {
362         if (isDelay()) {
363             postUpdateSliderMaybeLater();
364             return;
365         }
366         updateSlider();
367     }
368 
postUpdateSliderMaybeLater()369     private void postUpdateSliderMaybeLater() {
370         if (mHandler == null) return;
371         mHandler.removeMessages(MSG_UPDATE_SLIDER_MAYBE_LATER);
372         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SLIDER_MAYBE_LATER),
373                 CHECK_UPDATE_SLIDER_LATER_MS);
374     }
375 
376     // After stop volume it needs to add a small delay when playing volume or set stream.
377     // It is because the call volume is from the earpiece and the alarm/ring/media
378     // is from the speaker. If play the alarm volume or set alarm stream right after stop
379     // call volume, the alarm volume on earpiece is returned then cause the volume value incorrect.
380     // It needs a small delay after stop call volume to get alarm volume on speaker.
381     // e.g. : If the ring volume has adjusted right after call volume stopped in 2 second
382     // then delay 0.5 second to set stream or play volume ringtone.
isDelay()383     private boolean isDelay() {
384         final long durationTime = java.lang.System.currentTimeMillis() - sStopVolumeTime;
385         return durationTime >= 0 && durationTime < DURATION_TO_START_DELAYING;
386     }
387 
setStopVolumeTime()388     private void setStopVolumeTime() {
389         // set the time of stop volume
390         if ((mStreamType == AudioManager.STREAM_VOICE_CALL
391                 || mStreamType == AudioManager.STREAM_RING
392                 || mStreamType == AudioManager.STREAM_NOTIFICATION
393                 || mStreamType == AudioManager.STREAM_ALARM)) {
394             sStopVolumeTime = java.lang.System.currentTimeMillis();
395         }
396     }
397 
onStartSample()398     private void onStartSample() {
399         if (!isSamplePlaying()) {
400             if (mCallback != null) {
401                 mCallback.onSampleStarting(this);
402             }
403 
404             synchronized (this) {
405                 if (mRingtone != null) {
406                     try {
407                         mRingtone.setAudioAttributes(new AudioAttributes.Builder(mRingtone
408                                 .getAudioAttributes())
409                                 .setFlags(AudioAttributes.FLAG_BYPASS_MUTE)
410                                 .build());
411                         mRingtone.play();
412                     } catch (Throwable e) {
413                         Log.w(TAG, "Error playing ringtone, stream " + mStreamType, e);
414                     }
415                 }
416             }
417         }
418     }
419 
postStopSample()420     private void postStopSample() {
421         if (mHandler == null) return;
422         setStopVolumeTime();
423         // remove pending delayed start messages
424         mHandler.removeMessages(MSG_START_SAMPLE);
425         mHandler.removeMessages(MSG_STOP_SAMPLE);
426         mHandler.sendMessage(mHandler.obtainMessage(MSG_STOP_SAMPLE));
427     }
428 
onStopSample()429     private void onStopSample() {
430         synchronized (this) {
431             if (mRingtone != null) {
432                 mRingtone.stop();
433             }
434         }
435     }
436 
437     @UnsupportedAppUsage
stop()438     public void stop() {
439         if (mHandler == null) return;  // already stopped
440         postStopSample();
441         mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
442         mReceiver.setListening(false);
443         if (mDeviceHasProductStrategies) {
444             unregisterVolumeGroupCb();
445         }
446         mSeekBar.setOnSeekBarChangeListener(null);
447         mHandler.getLooper().quitSafely();
448         mHandler = null;
449         mVolumeObserver = null;
450     }
451 
start()452     public void start() {
453         if (mHandler != null) return;  // already started
454         HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
455         thread.start();
456         mHandler = new Handler(thread.getLooper(), this);
457         mHandler.sendEmptyMessage(MSG_INIT_SAMPLE);
458         mVolumeObserver = new Observer(mHandler);
459         mContext.getContentResolver().registerContentObserver(
460                 System.getUriFor(System.VOLUME_SETTINGS_INT[mStreamType]),
461                 false, mVolumeObserver);
462         mReceiver.setListening(true);
463         if (mDeviceHasProductStrategies) {
464             registerVolumeGroupCb();
465         }
466     }
467 
revertVolume()468     public void revertVolume() {
469         mAudioManager.setStreamVolume(mStreamType, mOriginalStreamVolume, 0);
470     }
471 
onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch)472     public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {
473         if (fromTouch) {
474             postSetVolume(progress);
475         }
476         if (mCallback != null) {
477             mCallback.onProgressChanged(seekBar, progress, fromTouch);
478         }
479     }
480 
postSetVolume(int progress)481     private void postSetVolume(int progress) {
482         if (mHandler == null) return;
483         // Do the volume changing separately to give responsive UI
484         mLastProgress = progress;
485         mHandler.removeMessages(MSG_SET_STREAM_VOLUME);
486         mHandler.removeMessages(MSG_START_SAMPLE);
487         mHandler.removeMessages(MSG_UPDATE_SLIDER_MAYBE_LATER);
488         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME),
489                 isDelay() ? SET_STREAM_VOLUME_DELAY_MS : 0);
490     }
491 
onStartTrackingTouch(SeekBar seekBar)492     public void onStartTrackingTouch(SeekBar seekBar) {
493         if (mCallback != null) {
494             mCallback.onStartTrackingTouch(this);
495         }
496     }
497 
onStopTrackingTouch(SeekBar seekBar)498     public void onStopTrackingTouch(SeekBar seekBar) {
499         postStartSample();
500         if (mCallback != null) {
501             mCallback.onStopTrackingTouch(this);
502         }
503     }
504 
isSamplePlaying()505     public boolean isSamplePlaying() {
506         synchronized (this) {
507             return mRingtone != null && mRingtone.isPlaying();
508         }
509     }
510 
startSample()511     public void startSample() {
512         postStartSample();
513     }
514 
stopSample()515     public void stopSample() {
516         postStopSample();
517     }
518 
getSeekBar()519     public SeekBar getSeekBar() {
520         return mSeekBar;
521     }
522 
changeVolumeBy(int amount)523     public void changeVolumeBy(int amount) {
524         mSeekBar.incrementProgressBy(amount);
525         postSetVolume(mSeekBar.getProgress());
526         postStartSample();
527         mVolumeBeforeMute = -1;
528     }
529 
muteVolume()530     public void muteVolume() {
531         if (mVolumeBeforeMute != -1) {
532             mSeekBar.setProgress(mVolumeBeforeMute, true);
533             postSetVolume(mVolumeBeforeMute);
534             postStartSample();
535             mVolumeBeforeMute = -1;
536         } else {
537             mVolumeBeforeMute = mSeekBar.getProgress();
538             mSeekBar.setProgress(0, true);
539             postStopSample();
540             postSetVolume(0);
541         }
542     }
543 
onSaveInstanceState(VolumeStore volumeStore)544     public void onSaveInstanceState(VolumeStore volumeStore) {
545         if (mLastProgress >= 0) {
546             volumeStore.volume = mLastProgress;
547             volumeStore.originalVolume = mOriginalStreamVolume;
548         }
549     }
550 
onRestoreInstanceState(VolumeStore volumeStore)551     public void onRestoreInstanceState(VolumeStore volumeStore) {
552         if (volumeStore.volume != -1) {
553             mOriginalStreamVolume = volumeStore.originalVolume;
554             mLastProgress = volumeStore.volume;
555             postSetVolume(mLastProgress);
556         }
557     }
558 
559     private final class H extends Handler {
560         private static final int UPDATE_SLIDER = 1;
561 
562         @Override
handleMessage(Message msg)563         public void handleMessage(Message msg) {
564             if (msg.what == UPDATE_SLIDER) {
565                 if (mSeekBar != null) {
566                     mLastProgress = msg.arg1;
567                     mLastAudibleStreamVolume = msg.arg2;
568                     final boolean muted = ((Boolean)msg.obj).booleanValue();
569                     if (muted != mMuted) {
570                         mMuted = muted;
571                         if (mCallback != null) {
572                             mCallback.onMuted(mMuted, isZenMuted());
573                         }
574                     }
575                     updateSeekBar();
576                 }
577             }
578         }
579 
postUpdateSlider(int volume, int lastAudibleVolume, boolean mute)580         public void postUpdateSlider(int volume, int lastAudibleVolume, boolean mute) {
581             obtainMessage(UPDATE_SLIDER, volume, lastAudibleVolume, Boolean.valueOf(mute))
582                     .sendToTarget();
583         }
584     }
585 
updateSlider()586     private void updateSlider() {
587         if (mSeekBar != null && mAudioManager != null) {
588             final int volume = mAudioManager.getStreamVolume(mStreamType);
589             final int lastAudibleVolume = mAudioManager.getLastAudibleStreamVolume(mStreamType);
590             final boolean mute = mAudioManager.isStreamMute(mStreamType);
591             mUiHandler.postUpdateSlider(volume, lastAudibleVolume, mute);
592         }
593     }
594 
595     private final class Observer extends ContentObserver {
Observer(Handler handler)596         public Observer(Handler handler) {
597             super(handler);
598         }
599 
600         @Override
onChange(boolean selfChange)601         public void onChange(boolean selfChange) {
602             super.onChange(selfChange);
603             updateSlider();
604         }
605     }
606 
607     private final class Receiver extends BroadcastReceiver {
608         private boolean mListening;
609 
setListening(boolean listening)610         public void setListening(boolean listening) {
611             if (mListening == listening) return;
612             mListening = listening;
613             if (listening) {
614                 final IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
615                 filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
616                 filter.addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED);
617                 filter.addAction(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED);
618                 filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
619                 mContext.registerReceiver(this, filter);
620             } else {
621                 mContext.unregisterReceiver(this);
622             }
623         }
624 
625         @Override
onReceive(Context context, Intent intent)626         public void onReceive(Context context, Intent intent) {
627             final String action = intent.getAction();
628             if (AudioManager.VOLUME_CHANGED_ACTION.equals(action)) {
629                 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
630                 int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
631                 if (mDeviceHasProductStrategies && !isDelay()) {
632                     updateVolumeSlider(streamType, streamValue);
633                 }
634             } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
635                 if (mNotificationOrRing) {
636                     mRingerMode = mAudioManager.getRingerModeInternal();
637                 }
638                 if (mAffectedByRingerMode) {
639                     updateSlider();
640                 }
641             } else if (AudioManager.STREAM_DEVICES_CHANGED_ACTION.equals(action)) {
642                 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
643 
644                 if (mDeviceHasProductStrategies) {
645                     if (isDelay()) {
646                         // not the right time to update the sliders, try again later
647                         postUpdateSliderMaybeLater();
648                     } else {
649                         int streamVolume = mAudioManager.getStreamVolume(streamType);
650                         updateVolumeSlider(streamType, streamVolume);
651                     }
652 
653                 } else {
654                     int volumeGroup = getVolumeGroupIdForLegacyStreamType(streamType);
655                     if (volumeGroup != AudioVolumeGroup.DEFAULT_VOLUME_GROUP
656                             && volumeGroup == mVolumeGroupId) {
657                         int streamVolume = mAudioManager.getStreamVolume(streamType);
658                         if (!isDelay()) {
659                             updateVolumeSlider(streamType, streamVolume);
660                         }
661                     }
662                 }
663             } else if (NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED.equals(action)) {
664                 mZenMode = mNotificationManager.getZenMode();
665                 updateSlider();
666             } else if (NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED.equals(action)) {
667                 mNotificationPolicy = mNotificationManager.getConsolidatedNotificationPolicy();
668                 mAllowAlarms = (mNotificationPolicy.priorityCategories & NotificationManager.Policy
669                         .PRIORITY_CATEGORY_ALARMS) != 0;
670                 mAllowMedia = (mNotificationPolicy.priorityCategories
671                         & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA) != 0;
672                 mAllowRinger = !ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(
673                         mNotificationPolicy);
674                 updateSlider();
675             }
676         }
677 
updateVolumeSlider(int streamType, int streamValue)678         private void updateVolumeSlider(int streamType, int streamValue) {
679             final boolean streamMatch = (streamType == mStreamType);
680             if (mSeekBar != null && streamMatch && streamValue != -1) {
681                 final boolean muted = mAudioManager.isStreamMute(mStreamType)
682                         || streamValue == 0;
683                 mUiHandler.postUpdateSlider(streamValue, mLastAudibleStreamVolume, muted);
684             }
685         }
686     }
687 
registerVolumeGroupCb()688     private void registerVolumeGroupCb() {
689         if (mVolumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
690             mAudioManager.registerVolumeGroupCallback(Runnable::run, mVolumeGroupCallback);
691             updateSlider();
692         }
693     }
694 
unregisterVolumeGroupCb()695     private void unregisterVolumeGroupCb() {
696         if (mVolumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
697             mAudioManager.unregisterVolumeGroupCallback(mVolumeGroupCallback);
698         }
699     }
700 
701     private class VolumeHandler extends Handler {
702         @Override
handleMessage(Message msg)703         public void handleMessage(Message msg) {
704             SomeArgs args = (SomeArgs) msg.obj;
705             switch (msg.what) {
706                 case MSG_GROUP_VOLUME_CHANGED:
707                     int group = (int) args.arg1;
708                     if (mVolumeGroupId != group
709                             || mVolumeGroupId == AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
710                         return;
711                     }
712                     updateSlider();
713                     break;
714             }
715         }
716     }
717 }
718