• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.data
18 
19 import android.annotation.TargetApi
20 import android.app.NotificationManager
21 import android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED
22 import android.app.NotificationManager.INTERRUPTION_FILTER_NONE
23 import android.content.BroadcastReceiver
24 import android.content.ContentResolver
25 import android.content.Context
26 import android.content.Context.AUDIO_SERVICE
27 import android.content.Context.NOTIFICATION_SERVICE
28 import android.content.Intent
29 import android.content.IntentFilter
30 import android.database.ContentObserver
31 import android.media.AudioManager
32 import android.media.AudioManager.STREAM_ALARM
33 import android.media.RingtoneManager
34 import android.media.RingtoneManager.TYPE_ALARM
35 import android.net.Uri
36 import android.os.AsyncTask
37 import android.os.Build
38 import android.os.Handler
39 import android.os.Looper
40 import android.provider.Settings.System.CONTENT_URI
41 import android.provider.Settings.System.DEFAULT_ALARM_ALERT_URI
42 import androidx.core.app.NotificationManagerCompat
43 
44 import com.android.deskclock.Utils
45 import com.android.deskclock.data.DataModel.SilentSetting
46 
47 /**
48  * This model fetches and stores reasons that alarms may be suppressed or silenced by system
49  * settings on the device. This information is displayed passively to notify the user of this
50  * condition and set their expectations for future firing alarms.
51  */
52 internal class SilentSettingsModel(
53     private val mContext: Context,
54     /** Used to determine if the application is in the foreground.  */
55     private val mNotificationModel: NotificationModel
56 ) {
57 
58     /** Used to query the alarm volume and display the system control to change the alarm volume. */
59     private val mAudioManager = mContext.getSystemService(AUDIO_SERVICE) as AudioManager
60 
61     /** Used to query the do-not-disturb setting value, also called "interruption filter".  */
62     private val mNotificationManager =
63             mContext.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
64 
65     /** List of listeners to invoke upon silence state change.  */
66     private val mListeners: MutableList<OnSilentSettingsListener> = ArrayList(1)
67 
68     /**
69      * The last setting known to be blocking alarms; `null` indicates no settings are
70      * blocking the app or the app is not in the foreground.
71      */
72     private var mSilentSetting: SilentSetting? = null
73 
74     /** The background task that checks the device system settings that influence alarm firing.  */
75     private var mCheckSilenceSettingsTask: CheckSilenceSettingsTask? = null
76 
77     init {
78         // Watch for changes to the settings that may silence alarms.
79         val cr: ContentResolver = mContext.getContentResolver()
80         val contentChangeWatcher: ContentObserver = ContentChangeWatcher()
81         cr.registerContentObserver(VOLUME_URI, false, contentChangeWatcher)
82         cr.registerContentObserver(DEFAULT_ALARM_ALERT_URI, false, contentChangeWatcher)
83         if (Utils.isMOrLater) {
84             val filter = IntentFilter(ACTION_INTERRUPTION_FILTER_CHANGED)
85             mContext.registerReceiver(DoNotDisturbChangeReceiver(), filter)
86         }
87     }
88 
addSilentSettingsListenernull89     fun addSilentSettingsListener(listener: OnSilentSettingsListener) {
90         mListeners.add(listener)
91     }
92 
removeSilentSettingsListenernull93     fun removeSilentSettingsListener(listener: OnSilentSettingsListener) {
94         mListeners.remove(listener)
95     }
96 
97     /**
98      * If the app is in the foreground, start a task to determine if any device setting will block
99      * alarms from firing. If the app is in the background, clear any results from the last time
100      * those settings were inspected.
101      */
updateSilentStatenull102     fun updateSilentState() {
103         // Cancel any task in flight, the result is no longer relevant.
104         if (mCheckSilenceSettingsTask != null) {
105             mCheckSilenceSettingsTask!!.cancel(true)
106             mCheckSilenceSettingsTask = null
107         }
108 
109         if (mNotificationModel.isApplicationInForeground) {
110             mCheckSilenceSettingsTask = CheckSilenceSettingsTask()
111             mCheckSilenceSettingsTask!!.execute()
112         } else {
113             setSilentState(null)
114         }
115     }
116 
117     /**
118      * @param silentSetting the latest notion of which setting is suppressing alarms; `null`
119      * if no settings are suppressing alarms
120      */
setSilentStatenull121     private fun setSilentState(silentSetting: SilentSetting?) {
122         if (mSilentSetting != silentSetting) {
123             val oldReason = mSilentSetting
124             mSilentSetting = silentSetting
125             for (listener in mListeners) {
126                 listener.onSilentSettingsChange(oldReason, silentSetting)
127             }
128         }
129     }
130 
131     /**
132      * This task inspects a variety of system settings that can prevent alarms from firing or the
133      * associated ringtone from playing. If any of them would prevent an alarm from firing or
134      * making noise, a description of the setting is reported to this model on the main thread.
135      */
136     // TODO(b/165664115) Replace deprecated AsyncTask calls
137     private inner class CheckSilenceSettingsTask : AsyncTask<Void?, Void?, SilentSetting?>() {
doInBackgroundnull138         override fun doInBackground(vararg parameters: Void?): SilentSetting? {
139             if (!isCancelled() && isDoNotDisturbBlockingAlarms) {
140                 return SilentSetting.DO_NOT_DISTURB
141             } else if (!isCancelled() && isAlarmStreamMuted) {
142                 return SilentSetting.MUTED_VOLUME
143             } else if (!isCancelled() && isSystemAlarmRingtoneSilent) {
144                 return SilentSetting.SILENT_RINGTONE
145             } else if (!isCancelled() && isAppNotificationBlocked) {
146                 return SilentSetting.BLOCKED_NOTIFICATIONS
147             }
148             return null
149         }
150 
onCancellednull151         override fun onCancelled() {
152             super.onCancelled()
153             if (mCheckSilenceSettingsTask == this) {
154                 mCheckSilenceSettingsTask = null
155             }
156         }
157 
onPostExecutenull158         override fun onPostExecute(silentSetting: SilentSetting?) {
159             if (mCheckSilenceSettingsTask == this) {
160                 mCheckSilenceSettingsTask = null
161                 setSilentState(silentSetting)
162             }
163         }
164 
165         @get:TargetApi(Build.VERSION_CODES.M)
166         private val isDoNotDisturbBlockingAlarms: Boolean
167             get() = if (!Utils.isMOrLater) {
168                 false
169             } else try {
170                 val interruptionFilter: Int = mNotificationManager.getCurrentInterruptionFilter()
171                 interruptionFilter == INTERRUPTION_FILTER_NONE
172             } catch (e: Exception) {
173                 // Since this is purely informational, avoid crashing the app.
174                 false
175             }
176 
177         private val isAlarmStreamMuted: Boolean
178             get() = try {
179                 mAudioManager.getStreamVolume(STREAM_ALARM) <= 0
180             } catch (e: Exception) {
181                 // Since this is purely informational, avoid crashing the app.
182                 false
183             }
184 
185         private val isSystemAlarmRingtoneSilent: Boolean
186             get() = try {
187                 RingtoneManager.getActualDefaultRingtoneUri(mContext, TYPE_ALARM) == null
188             } catch (e: Exception) {
189                 // Since this is purely informational, avoid crashing the app.
190                 false
191             }
192 
193         private val isAppNotificationBlocked: Boolean
194             get() = try {
195                 !NotificationManagerCompat.from(mContext).areNotificationsEnabled()
196             } catch (e: Exception) {
197                 // Since this is purely informational, avoid crashing the app.
198                 false
199             }
200     }
201 
202     /**
203      * Observe changes to specific URI for settings that can silence firing alarms.
204      */
205     private inner class ContentChangeWatcher : ContentObserver(Handler(Looper.myLooper()!!)) {
onChangenull206         override fun onChange(selfChange: Boolean) {
207             updateSilentState()
208         }
209     }
210 
211     /**
212      * Observe changes to the do-not-disturb setting.
213      */
214     private inner class DoNotDisturbChangeReceiver : BroadcastReceiver() {
onReceivenull215         override fun onReceive(context: Context?, intent: Intent?) {
216             updateSilentState()
217         }
218     }
219 
220     companion object {
221         /** The Uri to the settings entry that stores alarm stream volume.  */
222         private val VOLUME_URI: Uri = Uri.withAppendedPath(CONTENT_URI, "volume_alarm_speaker")
223     }
224 }