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 17package com.android.server.notification; 18 19import android.app.AlarmManager; 20import android.app.PendingIntent; 21import android.content.BroadcastReceiver; 22import android.content.ComponentName; 23import android.content.Context; 24import android.content.Intent; 25import android.content.IntentFilter; 26import android.net.Uri; 27import android.service.notification.Condition; 28import android.service.notification.IConditionProvider; 29import android.service.notification.ZenModeConfig; 30import android.service.notification.ZenModeConfig.ScheduleInfo; 31import android.util.ArraySet; 32import android.util.Log; 33import android.util.Slog; 34 35import com.android.server.notification.NotificationManagerService.DumpFilter; 36 37import java.io.PrintWriter; 38import java.util.TimeZone; 39 40/** 41 * Built-in zen condition provider for daily scheduled time-based conditions. 42 */ 43public class ScheduleConditionProvider extends SystemConditionProviderService { 44 private static final String TAG = "ConditionProviders.SCP"; 45 private static final boolean DEBUG = Log.isLoggable("ConditionProviders", Log.DEBUG); 46 47 public static final ComponentName COMPONENT = 48 new ComponentName("android", ScheduleConditionProvider.class.getName()); 49 private static final String NOT_SHOWN = "..."; 50 private static final String SIMPLE_NAME = ScheduleConditionProvider.class.getSimpleName(); 51 private static final String ACTION_EVALUATE = SIMPLE_NAME + ".EVALUATE"; 52 private static final int REQUEST_CODE_EVALUATE = 1; 53 private static final String EXTRA_TIME = "time"; 54 55 private final Context mContext = this; 56 private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>(); 57 58 private boolean mConnected; 59 private boolean mRegistered; 60 private long mNextAlarmTime; 61 62 public ScheduleConditionProvider() { 63 if (DEBUG) Slog.d(TAG, "new " + SIMPLE_NAME + "()"); 64 } 65 66 @Override 67 public ComponentName getComponent() { 68 return COMPONENT; 69 } 70 71 @Override 72 public boolean isValidConditionId(Uri id) { 73 return ZenModeConfig.isValidScheduleConditionId(id); 74 } 75 76 @Override 77 public void dump(PrintWriter pw, DumpFilter filter) { 78 pw.print(" "); pw.print(SIMPLE_NAME); pw.println(":"); 79 pw.print(" mConnected="); pw.println(mConnected); 80 pw.print(" mRegistered="); pw.println(mRegistered); 81 pw.println(" mSubscriptions="); 82 final long now = System.currentTimeMillis(); 83 for (Uri conditionId : mSubscriptions) { 84 pw.print(" "); 85 pw.print(meetsSchedule(conditionId, now) ? "* " : " "); 86 pw.println(conditionId); 87 } 88 dumpUpcomingTime(pw, "mNextAlarmTime", mNextAlarmTime, now); 89 } 90 91 @Override 92 public void onConnected() { 93 if (DEBUG) Slog.d(TAG, "onConnected"); 94 mConnected = true; 95 } 96 97 @Override 98 public void onBootComplete() { 99 // noop 100 } 101 102 @Override 103 public void onDestroy() { 104 super.onDestroy(); 105 if (DEBUG) Slog.d(TAG, "onDestroy"); 106 mConnected = false; 107 } 108 109 @Override 110 public void onRequestConditions(int relevance) { 111 if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance); 112 // does not advertise conditions 113 } 114 115 @Override 116 public void onSubscribe(Uri conditionId) { 117 if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId); 118 if (!ZenModeConfig.isValidScheduleConditionId(conditionId)) { 119 notifyCondition(conditionId, Condition.STATE_FALSE, "badCondition"); 120 return; 121 } 122 mSubscriptions.add(conditionId); 123 evaluateSubscriptions(); 124 } 125 126 @Override 127 public void onUnsubscribe(Uri conditionId) { 128 if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId); 129 mSubscriptions.remove(conditionId); 130 evaluateSubscriptions(); 131 } 132 133 @Override 134 public void attachBase(Context base) { 135 attachBaseContext(base); 136 } 137 138 @Override 139 public IConditionProvider asInterface() { 140 return (IConditionProvider) onBind(null); 141 } 142 143 private void evaluateSubscriptions() { 144 setRegistered(!mSubscriptions.isEmpty()); 145 final long now = System.currentTimeMillis(); 146 mNextAlarmTime = 0; 147 for (Uri conditionId : mSubscriptions) { 148 final ScheduleCalendar cal = toScheduleCalendar(conditionId); 149 if (cal != null && cal.isInSchedule(now)) { 150 notifyCondition(conditionId, Condition.STATE_TRUE, "meetsSchedule"); 151 } else { 152 notifyCondition(conditionId, Condition.STATE_FALSE, "!meetsSchedule"); 153 } 154 if (cal != null) { 155 final long nextChangeTime = cal.getNextChangeTime(now); 156 if (nextChangeTime > 0 && nextChangeTime > now) { 157 if (mNextAlarmTime == 0 || nextChangeTime < mNextAlarmTime) { 158 mNextAlarmTime = nextChangeTime; 159 } 160 } 161 } 162 } 163 updateAlarm(now, mNextAlarmTime); 164 } 165 166 private void updateAlarm(long now, long time) { 167 final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); 168 final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 169 REQUEST_CODE_EVALUATE, 170 new Intent(ACTION_EVALUATE) 171 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) 172 .putExtra(EXTRA_TIME, time), 173 PendingIntent.FLAG_UPDATE_CURRENT); 174 alarms.cancel(pendingIntent); 175 if (time > now) { 176 if (DEBUG) Slog.d(TAG, String.format("Scheduling evaluate for %s, in %s, now=%s", 177 ts(time), formatDuration(time - now), ts(now))); 178 alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); 179 } else { 180 if (DEBUG) Slog.d(TAG, "Not scheduling evaluate"); 181 } 182 } 183 184 private static boolean meetsSchedule(Uri conditionId, long time) { 185 final ScheduleCalendar cal = toScheduleCalendar(conditionId); 186 return cal != null && cal.isInSchedule(time); 187 } 188 189 private static ScheduleCalendar toScheduleCalendar(Uri conditionId) { 190 final ScheduleInfo schedule = ZenModeConfig.tryParseScheduleConditionId(conditionId); 191 if (schedule == null || schedule.days == null || schedule.days.length == 0) return null; 192 final ScheduleCalendar sc = new ScheduleCalendar(); 193 sc.setSchedule(schedule); 194 sc.setTimeZone(TimeZone.getDefault()); 195 return sc; 196 } 197 198 private void setRegistered(boolean registered) { 199 if (mRegistered == registered) return; 200 if (DEBUG) Slog.d(TAG, "setRegistered " + registered); 201 mRegistered = registered; 202 if (mRegistered) { 203 final IntentFilter filter = new IntentFilter(); 204 filter.addAction(Intent.ACTION_TIME_CHANGED); 205 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); 206 filter.addAction(ACTION_EVALUATE); 207 registerReceiver(mReceiver, filter); 208 } else { 209 unregisterReceiver(mReceiver); 210 } 211 } 212 213 private void notifyCondition(Uri conditionId, int state, String reason) { 214 if (DEBUG) Slog.d(TAG, "notifyCondition " + conditionId 215 + " " + Condition.stateToString(state) 216 + " reason=" + reason); 217 notifyCondition(createCondition(conditionId, state)); 218 } 219 220 private Condition createCondition(Uri id, int state) { 221 final String summary = NOT_SHOWN; 222 final String line1 = NOT_SHOWN; 223 final String line2 = NOT_SHOWN; 224 return new Condition(id, summary, line1, line2, 0, state, Condition.FLAG_RELEVANT_ALWAYS); 225 } 226 227 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 228 @Override 229 public void onReceive(Context context, Intent intent) { 230 if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction()); 231 evaluateSubscriptions(); 232 } 233 }; 234 235} 236