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