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