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