blob: 2b9ab4b2c062642c526cd38c7e67c7ec463e7b0d [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.deskclock.data
import android.annotation.TargetApi
import android.app.NotificationManager
import android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED
import android.app.NotificationManager.INTERRUPTION_FILTER_NONE
import android.content.BroadcastReceiver
import android.content.ContentResolver
import android.content.Context
import android.content.Context.AUDIO_SERVICE
import android.content.Context.NOTIFICATION_SERVICE
import android.content.Intent
import android.content.IntentFilter
import android.database.ContentObserver
import android.media.AudioManager
import android.media.AudioManager.STREAM_ALARM
import android.media.RingtoneManager
import android.media.RingtoneManager.TYPE_ALARM
import android.net.Uri
import android.os.AsyncTask
import android.os.Build
import android.os.Handler
import android.provider.Settings.System.CONTENT_URI
import android.provider.Settings.System.DEFAULT_ALARM_ALERT_URI
import androidx.core.app.NotificationManagerCompat
import com.android.deskclock.Utils
import com.android.deskclock.data.DataModel.SilentSetting
/**
* This model fetches and stores reasons that alarms may be suppressed or silenced by system
* settings on the device. This information is displayed passively to notify the user of this
* condition and set their expectations for future firing alarms.
*/
internal class SilentSettingsModel(
private val mContext: Context,
/** Used to determine if the application is in the foreground. */
private val mNotificationModel: NotificationModel
) {
/** Used to query the alarm volume and display the system control to change the alarm volume. */
private val mAudioManager = mContext.getSystemService(AUDIO_SERVICE) as AudioManager
/** Used to query the do-not-disturb setting value, also called "interruption filter". */
private val mNotificationManager =
mContext.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
/** List of listeners to invoke upon silence state change. */
private val mListeners: MutableList<OnSilentSettingsListener> = ArrayList(1)
/**
* The last setting known to be blocking alarms; `null` indicates no settings are
* blocking the app or the app is not in the foreground.
*/
private var mSilentSetting: SilentSetting? = null
/** The background task that checks the device system settings that influence alarm firing. */
private var mCheckSilenceSettingsTask: CheckSilenceSettingsTask? = null
init {
// Watch for changes to the settings that may silence alarms.
val cr: ContentResolver = mContext.getContentResolver()
val contentChangeWatcher: ContentObserver = ContentChangeWatcher()
cr.registerContentObserver(VOLUME_URI, false, contentChangeWatcher)
cr.registerContentObserver(DEFAULT_ALARM_ALERT_URI, false, contentChangeWatcher)
if (Utils.isMOrLater()) {
val filter = IntentFilter(ACTION_INTERRUPTION_FILTER_CHANGED)
mContext.registerReceiver(DoNotDisturbChangeReceiver(), filter)
}
}
fun addSilentSettingsListener(listener: OnSilentSettingsListener) {
mListeners.add(listener)
}
fun removeSilentSettingsListener(listener: OnSilentSettingsListener) {
mListeners.remove(listener)
}
/**
* If the app is in the foreground, start a task to determine if any device setting will block
* alarms from firing. If the app is in the background, clear any results from the last time
* those settings were inspected.
*/
fun updateSilentState() {
// Cancel any task in flight, the result is no longer relevant.
if (mCheckSilenceSettingsTask != null) {
mCheckSilenceSettingsTask!!.cancel(true)
mCheckSilenceSettingsTask = null
}
if (mNotificationModel.isApplicationInForeground) {
mCheckSilenceSettingsTask = CheckSilenceSettingsTask()
mCheckSilenceSettingsTask!!.execute()
} else {
setSilentState(null)
}
}
/**
* @param silentSetting the latest notion of which setting is suppressing alarms; `null`
* if no settings are suppressing alarms
*/
private fun setSilentState(silentSetting: SilentSetting?) {
if (mSilentSetting != silentSetting) {
val oldReason = mSilentSetting
mSilentSetting = silentSetting
for (listener in mListeners) {
listener.onSilentSettingsChange(oldReason, silentSetting)
}
}
}
/**
* This task inspects a variety of system settings that can prevent alarms from firing or the
* associated ringtone from playing. If any of them would prevent an alarm from firing or
* making noise, a description of the setting is reported to this model on the main thread.
*/
// TODO(b/157255731) Replace deprecated AsyncTask calls
private inner class CheckSilenceSettingsTask : AsyncTask<Void?, Void?, SilentSetting?>() {
override fun doInBackground(vararg parameters: Void?): SilentSetting? {
if (!isCancelled() && isDoNotDisturbBlockingAlarms) {
return SilentSetting.DO_NOT_DISTURB
} else if (!isCancelled() && isAlarmStreamMuted) {
return SilentSetting.MUTED_VOLUME
} else if (!isCancelled() && isSystemAlarmRingtoneSilent) {
return SilentSetting.SILENT_RINGTONE
} else if (!isCancelled() && isAppNotificationBlocked) {
return SilentSetting.BLOCKED_NOTIFICATIONS
}
return null
}
override fun onCancelled() {
super.onCancelled()
if (mCheckSilenceSettingsTask == this) {
mCheckSilenceSettingsTask = null
}
}
override fun onPostExecute(silentSetting: SilentSetting?) {
if (mCheckSilenceSettingsTask == this) {
mCheckSilenceSettingsTask = null
setSilentState(silentSetting)
}
}
@get:TargetApi(Build.VERSION_CODES.M)
private val isDoNotDisturbBlockingAlarms: Boolean
get() = if (!Utils.isMOrLater()) {
false
} else try {
val interruptionFilter: Int = mNotificationManager.getCurrentInterruptionFilter()
interruptionFilter == INTERRUPTION_FILTER_NONE
} catch (e: Exception) {
// Since this is purely informational, avoid crashing the app.
false
}
private val isAlarmStreamMuted: Boolean
get() = try {
mAudioManager.getStreamVolume(STREAM_ALARM) <= 0
} catch (e: Exception) {
// Since this is purely informational, avoid crashing the app.
false
}
private val isSystemAlarmRingtoneSilent: Boolean
get() = try {
RingtoneManager.getActualDefaultRingtoneUri(mContext, TYPE_ALARM) == null
} catch (e: Exception) {
// Since this is purely informational, avoid crashing the app.
false
}
private val isAppNotificationBlocked: Boolean
get() = try {
!NotificationManagerCompat.from(mContext).areNotificationsEnabled()
} catch (e: Exception) {
// Since this is purely informational, avoid crashing the app.
false
}
}
/**
* Observe changes to specific URI for settings that can silence firing alarms.
*/
// TODO(b/157255731) Replace Handler with non-deprecated constructor call
private inner class ContentChangeWatcher : ContentObserver(Handler()) {
override fun onChange(selfChange: Boolean) {
updateSilentState()
}
}
/**
* Observe changes to the do-not-disturb setting.
*/
private inner class DoNotDisturbChangeReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
updateSilentState()
}
}
companion object {
/** The Uri to the settings entry that stores alarm stream volume. */
private val VOLUME_URI: Uri = Uri.withAppendedPath(CONTENT_URI, "volume_alarm_speaker")
}
}