1 /*
2  * Copyright (C) 2020 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 com.android.deskclock.settings
18 
19 import android.annotation.TargetApi
20 import android.app.NotificationManager
21 import android.content.Context
22 import android.content.Context.AUDIO_SERVICE
23 import android.content.Context.NOTIFICATION_SERVICE
24 import android.database.ContentObserver
25 import android.media.AudioManager
26 import android.media.AudioManager.STREAM_ALARM
27 import android.os.Build
28 import android.provider.Settings
29 import android.util.AttributeSet
30 import android.view.View
31 import android.widget.ImageView
32 import android.widget.SeekBar
33 import androidx.preference.Preference
34 import androidx.preference.PreferenceViewHolder
35 
36 import com.android.deskclock.R
37 import com.android.deskclock.RingtonePreviewKlaxon
38 import com.android.deskclock.Utils
39 import com.android.deskclock.data.DataModel
40 
41 class AlarmVolumePreference(context: Context?, attrs: AttributeSet?) : Preference(context, attrs) {
42     private lateinit var mSeekbar: SeekBar
43     private lateinit var mAlarmIcon: ImageView
44 
45     private var mPreviewPlaying = false
46 
47     override fun onBindViewHolder(holder: PreferenceViewHolder) {
48         super.onBindViewHolder(holder)
49         val context: Context = getContext()
50         val audioManager: AudioManager = context.getSystemService(AUDIO_SERVICE) as AudioManager
51 
52         // Disable click feedback for this preference.
53         holder.itemView.setClickable(false)
54         mSeekbar = holder.findViewById(R.id.alarm_volume_slider) as SeekBar
55         mSeekbar.setMax(audioManager.getStreamMaxVolume(STREAM_ALARM))
56         mSeekbar.setProgress(audioManager.getStreamVolume(STREAM_ALARM))
57         mAlarmIcon = holder.findViewById(R.id.alarm_icon) as ImageView
58         onSeekbarChanged()
59 
60         val volumeObserver: ContentObserver = object : ContentObserver(mSeekbar.getHandler()) {
61             override fun onChange(selfChange: Boolean) {
62                 // Volume was changed elsewhere, update our slider.
63                 mSeekbar.setProgress(audioManager.getStreamVolume(STREAM_ALARM))
64             }
65         }
66 
67         mSeekbar.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
68             override fun onViewAttachedToWindow(v: View?) {
69                 context.getContentResolver().registerContentObserver(Settings.System.CONTENT_URI,
70                         true, volumeObserver)
71             }
72 
73             override fun onViewDetachedFromWindow(v: View?) {
74                 context.getContentResolver().unregisterContentObserver(volumeObserver)
75             }
76         })
77 
78         mSeekbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
79             override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
80                 if (fromUser) {
81                     audioManager.setStreamVolume(STREAM_ALARM, progress, 0)
82                 }
83                 onSeekbarChanged()
84             }
85 
86             override fun onStartTrackingTouch(seekBar: SeekBar?) {
87             }
88 
89             override fun onStopTrackingTouch(seekBar: SeekBar) {
90                 if (!mPreviewPlaying && seekBar.getProgress() != 0) {
91                     // If we are not currently playing and progress is set to non-zero, start.
92                     RingtonePreviewKlaxon
93                             .start(context, DataModel.dataModel.defaultAlarmRingtoneUri)
94                     mPreviewPlaying = true
95                     seekBar.postDelayed(Runnable {
96                         RingtonePreviewKlaxon.stop(context)
97                         mPreviewPlaying = false
98                     }, ALARM_PREVIEW_DURATION_MS)
99                 }
100             }
101         })
102     }
103 
104     private fun onSeekbarChanged() {
105         mSeekbar.setEnabled(doesDoNotDisturbAllowAlarmPlayback())
106         val imageRes = if (mSeekbar.getProgress() == 0) {
107             R.drawable.ic_alarm_off_24dp
108         } else {
109             R.drawable.ic_alarm_small
110         }
111         mAlarmIcon.setImageResource(imageRes)
112     }
113 
114     private fun doesDoNotDisturbAllowAlarmPlayback(): Boolean {
115         return !Utils.isNOrLater || doesDoNotDisturbAllowAlarmPlaybackNPlus()
116     }
117 
118     @TargetApi(Build.VERSION_CODES.N)
119     private fun doesDoNotDisturbAllowAlarmPlaybackNPlus(): Boolean {
120         val notificationManager =
121                 getContext().getSystemService(NOTIFICATION_SERVICE) as NotificationManager
122         return notificationManager.getCurrentInterruptionFilter() !=
123                 NotificationManager.INTERRUPTION_FILTER_NONE
124     }
125 
126     companion object {
127         private const val ALARM_PREVIEW_DURATION_MS: Long = 2000
128     }
129 }