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 }