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.annotation.Nullable; 20 import android.app.ActivityManager; 21 import android.app.AutomaticZenRule; 22 import android.app.INotificationManager; 23 import android.app.NotificationManager; 24 import android.content.Context; 25 import android.content.pm.ParceledListSlice; 26 import android.database.Cursor; 27 import android.net.Uri; 28 import android.os.ServiceManager; 29 import android.provider.ContactsContract; 30 import android.provider.Settings; 31 import android.service.notification.Condition; 32 import android.service.notification.ConversationChannelWrapper; 33 import android.service.notification.SystemZenRules; 34 import android.service.notification.ZenAdapters; 35 import android.service.notification.ZenModeConfig; 36 import android.util.Log; 37 38 import androidx.annotation.VisibleForTesting; 39 40 import com.android.settings.R; 41 42 import java.time.Duration; 43 import java.util.ArrayList; 44 import java.util.List; 45 import java.util.Map; 46 47 /** 48 * Class used for Settings-NMS interactions related to Mode management. 49 * 50 * <p>This class converts {@link AutomaticZenRule} instances, as well as the manual zen mode, 51 * into the unified {@link ZenMode} format. 52 */ 53 class ZenModesBackend { 54 55 private static final String TAG = "ZenModeBackend"; 56 57 @Nullable // Until first usage 58 private static ZenModesBackend sInstance; 59 60 private final NotificationManager mNotificationManager; 61 static INotificationManager sINM = INotificationManager.Stub.asInterface( 62 ServiceManager.getService(Context.NOTIFICATION_SERVICE)); 63 64 private final Context mContext; 65 getInstance(Context context)66 static ZenModesBackend getInstance(Context context) { 67 if (sInstance == null) { 68 sInstance = new ZenModesBackend(context.getApplicationContext()); 69 } 70 return sInstance; 71 } 72 ZenModesBackend(Context context)73 ZenModesBackend(Context context) { 74 mContext = context; 75 mNotificationManager = context.getSystemService(NotificationManager.class); 76 } 77 getModes()78 List<ZenMode> getModes() { 79 ArrayList<ZenMode> modes = new ArrayList<>(); 80 ZenModeConfig currentConfig = mNotificationManager.getZenModeConfig(); 81 modes.add(getManualDndMode(currentConfig)); 82 83 Map<String, AutomaticZenRule> zenRules = mNotificationManager.getAutomaticZenRules(); 84 for (Map.Entry<String, AutomaticZenRule> zenRuleEntry : zenRules.entrySet()) { 85 String ruleId = zenRuleEntry.getKey(); 86 modes.add(new ZenMode(ruleId, zenRuleEntry.getValue(), 87 isRuleActive(ruleId, currentConfig))); 88 } 89 90 modes.sort((l, r) -> { 91 if (l.isManualDnd()) { 92 return -1; 93 } else if (r.isManualDnd()) { 94 return 1; 95 } 96 return l.getRule().getName().compareTo(r.getRule().getName()); 97 }); 98 99 return modes; 100 } 101 102 @Nullable getMode(String id)103 ZenMode getMode(String id) { 104 ZenModeConfig currentConfig = mNotificationManager.getZenModeConfig(); 105 if (ZenMode.MANUAL_DND_MODE_ID.equals(id)) { 106 return getManualDndMode(currentConfig); 107 } else { 108 AutomaticZenRule rule = mNotificationManager.getAutomaticZenRule(id); 109 if (rule == null) { 110 return null; 111 } 112 return new ZenMode(id, rule, isRuleActive(id, currentConfig)); 113 } 114 } 115 getConversations(boolean onlyImportant)116 public ParceledListSlice<ConversationChannelWrapper> getConversations(boolean onlyImportant) { 117 try { 118 return sINM.getConversations(onlyImportant); 119 } catch (Exception e) { 120 Log.w(TAG, "Error calling NoMan", e); 121 return ParceledListSlice.emptyList(); 122 } 123 } 124 getStarredContacts()125 public List<String> getStarredContacts() { 126 Cursor cursor = null; 127 try { 128 cursor = queryStarredContactsData(); 129 return getStarredContacts(cursor); 130 } finally { 131 if (cursor != null) { 132 cursor.close(); 133 } 134 } 135 } 136 137 @VisibleForTesting getStarredContacts(Cursor cursor)138 List<String> getStarredContacts(Cursor cursor) { 139 List<String> starredContacts = new ArrayList<>(); 140 if (cursor != null && cursor.moveToFirst()) { 141 do { 142 String contact = cursor.getString(0); 143 starredContacts.add(contact != null ? contact : 144 mContext.getString(R.string.zen_mode_starred_contacts_empty_name)); 145 146 } while (cursor.moveToNext()); 147 } 148 return starredContacts; 149 } 150 queryStarredContactsData()151 private Cursor queryStarredContactsData() { 152 return mContext.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, 153 new String[]{ContactsContract.Contacts.DISPLAY_NAME_PRIMARY}, 154 ContactsContract.Data.STARRED + "=1", null, 155 ContactsContract.Data.TIMES_CONTACTED); 156 } 157 queryAllContactsData()158 Cursor queryAllContactsData() { 159 return mContext.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, 160 new String[]{ContactsContract.Contacts.DISPLAY_NAME_PRIMARY}, 161 null, null, null); 162 } 163 getManualDndMode(ZenModeConfig config)164 private ZenMode getManualDndMode(ZenModeConfig config) { 165 ZenModeConfig.ZenRule manualRule = config.manualRule; 166 // TODO: b/333682392 - Replace with final strings for name & trigger description 167 AutomaticZenRule manualDndRule = new AutomaticZenRule.Builder( 168 mContext.getString(R.string.zen_mode_settings_title), manualRule.conditionId) 169 .setType(manualRule.type) 170 .setZenPolicy(manualRule.zenPolicy) 171 .setDeviceEffects(manualRule.zenDeviceEffects) 172 .setManualInvocationAllowed(manualRule.allowManualInvocation) 173 .setConfigurationActivity(null) // No further settings 174 .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY) 175 .build(); 176 177 return ZenMode.manualDndMode(manualDndRule, config != null && config.isManualActive()); 178 } 179 isRuleActive(String id, ZenModeConfig config)180 private static boolean isRuleActive(String id, ZenModeConfig config) { 181 if (config == null) { 182 // shouldn't happen if the config is coming from NM, but be safe 183 return false; 184 } 185 ZenModeConfig.ZenRule configRule = config.automaticRules.get(id); 186 return configRule != null && configRule.isAutomaticActive(); 187 } 188 updateMode(ZenMode mode)189 void updateMode(ZenMode mode) { 190 if (mode.isManualDnd()) { 191 try { 192 NotificationManager.Policy dndPolicy = 193 new ZenModeConfig().toNotificationPolicy(mode.getPolicy()); 194 mNotificationManager.setNotificationPolicy(dndPolicy, /* fromUser= */ true); 195 196 mNotificationManager.setManualZenRuleDeviceEffects( 197 mode.getRule().getDeviceEffects()); 198 } catch (Exception e) { 199 Log.w(TAG, "Error updating manual mode", e); 200 } 201 } else { 202 mNotificationManager.updateAutomaticZenRule(mode.getId(), mode.getRule(), 203 /* fromUser= */ true); 204 } 205 } 206 activateMode(ZenMode mode, @Nullable Duration forDuration)207 void activateMode(ZenMode mode, @Nullable Duration forDuration) { 208 if (mode.isManualDnd()) { 209 Uri durationConditionId = null; 210 if (forDuration != null) { 211 durationConditionId = ZenModeConfig.toTimeCondition(mContext, 212 (int) forDuration.toMinutes(), ActivityManager.getCurrentUser(), true).id; 213 } 214 mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, 215 durationConditionId, TAG, /* fromUser= */ true); 216 217 } else { 218 if (forDuration != null) { 219 throw new IllegalArgumentException( 220 "Only the manual DND mode can be activated for a specific duration"); 221 } 222 mNotificationManager.setAutomaticZenRuleState(mode.getId(), 223 new Condition(mode.getRule().getConditionId(), "", Condition.STATE_TRUE, 224 Condition.SOURCE_USER_ACTION)); 225 } 226 } 227 deactivateMode(ZenMode mode)228 void deactivateMode(ZenMode mode) { 229 if (mode.isManualDnd()) { 230 // When calling with fromUser=true this will not snooze other modes. 231 mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_OFF, null, TAG, 232 /* fromUser= */ true); 233 } else { 234 // TODO: b/333527800 - This should (potentially) snooze the rule if it was active. 235 mNotificationManager.setAutomaticZenRuleState(mode.getId(), 236 new Condition(mode.getRule().getConditionId(), "", Condition.STATE_FALSE, 237 Condition.SOURCE_USER_ACTION)); 238 } 239 } 240 removeMode(ZenMode mode)241 void removeMode(ZenMode mode) { 242 if (!mode.canBeDeleted()) { 243 throw new IllegalArgumentException("Mode " + mode + " cannot be deleted!"); 244 } 245 mNotificationManager.removeAutomaticZenRule(mode.getId(), /* fromUser= */ true); 246 } 247 248 /** 249 * Creates a new custom mode with the provided {@code name}. The mode will be "manual" (i.e. 250 * not have a schedule), this can be later updated by the user in the mode settings page. 251 * 252 * @return the created mode. Only {@code null} if creation failed due to an internal error 253 */ 254 @Nullable addCustomMode(String name)255 ZenMode addCustomMode(String name) { 256 ZenModeConfig.ScheduleInfo schedule = new ZenModeConfig.ScheduleInfo(); 257 schedule.days = ZenModeConfig.ALL_DAYS; 258 schedule.startHour = 22; 259 schedule.endHour = 7; 260 261 // TODO: b/326442408 - Create as "manual" (i.e. no trigger) instead of schedule-time. 262 AutomaticZenRule rule = new AutomaticZenRule.Builder(name, 263 ZenModeConfig.toScheduleConditionId(schedule)) 264 .setPackage(ZenModeConfig.getScheduleConditionProvider().getPackageName()) 265 .setType(AutomaticZenRule.TYPE_SCHEDULE_CALENDAR) 266 .setOwner(ZenModeConfig.getScheduleConditionProvider()) 267 .setTriggerDescription(SystemZenRules.getTriggerDescriptionForScheduleTime( 268 mContext, schedule)) 269 .setManualInvocationAllowed(true) 270 .build(); 271 272 String ruleId = mNotificationManager.addAutomaticZenRule(rule); 273 return getMode(ruleId); 274 } 275 } 276