1 /* 2 * Copyright (C) 2015 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 static android.service.notification.ZenModeConfig.ALL_DAYS; 20 21 import android.content.ComponentName; 22 import android.content.Intent; 23 import android.content.pm.ServiceInfo; 24 import android.content.res.Resources; 25 import android.os.Bundle; 26 import android.os.UserHandle; 27 import android.preference.Preference; 28 import android.preference.Preference.OnPreferenceClickListener; 29 import android.preference.PreferenceScreen; 30 import android.provider.Settings; 31 import android.provider.Settings.Global; 32 import android.service.notification.ConditionProviderService; 33 import android.service.notification.ZenModeConfig; 34 import android.service.notification.ZenModeConfig.EventInfo; 35 import android.service.notification.ZenModeConfig.ScheduleInfo; 36 import android.service.notification.ZenModeConfig.ZenRule; 37 import android.text.format.DateFormat; 38 import android.util.Log; 39 40 import com.android.internal.logging.MetricsLogger; 41 import com.android.settings.R; 42 import com.android.settings.notification.ManagedServiceSettings.Config; 43 import com.android.settings.notification.ZenModeEventRuleSettings.CalendarInfo; 44 import com.android.settings.notification.ZenRuleNameDialog.RuleInfo; 45 46 import java.text.SimpleDateFormat; 47 import java.util.Arrays; 48 import java.util.Calendar; 49 import java.util.Comparator; 50 import java.util.List; 51 import java.util.TreeSet; 52 53 public class ZenModeAutomationSettings extends ZenModeSettingsBase { 54 55 static final Config CONFIG = getConditionProviderConfig(); 56 57 // per-instance to ensure we're always using the current locale 58 private final SimpleDateFormat mDayFormat = new SimpleDateFormat("EEE"); 59 private final Calendar mCalendar = Calendar.getInstance(); 60 61 private ServiceListing mServiceListing; 62 63 @Override onCreate(Bundle icicle)64 public void onCreate(Bundle icicle) { 65 super.onCreate(icicle); 66 addPreferencesFromResource(R.xml.zen_mode_automation_settings); 67 mServiceListing = new ServiceListing(mContext, CONFIG); 68 mServiceListing.addCallback(mServiceListingCallback); 69 mServiceListing.reload(); 70 mServiceListing.setListening(true); 71 } 72 73 @Override onDestroy()74 public void onDestroy() { 75 super.onDestroy(); 76 mServiceListing.setListening(false); 77 mServiceListing.removeCallback(mServiceListingCallback); 78 } 79 80 @Override onZenModeChanged()81 protected void onZenModeChanged() { 82 // don't care 83 } 84 85 @Override onZenModeConfigChanged()86 protected void onZenModeConfigChanged() { 87 updateControls(); 88 } 89 90 @Override onResume()91 public void onResume() { 92 super.onResume(); 93 updateControls(); 94 } 95 showAddRuleDialog()96 private void showAddRuleDialog() { 97 new ZenRuleNameDialog(mContext, mServiceListing, null, mConfig.getAutomaticRuleNames()) { 98 @Override 99 public void onOk(String ruleName, RuleInfo ri) { 100 MetricsLogger.action(mContext, MetricsLogger.ACTION_ZEN_ADD_RULE_OK); 101 final ZenRule rule = new ZenRule(); 102 rule.name = ruleName; 103 rule.enabled = true; 104 rule.zenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; 105 rule.conditionId = ri.defaultConditionId; 106 rule.component = ri.serviceComponent; 107 final ZenModeConfig newConfig = mConfig.copy(); 108 final String ruleId = newConfig.newRuleId(); 109 newConfig.automaticRules.put(ruleId, rule); 110 if (setZenModeConfig(newConfig)) { 111 showRule(ri.settingsAction, ri.configurationActivity, ruleId, rule.name); 112 } 113 } 114 }.show(); 115 } 116 showRule(String settingsAction, ComponentName configurationActivity, String ruleId, String ruleName)117 private void showRule(String settingsAction, ComponentName configurationActivity, 118 String ruleId, String ruleName) { 119 if (DEBUG) Log.d(TAG, "showRule " + ruleId + " name=" + ruleName); 120 mContext.startActivity(new Intent(settingsAction) 121 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) 122 .putExtra(ZenModeRuleSettingsBase.EXTRA_RULE_ID, ruleId)); 123 } 124 sortedRules()125 private ZenRuleInfo[] sortedRules() { 126 final ZenRuleInfo[] rt = new ZenRuleInfo[mConfig.automaticRules.size()]; 127 for (int i = 0; i < rt.length; i++) { 128 final ZenRuleInfo zri = new ZenRuleInfo(); 129 zri.id = mConfig.automaticRules.keyAt(i); 130 zri.rule = mConfig.automaticRules.valueAt(i); 131 rt[i] = zri; 132 } 133 Arrays.sort(rt, RULE_COMPARATOR); 134 return rt; 135 } 136 updateControls()137 private void updateControls() { 138 final PreferenceScreen root = getPreferenceScreen(); 139 root.removeAll(); 140 if (mConfig == null) return; 141 final ZenRuleInfo[] sortedRules = sortedRules(); 142 for (int i = 0; i < sortedRules.length; i++) { 143 final String id = sortedRules[i].id; 144 final ZenRule rule = sortedRules[i].rule; 145 final boolean isSchedule = ZenModeConfig.isValidScheduleConditionId(rule.conditionId); 146 final boolean isEvent = ZenModeConfig.isValidEventConditionId(rule.conditionId); 147 final Preference p = new Preference(mContext); 148 p.setIcon(isSchedule ? R.drawable.ic_schedule 149 : isEvent ? R.drawable.ic_event 150 : R.drawable.ic_label); 151 p.setTitle(rule.name); 152 p.setSummary(computeRuleSummary(rule)); 153 p.setPersistent(false); 154 p.setOnPreferenceClickListener(new OnPreferenceClickListener() { 155 @Override 156 public boolean onPreferenceClick(Preference preference) { 157 final String action = isSchedule ? ZenModeScheduleRuleSettings.ACTION 158 : isEvent ? ZenModeEventRuleSettings.ACTION 159 : ZenModeExternalRuleSettings.ACTION; 160 showRule(action, null, id, rule.name); 161 return true; 162 } 163 }); 164 root.addPreference(p); 165 } 166 final Preference p = new Preference(mContext); 167 p.setIcon(R.drawable.ic_add); 168 p.setTitle(R.string.zen_mode_add_rule); 169 p.setPersistent(false); 170 p.setOnPreferenceClickListener(new OnPreferenceClickListener() { 171 @Override 172 public boolean onPreferenceClick(Preference preference) { 173 MetricsLogger.action(mContext, MetricsLogger.ACTION_ZEN_ADD_RULE); 174 showAddRuleDialog(); 175 return true; 176 } 177 }); 178 root.addPreference(p); 179 } 180 181 @Override getMetricsCategory()182 protected int getMetricsCategory() { 183 return MetricsLogger.NOTIFICATION_ZEN_MODE_AUTOMATION; 184 } 185 computeRuleSummary(ZenRule rule)186 private String computeRuleSummary(ZenRule rule) { 187 if (rule == null || !rule.enabled) return getString(R.string.switch_off_text); 188 final String mode = computeZenModeCaption(getResources(), rule.zenMode); 189 String summary = getString(R.string.switch_on_text); 190 final ScheduleInfo schedule = ZenModeConfig.tryParseScheduleConditionId(rule.conditionId); 191 final EventInfo event = ZenModeConfig.tryParseEventConditionId(rule.conditionId); 192 if (schedule != null) { 193 summary = computeScheduleRuleSummary(schedule); 194 } else if (event != null) { 195 summary = computeEventRuleSummary(event); 196 } 197 return getString(R.string.zen_mode_rule_summary_combination, summary, mode); 198 } 199 computeScheduleRuleSummary(ScheduleInfo schedule)200 private String computeScheduleRuleSummary(ScheduleInfo schedule) { 201 final String days = computeContiguousDayRanges(schedule.days); 202 final String start = getTime(schedule.startHour, schedule.startMinute); 203 final String end = getTime(schedule.endHour, schedule.endMinute); 204 final String time = getString(R.string.summary_range_verbal_combination, start, end); 205 return getString(R.string.zen_mode_rule_summary_combination, days, time); 206 } 207 computeEventRuleSummary(EventInfo event)208 private String computeEventRuleSummary(EventInfo event) { 209 final String calendar = getString(R.string.zen_mode_event_rule_summary_calendar_template, 210 computeCalendarName(event)); 211 final String reply = getString(R.string.zen_mode_event_rule_summary_reply_template, 212 getString(computeReply(event))); 213 return getString(R.string.zen_mode_rule_summary_combination, calendar, reply); 214 } 215 computeCalendarName(EventInfo event)216 private String computeCalendarName(EventInfo event) { 217 return event.calendar != null ? event.calendar 218 : getString(R.string.zen_mode_event_rule_summary_any_calendar); 219 } 220 computeReply(EventInfo event)221 private int computeReply(EventInfo event) { 222 switch (event.reply) { 223 case EventInfo.REPLY_ANY_EXCEPT_NO: 224 return R.string.zen_mode_event_rule_reply_any_except_no; 225 case EventInfo.REPLY_YES: 226 return R.string.zen_mode_event_rule_reply_yes; 227 case EventInfo.REPLY_YES_OR_MAYBE: 228 return R.string.zen_mode_event_rule_reply_yes_or_maybe; 229 default: 230 throw new IllegalArgumentException("Bad reply: " + event.reply); 231 } 232 } 233 getTime(int hour, int minute)234 private String getTime(int hour, int minute) { 235 mCalendar.set(Calendar.HOUR_OF_DAY, hour); 236 mCalendar.set(Calendar.MINUTE, minute); 237 return DateFormat.getTimeFormat(mContext).format(mCalendar.getTime()); 238 } 239 computeContiguousDayRanges(int[] days)240 private String computeContiguousDayRanges(int[] days) { 241 final TreeSet<Integer> daySet = new TreeSet<>(); 242 for (int i = 0; days != null && i < days.length; i++) { 243 daySet.add(days[i]); 244 } 245 if (daySet.isEmpty()) { 246 return getString(R.string.zen_mode_schedule_rule_days_none); 247 } 248 final int N = ALL_DAYS.length; 249 if (daySet.size() == N) { 250 return getString(R.string.zen_mode_schedule_rule_days_all); 251 } 252 String rt = null; 253 for (int i = 0; i < N; i++) { 254 final int startDay = ALL_DAYS[i]; 255 final boolean active = daySet.contains(startDay); 256 if (!active) continue; 257 int end = 0; 258 while (daySet.contains(ALL_DAYS[(i + end + 1) % N])) { 259 end++; 260 } 261 if (!(i == 0 && daySet.contains(ALL_DAYS[N - 1]))) { 262 final String v = end == 0 ? dayString(startDay) 263 : getString(R.string.summary_range_symbol_combination, 264 dayString(startDay), 265 dayString(ALL_DAYS[(i + end) % N])); 266 rt = rt == null ? v : getString(R.string.summary_divider_text, rt, v); 267 } 268 i += end; 269 } 270 return rt; 271 } 272 dayString(int day)273 private String dayString(int day) { 274 mCalendar.set(Calendar.DAY_OF_WEEK, day); 275 return mDayFormat.format(mCalendar.getTime()); 276 } 277 getConditionProviderConfig()278 private static Config getConditionProviderConfig() { 279 final Config c = new Config(); 280 c.tag = TAG; 281 c.setting = Settings.Secure.ENABLED_CONDITION_PROVIDERS; 282 c.intentAction = ConditionProviderService.SERVICE_INTERFACE; 283 c.permission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE; 284 c.noun = "condition provider"; 285 return c; 286 } 287 computeZenModeCaption(Resources res, int zenMode)288 private static String computeZenModeCaption(Resources res, int zenMode) { 289 switch (zenMode) { 290 case Global.ZEN_MODE_ALARMS: 291 return res.getString(R.string.zen_mode_option_alarms); 292 case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: 293 return res.getString(R.string.zen_mode_option_important_interruptions); 294 case Global.ZEN_MODE_NO_INTERRUPTIONS: 295 return res.getString(R.string.zen_mode_option_no_interruptions); 296 default: 297 return null; 298 } 299 } 300 301 private final ServiceListing.Callback mServiceListingCallback = new ServiceListing.Callback() { 302 @Override 303 public void onServicesReloaded(List<ServiceInfo> services) { 304 for (ServiceInfo service : services) { 305 final RuleInfo ri = ZenModeExternalRuleSettings.getRuleInfo(service); 306 if (ri != null && ri.serviceComponent != null 307 && ri.settingsAction == ZenModeExternalRuleSettings.ACTION) { 308 if (!mServiceListing.isEnabled(ri.serviceComponent)) { 309 Log.i(TAG, "Enabling external condition provider: " + ri.serviceComponent); 310 mServiceListing.setEnabled(ri.serviceComponent, true); 311 } 312 } 313 } 314 } 315 }; 316 317 private static final Comparator<ZenRuleInfo> RULE_COMPARATOR = new Comparator<ZenRuleInfo>() { 318 @Override 319 public int compare(ZenRuleInfo lhs, ZenRuleInfo rhs) { 320 return key(lhs).compareTo(key(rhs)); 321 } 322 323 private String key(ZenRuleInfo zri) { 324 final ZenRule rule = zri.rule; 325 final int type = ZenModeConfig.isValidScheduleConditionId(rule.conditionId) ? 1 326 : ZenModeConfig.isValidEventConditionId(rule.conditionId) ? 2 327 : 3; 328 return type + rule.name; 329 } 330 }; 331 332 private static class ZenRuleInfo { 333 String id; 334 ZenRule rule; 335 } 336 337 } 338