ScheduleConditionProvider.java revision a62496d8f7cb9048331451af07466b1edc568c7d
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 onSubscribe(Uri conditionId) { 111 if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId); 112 if (!ZenModeConfig.isValidScheduleConditionId(conditionId)) { 113 notifyCondition(conditionId, Condition.STATE_FALSE, "badCondition"); 114 return; 115 } 116 mSubscriptions.add(conditionId); 117 evaluateSubscriptions(); 118 } 119 120 @Override 121 public void onUnsubscribe(Uri conditionId) { 122 if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId); 123 mSubscriptions.remove(conditionId); 124 evaluateSubscriptions(); 125 } 126 127 @Override 128 public void attachBase(Context base) { 129 attachBaseContext(base); 130 } 131 132 @Override 133 public IConditionProvider asInterface() { 134 return (IConditionProvider) onBind(null); 135 } 136 137 private void evaluateSubscriptions() { 138 setRegistered(!mSubscriptions.isEmpty()); 139 final long now = System.currentTimeMillis(); 140 mNextAlarmTime = 0; 141 for (Uri conditionId : mSubscriptions) { 142 final ScheduleCalendar cal = toScheduleCalendar(conditionId); 143 if (cal != null && cal.isInSchedule(now)) { 144 notifyCondition(conditionId, Condition.STATE_TRUE, "meetsSchedule"); 145 } else { 146 notifyCondition(conditionId, Condition.STATE_FALSE, "!meetsSchedule"); 147 } 148 if (cal != null) { 149 final long nextChangeTime = cal.getNextChangeTime(now); 150 if (nextChangeTime > 0 && nextChangeTime > now) { 151 if (mNextAlarmTime == 0 || nextChangeTime < mNextAlarmTime) { 152 mNextAlarmTime = nextChangeTime; 153 } 154 } 155 } 156 } 157 updateAlarm(now, mNextAlarmTime); 158 } 159 160 private void updateAlarm(long now, long time) { 161 final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); 162 final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 163 REQUEST_CODE_EVALUATE, 164 new Intent(ACTION_EVALUATE) 165 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) 166 .putExtra(EXTRA_TIME, time), 167 PendingIntent.FLAG_UPDATE_CURRENT); 168 alarms.cancel(pendingIntent); 169 if (time > now) { 170 if (DEBUG) Slog.d(TAG, String.format("Scheduling evaluate for %s, in %s, now=%s", 171 ts(time), formatDuration(time - now), ts(now))); 172 alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); 173 } else { 174 if (DEBUG) Slog.d(TAG, "Not scheduling evaluate"); 175 } 176 } 177 178 private static boolean meetsSchedule(Uri conditionId, long time) { 179 final ScheduleCalendar cal = toScheduleCalendar(conditionId); 180 return cal != null && cal.isInSchedule(time); 181 } 182 183 private static ScheduleCalendar toScheduleCalendar(Uri conditionId) { 184 final ScheduleInfo schedule = ZenModeConfig.tryParseScheduleConditionId(conditionId); 185 if (schedule == null || schedule.days == null || schedule.days.length == 0) return null; 186 final ScheduleCalendar sc = new ScheduleCalendar(); 187 sc.setSchedule(schedule); 188 sc.setTimeZone(TimeZone.getDefault()); 189 return sc; 190 } 191 192 private void setRegistered(boolean registered) { 193 if (mRegistered == registered) return; 194 if (DEBUG) Slog.d(TAG, "setRegistered " + registered); 195 mRegistered = registered; 196 if (mRegistered) { 197 final IntentFilter filter = new IntentFilter(); 198 filter.addAction(Intent.ACTION_TIME_CHANGED); 199 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); 200 filter.addAction(ACTION_EVALUATE); 201 registerReceiver(mReceiver, filter); 202 } else { 203 unregisterReceiver(mReceiver); 204 } 205 } 206 207 private void notifyCondition(Uri conditionId, int state, String reason) { 208 if (DEBUG) Slog.d(TAG, "notifyCondition " + conditionId 209 + " " + Condition.stateToString(state) 210 + " reason=" + reason); 211 notifyCondition(createCondition(conditionId, state)); 212 } 213 214 private Condition createCondition(Uri id, int state) { 215 final String summary = NOT_SHOWN; 216 final String line1 = NOT_SHOWN; 217 final String line2 = NOT_SHOWN; 218 return new Condition(id, summary, line1, line2, 0, state, Condition.FLAG_RELEVANT_ALWAYS); 219 } 220 221 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 222 @Override 223 public void onReceive(Context context, Intent intent) { 224 if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction()); 225 evaluateSubscriptions(); 226 } 227 }; 228 229} 230