Home | History | Annotate | Download | only in notification
      1 /*
      2  * Copyright (C) 2014 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.settings.notification;
     18 
     19 import android.app.AlertDialog;
     20 import android.app.AutomaticZenRule;
     21 import android.app.NotificationManager;
     22 import android.app.NotificationManager.Policy;
     23 import android.content.ComponentName;
     24 import android.content.Context;
     25 import android.content.DialogInterface;
     26 import android.content.Intent;
     27 import android.content.pm.ApplicationInfo;
     28 import android.content.pm.PackageManager;
     29 import android.content.pm.ServiceInfo;
     30 import android.content.res.Resources;
     31 import android.graphics.drawable.Drawable;
     32 import android.os.AsyncTask;
     33 import android.os.Bundle;
     34 import android.provider.Settings;
     35 import android.service.notification.ConditionProviderService;
     36 import android.service.notification.ZenModeConfig;
     37 import com.android.settings.utils.ManagedServiceSettings;
     38 import com.android.settings.utils.ZenServiceListing;
     39 import android.support.annotation.VisibleForTesting;
     40 import android.support.v7.preference.Preference;
     41 import android.support.v7.preference.PreferenceCategory;
     42 import android.support.v7.preference.PreferenceScreen;
     43 import android.support.v7.preference.PreferenceViewHolder;
     44 import android.view.View;
     45 
     46 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
     47 import com.android.settings.R;
     48 import com.android.settingslib.TwoTargetPreference;
     49 
     50 import java.lang.ref.WeakReference;
     51 import java.util.Arrays;
     52 import java.util.Comparator;
     53 import java.util.Map;
     54 import java.util.Map.Entry;
     55 
     56 public class ZenModeSettings extends ZenModeSettingsBase {
     57 
     58     public static final String KEY_VISUAL_SETTINGS = "visual_interruptions_settings";
     59 
     60     private static final String KEY_PRIORITY_SETTINGS = "priority_settings";
     61     private static final String KEY_AUTOMATIC_RULES = "automatic_rules";
     62 
     63     static final ManagedServiceSettings.Config CONFIG = getConditionProviderConfig();
     64 
     65     private PreferenceCategory mAutomaticRules;
     66     private Preference mPrioritySettings;
     67     private Preference mVisualSettings;
     68     private Policy mPolicy;
     69     private SummaryBuilder mSummaryBuilder;
     70     private PackageManager mPm;
     71     private ZenServiceListing mServiceListing;
     72 
     73     @Override
     74     public void onCreate(Bundle savedInstanceState) {
     75         super.onCreate(savedInstanceState);
     76 
     77         addPreferencesFromResource(R.xml.zen_mode_settings);
     78         final PreferenceScreen root = getPreferenceScreen();
     79 
     80         mAutomaticRules = (PreferenceCategory) root.findPreference(KEY_AUTOMATIC_RULES);
     81         mPrioritySettings = root.findPreference(KEY_PRIORITY_SETTINGS);
     82         mVisualSettings = root.findPreference(KEY_VISUAL_SETTINGS);
     83         mPolicy = NotificationManager.from(mContext).getNotificationPolicy();
     84         mSummaryBuilder = new SummaryBuilder(getContext());
     85         mPm = mContext.getPackageManager();
     86         mServiceListing = new ZenServiceListing(mContext, CONFIG);
     87         mServiceListing.reloadApprovedServices();
     88     }
     89 
     90     @Override
     91     public void onResume() {
     92         super.onResume();
     93         if (isUiRestricted()) {
     94             return;
     95         }
     96         updateControls();
     97     }
     98 
     99     @Override
    100     public int getMetricsCategory() {
    101         return MetricsEvent.NOTIFICATION_ZEN_MODE;
    102     }
    103 
    104     @Override
    105     protected void onZenModeChanged() {
    106         updateControls();
    107     }
    108 
    109     @Override
    110     protected void onZenModeConfigChanged() {
    111         mPolicy = NotificationManager.from(mContext).getNotificationPolicy();
    112         updateControls();
    113     }
    114 
    115     private void updateControls() {
    116         updatePrioritySettingsSummary();
    117         updateVisualSettingsSummary();
    118         updateAutomaticRules();
    119     }
    120 
    121     private void updatePrioritySettingsSummary() {
    122         mPrioritySettings.setSummary(mSummaryBuilder.getPrioritySettingSummary(mPolicy));
    123     }
    124 
    125     private void updateVisualSettingsSummary() {
    126         mVisualSettings.setSummary(mSummaryBuilder.getVisualSettingSummary(mPolicy));
    127     }
    128 
    129     private void updateAutomaticRules() {
    130         mAutomaticRules.removeAll();
    131         final Map.Entry<String,AutomaticZenRule>[] sortedRules = sortedRules();
    132         for (Map.Entry<String,AutomaticZenRule> sortedRule : sortedRules) {
    133             ZenRulePreference pref = new ZenRulePreference(getPrefContext(), sortedRule);
    134             if (pref.appExists) {
    135                 mAutomaticRules.addPreference(pref);
    136             }
    137         }
    138         final Preference p = new Preference(getPrefContext());
    139         p.setIcon(R.drawable.ic_menu_add);
    140         p.setTitle(R.string.zen_mode_add_rule);
    141         p.setPersistent(false);
    142         p.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
    143             @Override
    144             public boolean onPreferenceClick(Preference preference) {
    145                 mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_ZEN_ADD_RULE);
    146                 showAddRuleDialog();
    147                 return true;
    148             }
    149         });
    150         mAutomaticRules.addPreference(p);
    151     }
    152 
    153     private void showAddRuleDialog() {
    154         new ZenRuleSelectionDialog(mContext, mServiceListing) {
    155             @Override
    156             public void onSystemRuleSelected(ZenRuleInfo ri) {
    157                 showNameRuleDialog(ri);
    158             }
    159 
    160             @Override
    161             public void onExternalRuleSelected(ZenRuleInfo ri) {
    162                 Intent intent = new Intent().setComponent(ri.configurationActivity);
    163                 startActivity(intent);
    164             }
    165         }.show();
    166     }
    167 
    168     private String computeRuleSummary(AutomaticZenRule rule, boolean isSystemRule,
    169             CharSequence providerLabel) {
    170         final String mode = computeZenModeCaption(getResources(), rule.getInterruptionFilter());
    171         final String ruleState = (rule == null || !rule.isEnabled())
    172                 ? getString(R.string.switch_off_text)
    173                 : getString(R.string.zen_mode_rule_summary_enabled_combination, mode);
    174 
    175         return ruleState;
    176     }
    177 
    178     private static ManagedServiceSettings.Config getConditionProviderConfig() {
    179         final ManagedServiceSettings.Config c = new ManagedServiceSettings.Config();
    180         c.tag = TAG;
    181         c.intentAction = ConditionProviderService.SERVICE_INTERFACE;
    182         c.permission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE;
    183         c.noun = "condition provider";
    184         return c;
    185     }
    186 
    187     private static String computeZenModeCaption(Resources res, int zenMode) {
    188         switch (zenMode) {
    189             case NotificationManager.INTERRUPTION_FILTER_ALARMS:
    190                 return res.getString(R.string.zen_mode_option_alarms);
    191             case NotificationManager.INTERRUPTION_FILTER_PRIORITY:
    192                 return res.getString(R.string.zen_mode_option_important_interruptions);
    193             case NotificationManager.INTERRUPTION_FILTER_NONE:
    194                 return res.getString(R.string.zen_mode_option_no_interruptions);
    195             default:
    196                 return null;
    197         }
    198     }
    199 
    200     public static ZenRuleInfo getRuleInfo(PackageManager pm, ServiceInfo si) {
    201         if (si == null || si.metaData == null) return null;
    202         final String ruleType = si.metaData.getString(ConditionProviderService.META_DATA_RULE_TYPE);
    203         final ComponentName configurationActivity = getSettingsActivity(si);
    204         if (ruleType != null && !ruleType.trim().isEmpty() && configurationActivity != null) {
    205             final ZenRuleInfo ri = new ZenRuleInfo();
    206             ri.serviceComponent = new ComponentName(si.packageName, si.name);
    207             ri.settingsAction = Settings.ACTION_ZEN_MODE_EXTERNAL_RULE_SETTINGS;
    208             ri.title = ruleType;
    209             ri.packageName = si.packageName;
    210             ri.configurationActivity = getSettingsActivity(si);
    211             ri.packageLabel = si.applicationInfo.loadLabel(pm);
    212             ri.ruleInstanceLimit =
    213                     si.metaData.getInt(ConditionProviderService.META_DATA_RULE_INSTANCE_LIMIT, -1);
    214             return ri;
    215         }
    216         return null;
    217     }
    218 
    219     private static ComponentName getSettingsActivity(ServiceInfo si) {
    220         if (si == null || si.metaData == null) return null;
    221         final String configurationActivity =
    222                 si.metaData.getString(ConditionProviderService.META_DATA_CONFIGURATION_ACTIVITY);
    223         if (configurationActivity != null) {
    224             return ComponentName.unflattenFromString(configurationActivity);
    225         }
    226         return null;
    227     }
    228 
    229     private void showNameRuleDialog(final ZenRuleInfo ri) {
    230         new ZenRuleNameDialog(mContext, null) {
    231             @Override
    232             public void onOk(String ruleName) {
    233                 mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_ZEN_ADD_RULE_OK);
    234                 AutomaticZenRule rule = new AutomaticZenRule(ruleName, ri.serviceComponent,
    235                         ri.defaultConditionId, NotificationManager.INTERRUPTION_FILTER_PRIORITY,
    236                         true);
    237                 String savedRuleId = addZenRule(rule);
    238                 if (savedRuleId != null) {
    239                     startActivity(getRuleIntent(ri.settingsAction, null, savedRuleId));
    240                 }
    241             }
    242         }.show();
    243     }
    244 
    245     private void showDeleteRuleDialog(final String ruleId, final CharSequence ruleName) {
    246         new AlertDialog.Builder(mContext)
    247                 .setMessage(getString(R.string.zen_mode_delete_rule_confirmation, ruleName))
    248                 .setNegativeButton(R.string.cancel, null)
    249                 .setPositiveButton(R.string.zen_mode_delete_rule_button,
    250                         new DialogInterface.OnClickListener() {
    251                             @Override
    252                             public void onClick(DialogInterface dialog, int which) {
    253                                 mMetricsFeatureProvider.action(mContext,
    254                                         MetricsEvent.ACTION_ZEN_DELETE_RULE_OK);
    255                                 removeZenRule(ruleId);
    256                             }
    257                         })
    258                 .show();
    259     }
    260 
    261     private Intent getRuleIntent(String settingsAction, ComponentName configurationActivity,
    262             String ruleId) {
    263         Intent intent = new Intent()
    264                 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
    265                 .putExtra(ConditionProviderService.EXTRA_RULE_ID, ruleId);
    266         if (configurationActivity != null) {
    267             intent.setComponent(configurationActivity);
    268         } else {
    269             intent.setAction(settingsAction);
    270         }
    271         return intent;
    272     }
    273 
    274     private Map.Entry<String,AutomaticZenRule>[] sortedRules() {
    275         final Map.Entry<String,AutomaticZenRule>[] rt =
    276                 mRules.toArray(new Map.Entry[mRules.size()]);
    277         Arrays.sort(rt, RULE_COMPARATOR);
    278         return rt;
    279     }
    280 
    281     @Override
    282     protected int getHelpResource() {
    283         return R.string.help_uri_interruptions;
    284     }
    285 
    286     private class ZenRulePreference extends TwoTargetPreference {
    287         final CharSequence mName;
    288         final String mId;
    289         final boolean appExists;
    290 
    291         public ZenRulePreference(Context context,
    292                 final Map.Entry<String, AutomaticZenRule> ruleEntry) {
    293             super(context);
    294 
    295             final AutomaticZenRule rule = ruleEntry.getValue();
    296             mName = rule.getName();
    297             mId = ruleEntry.getKey();
    298 
    299             final boolean isSchedule = ZenModeConfig.isValidScheduleConditionId(
    300                     rule.getConditionId());
    301             final boolean isEvent = ZenModeConfig.isValidEventConditionId(rule.getConditionId());
    302             final boolean isSystemRule = isSchedule || isEvent;
    303 
    304             try {
    305                 ApplicationInfo info = mPm.getApplicationInfo(rule.getOwner().getPackageName(), 0);
    306                 setSummary(computeRuleSummary(rule, isSystemRule, info.loadLabel(mPm)));
    307             } catch (PackageManager.NameNotFoundException e) {
    308                 appExists = false;
    309                 return;
    310             }
    311 
    312             appExists = true;
    313             setTitle(rule.getName());
    314             setPersistent(false);
    315 
    316             final String action = isSchedule ? ZenModeScheduleRuleSettings.ACTION
    317                     : isEvent ? ZenModeEventRuleSettings.ACTION : "";
    318             ServiceInfo si = mServiceListing.findService(rule.getOwner());
    319             ComponentName settingsActivity = getSettingsActivity(si);
    320             setIntent(getRuleIntent(action, settingsActivity, mId));
    321             setSelectable(settingsActivity != null || isSystemRule);
    322         }
    323 
    324         @Override
    325         protected int getSecondTargetResId() {
    326             return R.layout.zen_rule_widget;
    327         }
    328 
    329         @Override
    330         public void onBindViewHolder(PreferenceViewHolder view) {
    331             super.onBindViewHolder(view);
    332 
    333             View v = view.findViewById(R.id.delete_zen_rule);
    334             if (v != null) {
    335                 v.setOnClickListener(mDeleteListener);
    336             }
    337         }
    338 
    339         private final View.OnClickListener mDeleteListener = new View.OnClickListener() {
    340             @Override
    341             public void onClick(View v) {
    342                 showDeleteRuleDialog(mId, mName);
    343             }
    344         };
    345     }
    346 
    347     public static class SummaryBuilder {
    348 
    349         private Context mContext;
    350 
    351         public SummaryBuilder(Context context) {
    352             mContext = context;
    353         }
    354 
    355         String getPrioritySettingSummary(Policy policy) {
    356             String s = mContext.getString(R.string.zen_mode_alarms);
    357             s = prepend(s, isCategoryEnabled(policy, Policy.PRIORITY_CATEGORY_EVENTS),
    358                     R.string.zen_mode_events);
    359             s = prepend(s, isCategoryEnabled(policy, Policy.PRIORITY_CATEGORY_REMINDERS),
    360                 R.string.zen_mode_reminders);
    361             if (isCategoryEnabled(policy, Policy.PRIORITY_CATEGORY_MESSAGES)) {
    362                 if (policy.priorityMessageSenders == Policy.PRIORITY_SENDERS_ANY) {
    363                     s = append(s, true, R.string.zen_mode_all_messages);
    364                 } else {
    365                     s = append(s, true, R.string.zen_mode_selected_messages);
    366                 }
    367             }
    368             if (isCategoryEnabled(policy, Policy.PRIORITY_CATEGORY_CALLS)) {
    369                 if (policy.priorityCallSenders == Policy.PRIORITY_SENDERS_ANY) {
    370                     s = append(s, true, R.string.zen_mode_all_callers);
    371                 } else {
    372                     s = append(s, true, R.string.zen_mode_selected_callers);
    373                 }
    374             } else if (isCategoryEnabled(policy, Policy.PRIORITY_CATEGORY_REPEAT_CALLERS)) {
    375                 s = append(s, true, R.string.zen_mode_repeat_callers);
    376             }
    377             return s;
    378         }
    379 
    380         String getVisualSettingSummary(Policy policy) {
    381             String s = mContext.getString(R.string.zen_mode_all_visual_interruptions);
    382             if (isEffectSuppressed(policy, Policy.SUPPRESSED_EFFECT_SCREEN_ON)
    383                 && isEffectSuppressed(policy, Policy.SUPPRESSED_EFFECT_SCREEN_OFF)) {
    384                 s = mContext.getString(R.string.zen_mode_no_visual_interruptions);
    385             } else if (isEffectSuppressed(policy, Policy.SUPPRESSED_EFFECT_SCREEN_ON)) {
    386                 s = mContext.getString(R.string.zen_mode_screen_on_visual_interruptions);
    387             } else if (isEffectSuppressed(policy, Policy.SUPPRESSED_EFFECT_SCREEN_OFF)) {
    388                 s = mContext.getString(R.string.zen_mode_screen_off_visual_interruptions);
    389             }
    390             return s;
    391         }
    392 
    393         String getAutomaticRulesSummary() {
    394             final int count = getEnabledAutomaticRulesCount();
    395             return count == 0 ? mContext.getString(R.string.zen_mode_settings_summary_off)
    396                 : mContext.getResources().getQuantityString(
    397                     R.plurals.zen_mode_settings_summary_on, count, count);
    398         }
    399 
    400         @VisibleForTesting
    401         int getEnabledAutomaticRulesCount() {
    402             int count = 0;
    403             final Map<String, AutomaticZenRule> ruleMap =
    404                 NotificationManager.from(mContext).getAutomaticZenRules();
    405             if (ruleMap != null) {
    406                 for (Entry<String, AutomaticZenRule> ruleEntry : ruleMap.entrySet()) {
    407                     final AutomaticZenRule rule = ruleEntry.getValue();
    408                     if (rule != null && rule.isEnabled()) {
    409                         count++;
    410                     }
    411                 }
    412             }
    413             return count;
    414         }
    415 
    416         @VisibleForTesting
    417         String append(String s, boolean condition, int resId) {
    418             if (condition) {
    419                 return mContext.getString(
    420                     R.string.join_many_items_middle, s, mContext.getString(resId));
    421             }
    422             return s;
    423         }
    424 
    425         @VisibleForTesting
    426         String prepend(String s, boolean condition, int resId) {
    427             if (condition) {
    428                 return mContext.getString(
    429                         R.string.join_many_items_middle, mContext.getString(resId), s);
    430             }
    431             return s;
    432         }
    433 
    434         private boolean isCategoryEnabled(Policy policy, int categoryType) {
    435             return (policy.priorityCategories & categoryType) != 0;
    436         }
    437 
    438         private boolean isEffectSuppressed(Policy policy, int effect) {
    439             return (policy.suppressedVisualEffects & effect) != 0;
    440         }
    441     }
    442 
    443     private static final Comparator<Map.Entry<String,AutomaticZenRule>> RULE_COMPARATOR =
    444             new Comparator<Map.Entry<String,AutomaticZenRule>>() {
    445                 @Override
    446                 public int compare(Map.Entry<String,AutomaticZenRule> lhs,
    447                         Map.Entry<String,AutomaticZenRule> rhs) {
    448                     int byDate = Long.compare(lhs.getValue().getCreationTime(),
    449                             rhs.getValue().getCreationTime());
    450                     if (byDate != 0) {
    451                         return byDate;
    452                     } else {
    453                         return key(lhs.getValue()).compareTo(key(rhs.getValue()));
    454                     }
    455                 }
    456 
    457                 private String key(AutomaticZenRule rule) {
    458                     final int type = ZenModeConfig.isValidScheduleConditionId(rule.getConditionId())
    459                             ? 1
    460                             : ZenModeConfig.isValidEventConditionId(rule.getConditionId())
    461                                     ? 2
    462                                     : 3;
    463                     return type + rule.getName().toString();
    464                 }
    465             };
    466 }
    467