1 /* 2 * Copyright (C) 2024 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.modes; 18 19 import android.app.Flags; 20 import android.content.Context; 21 import android.content.pm.PackageManager; 22 import android.database.Cursor; 23 import android.os.UserHandle; 24 import android.os.UserManager; 25 import android.provider.CalendarContract; 26 import android.service.notification.SystemZenRules; 27 import android.service.notification.ZenModeConfig; 28 29 import androidx.annotation.NonNull; 30 import androidx.preference.DropDownPreference; 31 import androidx.preference.Preference; 32 import androidx.preference.PreferenceCategory; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.settings.R; 36 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.Collections; 40 import java.util.Comparator; 41 import java.util.List; 42 import java.util.Objects; 43 import java.util.function.Function; 44 45 public class ZenModeSetCalendarPreferenceController extends AbstractZenModePreferenceController { 46 @VisibleForTesting 47 protected static final String KEY_CALENDAR = "calendar"; 48 @VisibleForTesting 49 protected static final String KEY_REPLY = "reply"; 50 51 private DropDownPreference mCalendar; 52 private DropDownPreference mReply; 53 54 private ZenModeConfig.EventInfo mEvent; 55 ZenModeSetCalendarPreferenceController(Context context, String key, ZenModesBackend backend)56 public ZenModeSetCalendarPreferenceController(Context context, String key, 57 ZenModesBackend backend) { 58 super(context, key, backend); 59 } 60 61 @Override updateState(Preference preference, @NonNull ZenMode zenMode)62 public void updateState(Preference preference, @NonNull ZenMode zenMode) { 63 PreferenceCategory cat = (PreferenceCategory) preference; 64 65 // Refresh our understanding of local preferences 66 mCalendar = cat.findPreference(KEY_CALENDAR); 67 mReply = cat.findPreference(KEY_REPLY); 68 69 if (mCalendar == null || mReply == null) { 70 return; 71 } 72 73 mCalendar.setOnPreferenceChangeListener(mCalendarChangeListener); 74 75 mReply.setEntries(new CharSequence[] { 76 mContext.getString(R.string.zen_mode_event_rule_reply_any_except_no), 77 mContext.getString(R.string.zen_mode_event_rule_reply_yes_or_maybe), 78 mContext.getString(R.string.zen_mode_event_rule_reply_yes), 79 }); 80 mReply.setEntryValues(new CharSequence[] { 81 Integer.toString(ZenModeConfig.EventInfo.REPLY_ANY_EXCEPT_NO), 82 Integer.toString(ZenModeConfig.EventInfo.REPLY_YES_OR_MAYBE), 83 Integer.toString(ZenModeConfig.EventInfo.REPLY_YES), 84 }); 85 mReply.setOnPreferenceChangeListener(mReplyChangeListener); 86 87 // Parse the zen mode's condition to update our EventInfo object. 88 mEvent = ZenModeConfig.tryParseEventConditionId(zenMode.getRule().getConditionId()); 89 if (mEvent != null) { 90 reloadCalendar(); 91 updatePrefValues(); 92 } 93 } 94 reloadCalendar()95 private void reloadCalendar() { 96 List<CalendarInfo> calendars = getCalendars(mContext); 97 ArrayList<CharSequence> entries = new ArrayList<>(); 98 ArrayList<CharSequence> values = new ArrayList<>(); 99 entries.add(mContext.getString(R.string.zen_mode_event_rule_calendar_any)); 100 values.add(key(0, null, "")); 101 final String eventCalendar = mEvent != null ? mEvent.calName : null; 102 for (CalendarInfo calendar : calendars) { 103 entries.add(calendar.name); 104 values.add(key(calendar)); 105 if (eventCalendar != null && (mEvent.calendarId == null 106 && eventCalendar.equals(calendar.name))) { 107 mEvent.calendarId = calendar.calendarId; 108 } 109 } 110 111 CharSequence[] entriesArr = entries.toArray(new CharSequence[entries.size()]); 112 CharSequence[] valuesArr = values.toArray(new CharSequence[values.size()]); 113 if (!Arrays.equals(mCalendar.getEntries(), entriesArr)) { 114 mCalendar.setEntries(entriesArr); 115 } 116 117 if (!Arrays.equals(mCalendar.getEntryValues(), valuesArr)) { 118 mCalendar.setEntryValues(valuesArr); 119 } 120 } 121 122 @VisibleForTesting updateEventMode(ZenModeConfig.EventInfo event)123 protected Function<ZenMode, ZenMode> updateEventMode(ZenModeConfig.EventInfo event) { 124 return (zenMode) -> { 125 zenMode.getRule().setConditionId(ZenModeConfig.toEventConditionId(event)); 126 if (Flags.modesApi() && Flags.modesUi()) { 127 zenMode.getRule().setTriggerDescription( 128 SystemZenRules.getTriggerDescriptionForScheduleEvent(mContext, event)); 129 } 130 return zenMode; 131 }; 132 } 133 134 Preference.OnPreferenceChangeListener mCalendarChangeListener = 135 new Preference.OnPreferenceChangeListener() { 136 @Override 137 public boolean onPreferenceChange(Preference preference, Object newValue) { 138 final String calendarKey = (String) newValue; 139 if (calendarKey.equals(key(mEvent))) return false; 140 String[] key = calendarKey.split(":", 3); 141 mEvent.userId = Integer.parseInt(key[0]); 142 mEvent.calendarId = key[1].equals("") ? null : Long.parseLong(key[1]); 143 mEvent.calName = key[2].equals("") ? null : key[2]; 144 saveMode(updateEventMode(mEvent)); 145 return true; 146 } 147 }; 148 149 Preference.OnPreferenceChangeListener mReplyChangeListener = 150 new Preference.OnPreferenceChangeListener() { 151 @Override 152 public boolean onPreferenceChange(Preference preference, Object newValue) { 153 final int reply = Integer.parseInt((String) newValue); 154 if (reply == mEvent.reply) return false; 155 mEvent.reply = reply; 156 saveMode(updateEventMode(mEvent)); 157 return true; 158 } 159 }; 160 updatePrefValues()161 private void updatePrefValues() { 162 if (!Objects.equals(mCalendar.getValue(), key(mEvent))) { 163 mCalendar.setValue(key(mEvent)); 164 } 165 if (!Objects.equals(mReply.getValue(), Integer.toString(mEvent.reply))) { 166 mReply.setValue(Integer.toString(mEvent.reply)); 167 } 168 } 169 getCalendars(Context context)170 private List<CalendarInfo> getCalendars(Context context) { 171 final List<CalendarInfo> calendars = new ArrayList<>(); 172 for (UserHandle user : UserManager.get(context).getUserProfiles()) { 173 final Context userContext = getContextForUser(context, user); 174 if (userContext != null) { 175 addCalendars(userContext, calendars); 176 } 177 } 178 Collections.sort(calendars, CALENDAR_NAME); 179 return calendars; 180 } 181 getContextForUser(Context context, UserHandle user)182 private static Context getContextForUser(Context context, UserHandle user) { 183 try { 184 return context.createPackageContextAsUser(context.getPackageName(), 0, user); 185 } catch (PackageManager.NameNotFoundException e) { 186 return null; 187 } 188 } 189 addCalendars(Context context, List<CalendarInfo> outCalendars)190 private void addCalendars(Context context, List<CalendarInfo> outCalendars) { 191 final String[] projection = 192 {CalendarContract.Calendars._ID, CalendarContract.Calendars.CALENDAR_DISPLAY_NAME}; 193 final String selection = CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL + " >= " 194 + CalendarContract.Calendars.CAL_ACCESS_CONTRIBUTOR 195 + " AND " + CalendarContract.Calendars.SYNC_EVENTS + " = 1"; 196 Cursor cursor = null; 197 try { 198 cursor = context.getContentResolver().query(CalendarContract.Calendars.CONTENT_URI, 199 projection, selection, null, null); 200 if (cursor == null) { 201 return; 202 } 203 while (cursor.moveToNext()) { 204 addCalendar(cursor.getLong(0), cursor.getString(1), 205 context.getUserId(), outCalendars); 206 } 207 } finally { 208 if (cursor != null) { 209 cursor.close(); 210 } 211 } 212 } 213 214 @VisibleForTesting addCalendar(long calendarId, String calName, int userId, List<CalendarInfo> outCalendars)215 protected static void addCalendar(long calendarId, String calName, int userId, 216 List<CalendarInfo> outCalendars) { 217 final CalendarInfo ci = new CalendarInfo(); 218 ci.calendarId = calendarId; 219 ci.name = calName; 220 ci.userId = userId; 221 if (!outCalendars.contains(ci)) { 222 outCalendars.add(ci); 223 } 224 } 225 key(CalendarInfo calendar)226 private static String key(CalendarInfo calendar) { 227 return key(calendar.userId, calendar.calendarId, calendar.name); 228 } 229 key(ZenModeConfig.EventInfo event)230 private static String key(ZenModeConfig.EventInfo event) { 231 return key(event.userId, event.calendarId, event.calName); 232 } 233 234 @VisibleForTesting key(int userId, Long calendarId, String displayName)235 protected static String key(int userId, Long calendarId, String displayName) { 236 return ZenModeConfig.EventInfo.resolveUserId(userId) + ":" 237 + (calendarId == null ? "" : calendarId) 238 + ":" + (displayName == null ? "" : displayName); 239 } 240 241 @VisibleForTesting 242 protected static final Comparator<CalendarInfo> CALENDAR_NAME = Comparator.comparing( 243 lhs -> lhs.name); 244 245 public static class CalendarInfo { 246 public String name; 247 public int userId; 248 public Long calendarId; 249 250 @Override equals(Object o)251 public boolean equals(Object o) { 252 if (!(o instanceof CalendarInfo)) return false; 253 if (o == this) return true; 254 final CalendarInfo other = (CalendarInfo) o; 255 return Objects.equals(other.name, name) 256 && Objects.equals(other.calendarId, calendarId); 257 } 258 259 @Override hashCode()260 public int hashCode() { 261 return Objects.hash(name, calendarId); 262 } 263 } 264 } 265