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