Loading services/core/java/com/android/server/notification/CalendarTracker.java 0 → 100644 +289 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.notification; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; import android.provider.BaseColumns; import android.provider.CalendarContract.Attendees; import android.provider.CalendarContract.Instances; import android.service.notification.ZenModeConfig.EventInfo; import android.util.Log; import java.io.PrintWriter; import java.util.Date; import java.util.Objects; public class CalendarTracker { private static final String TAG = "ConditionProviders.CT"; private static final boolean DEBUG = Log.isLoggable("ConditionProviders", Log.DEBUG); private static final boolean DEBUG_ATTENDEES = false; private static final int EVENT_CHECK_LOOKAHEAD = 24 * 60 * 60 * 1000; private static final String[] INSTANCE_PROJECTION = { Instances.BEGIN, Instances.END, Instances.TITLE, Instances.VISIBLE, Instances.EVENT_ID, Instances.OWNER_ACCOUNT, Instances.CALENDAR_ID, }; private static final String INSTANCE_ORDER_BY = Instances.BEGIN + " ASC"; private static final String[] ATTENDEE_PROJECTION = { Attendees.EVENT_ID, Attendees.ATTENDEE_EMAIL, Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_TYPE, }; private static final String ATTENDEE_SELECTION = Attendees.EVENT_ID + " = ? AND " + Attendees.ATTENDEE_EMAIL + " = ?"; private final Context mContext; private Callback mCallback; private boolean mRegistered; public CalendarTracker(Context context) { mContext = context; } public void setCallback(Callback callback) { if (mCallback == callback) return; mCallback = callback; setRegistered(mCallback != null); } public void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("mCallback="); pw.println(mCallback); pw.print(prefix); pw.print("mRegistered="); pw.println(mRegistered); } public void dumpContent(Uri uri) { Log.d(TAG, "dumpContent: " + uri); final Cursor cursor = mContext.getContentResolver().query(uri, null, null, null, null); try { int r = 0; while (cursor.moveToNext()) { Log.d(TAG, "Row " + (++r) + ": id=" + cursor.getInt(cursor.getColumnIndex(BaseColumns._ID))); for (int i = 0; i < cursor.getColumnCount(); i++) { final String name = cursor.getColumnName(i); final int type = cursor.getType(i); Object o = null; String typeName = null; switch (type) { case Cursor.FIELD_TYPE_INTEGER: o = cursor.getLong(i); typeName = "INTEGER"; break; case Cursor.FIELD_TYPE_STRING: o = cursor.getString(i); typeName = "STRING"; break; case Cursor.FIELD_TYPE_NULL: o = null; typeName = "NULL"; break; default: throw new UnsupportedOperationException("type: " + type); } if (name.equals(BaseColumns._ID) || name.toLowerCase().contains("sync") || o == null) { continue; } Log.d(TAG, " " + name + "(" + typeName + ")=" + o); } } Log.d(TAG, " " + uri + " " + r + " rows"); } finally { cursor.close(); } } public CheckEventResult checkEvent(EventInfo filter, long time) { final Uri.Builder uriBuilder = Instances.CONTENT_URI.buildUpon(); ContentUris.appendId(uriBuilder, time); ContentUris.appendId(uriBuilder, time + EVENT_CHECK_LOOKAHEAD); final Uri uri = uriBuilder.build(); final Cursor cursor = mContext.getContentResolver().query(uri, INSTANCE_PROJECTION, null, null, INSTANCE_ORDER_BY); final CheckEventResult result = new CheckEventResult(); result.recheckAt = time + EVENT_CHECK_LOOKAHEAD; try { while (cursor.moveToNext()) { final long begin = cursor.getLong(0); final long end = cursor.getLong(1); final String title = cursor.getString(2); final boolean visible = cursor.getInt(3) == 1; final int eventId = cursor.getInt(4); final String owner = cursor.getString(5); final long calendarId = cursor.getLong(6); if (DEBUG) Log.d(TAG, String.format("%s %s-%s v=%s eid=%s o=%s cid=%s", title, new Date(begin), new Date(end), visible, eventId, owner, calendarId)); final boolean meetsTime = time >= begin && time < end; final boolean meetsCalendar = visible && (filter.calendar == 0 || filter.calendar == calendarId); if (meetsCalendar) { if (DEBUG) Log.d(TAG, " MEETS CALENDAR"); final boolean meetsAttendee = meetsAttendee(filter, eventId, owner); if (meetsAttendee) { if (DEBUG) Log.d(TAG, " MEETS ATTENDEE"); if (meetsTime) { if (DEBUG) Log.d(TAG, " MEETS TIME"); result.inEvent = true; } if (begin > time && begin < result.recheckAt) { result.recheckAt = begin; } else if (end > time && end < result.recheckAt) { result.recheckAt = end; } } } } } finally { cursor.close(); } return result; } private boolean meetsAttendee(EventInfo filter, int eventId, String email) { String selection = ATTENDEE_SELECTION; String[] selectionArgs = { Integer.toString(eventId), email }; if (DEBUG_ATTENDEES) { selection = null; selectionArgs = null; } final Cursor cursor = mContext.getContentResolver().query(Attendees.CONTENT_URI, ATTENDEE_PROJECTION, selection, selectionArgs, null); try { if (cursor.getCount() == 0) { if (DEBUG) Log.d(TAG, "No attendees found"); return true; } boolean rt = false; while (cursor.moveToNext()) { final long rowEventId = cursor.getLong(0); final String rowEmail = cursor.getString(1); final int status = cursor.getInt(2); final int type = cursor.getInt(3); final boolean meetsReply = meetsReply(filter.reply, status); final boolean meetsAttendance = meetsAttendance(filter.attendance, type); if (DEBUG) Log.d(TAG, (DEBUG_ATTENDEES ? String.format( "rowEventId=%s, rowEmail=%s, ", rowEventId, rowEmail) : "") + String.format("status=%s, type=%s, meetsReply=%s, meetsAttendance=%s", attendeeStatusToString(status), attendeeTypeToString(type), meetsReply, meetsAttendance)); final boolean eventMeets = rowEventId == eventId && Objects.equals(rowEmail, email) && meetsReply && meetsAttendance; rt |= eventMeets; } return rt; } finally { cursor.close(); } } private void setRegistered(boolean registered) { if (mRegistered == registered) return; final ContentResolver cr = mContext.getContentResolver(); if (mRegistered) { cr.unregisterContentObserver(mObserver); } mRegistered = registered; if (mRegistered) { cr.registerContentObserver(Instances.CONTENT_URI, false, mObserver); } } private static String attendeeStatusToString(int status) { switch (status) { case Attendees.ATTENDEE_STATUS_NONE: return "ATTENDEE_STATUS_NONE"; case Attendees.ATTENDEE_STATUS_ACCEPTED: return "ATTENDEE_STATUS_ACCEPTED"; case Attendees.ATTENDEE_STATUS_DECLINED: return "ATTENDEE_STATUS_DECLINED"; case Attendees.ATTENDEE_STATUS_INVITED: return "ATTENDEE_STATUS_INVITED"; case Attendees.ATTENDEE_STATUS_TENTATIVE: return "ATTENDEE_STATUS_TENTATIVE"; default: return "ATTENDEE_STATUS_UNKNOWN_" + status; } } private static String attendeeTypeToString(int type) { switch (type) { case Attendees.TYPE_NONE: return "TYPE_NONE"; case Attendees.TYPE_REQUIRED: return "TYPE_REQUIRED"; case Attendees.TYPE_OPTIONAL: return "TYPE_OPTIONAL"; case Attendees.TYPE_RESOURCE: return "TYPE_RESOURCE"; default: return "TYPE_" + type; } } private static boolean meetsAttendance(int attendance, int attendeeType) { switch (attendance) { case EventInfo.ATTENDANCE_OPTIONAL: return attendeeType == Attendees.TYPE_OPTIONAL; case EventInfo.ATTENDANCE_REQUIRED: return attendeeType == Attendees.TYPE_REQUIRED; default: // EventInfo.ATTENDANCE_REQUIRED_OR_OPTIONAL return true; } } private static boolean meetsReply(int reply, int attendeeStatus) { switch (reply) { case EventInfo.REPLY_YES: return attendeeStatus == Attendees.ATTENDEE_STATUS_ACCEPTED; case EventInfo.REPLY_ANY_EXCEPT_NO: return attendeeStatus != Attendees.ATTENDEE_STATUS_DECLINED; default: // EventInfo.REPLY_ANY return true; } } private final ContentObserver mObserver = new ContentObserver(null) { @Override public void onChange(boolean selfChange, Uri u) { if (DEBUG) Log.d(TAG, "onChange selfChange=" + selfChange + " uri=" + u); mCallback.onChanged(); } @Override public void onChange(boolean selfChange) { if (DEBUG) Log.d(TAG, "onChange selfChange=" + selfChange); } }; public static class CheckEventResult { public boolean inEvent; public long recheckAt; } public interface Callback { void onChanged(); } } services/core/java/com/android/server/notification/ConditionProviders.java +3 −0 Original line number Diff line number Diff line Loading @@ -121,6 +121,9 @@ public class ConditionProviders extends ManagedServices { @Override public void onBootPhaseAppsCanStart() { super.onBootPhaseAppsCanStart(); for (int i = 0; i < mSystemConditionProviders.size(); i++) { mSystemConditionProviders.valueAt(i).onBootComplete(); } if (mCallback != null) { mCallback.onBootComplete(); } Loading services/core/java/com/android/server/notification/CountdownConditionProvider.java +7 −7 Original line number Diff line number Diff line Loading @@ -34,12 +34,11 @@ import android.util.Slog; import com.android.server.notification.NotificationManagerService.DumpFilter; import java.io.PrintWriter; import java.util.Date; /** Built-in zen condition provider for simple time-based conditions */ public class CountdownConditionProvider extends SystemConditionProviderService { private static final String TAG = "ConditionProviders"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final String TAG = "ConditionProviders.CCP"; private static final boolean DEBUG = Log.isLoggable("ConditionProviders", Log.DEBUG); public static final ComponentName COMPONENT = new ComponentName("android", CountdownConditionProvider.class.getName()); Loading Loading @@ -73,6 +72,11 @@ public class CountdownConditionProvider extends SystemConditionProviderService { attachBaseContext(base); } @Override public void onBootComplete() { // noop } @Override public IConditionProvider asInterface() { return (IConditionProvider) onBind(null); Loading Loading @@ -170,8 +174,4 @@ public class CountdownConditionProvider extends SystemConditionProviderService { ts(time), time - now, span, ts(now)); } private static String ts(long time) { return new Date(time) + " (" + time + ")"; } } services/core/java/com/android/server/notification/EventConditionProvider.java +106 −7 Original line number Diff line number Diff line Loading @@ -16,16 +16,23 @@ package com.android.server.notification; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.service.notification.Condition; import android.service.notification.IConditionProvider; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.EventInfo; import android.util.ArraySet; import android.util.Log; import android.util.Slog; import com.android.server.notification.CalendarTracker.CheckEventResult; import com.android.server.notification.NotificationManagerService.DumpFilter; import java.io.PrintWriter; Loading @@ -34,20 +41,27 @@ import java.io.PrintWriter; * Built-in zen condition provider for calendar event-based conditions. */ public class EventConditionProvider extends SystemConditionProviderService { private static final String TAG = "ConditionProviders"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final String TAG = "ConditionProviders.ECP"; private static final boolean DEBUG = Log.isLoggable("ConditionProviders", Log.DEBUG); public static final ComponentName COMPONENT = new ComponentName("android", EventConditionProvider.class.getName()); private static final String NOT_SHOWN = "..."; private static final String SIMPLE_NAME = EventConditionProvider.class.getSimpleName(); private static final String ACTION_EVALUATE = SIMPLE_NAME + ".EVALUATE"; private static final int REQUEST_CODE_EVALUATE = 1; private static final String EXTRA_TIME = "time"; private final Context mContext = this; private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>(); private final CalendarTracker mTracker = new CalendarTracker(mContext); private boolean mConnected; private boolean mRegistered; private boolean mBootComplete; // don't hammer the calendar provider until boot completes. public EventConditionProvider() { if (DEBUG) Slog.d(TAG, "new EventConditionProvider()"); if (DEBUG) Slog.d(TAG, "new " + SIMPLE_NAME + "()"); } @Override Loading @@ -62,14 +76,25 @@ public class EventConditionProvider extends SystemConditionProviderService { @Override public void dump(PrintWriter pw, DumpFilter filter) { pw.println(" EventConditionProvider:"); pw.print(" "); pw.print(SIMPLE_NAME); pw.println(":"); pw.print(" mConnected="); pw.println(mConnected); pw.print(" mRegistered="); pw.println(mRegistered); pw.print(" mBootComplete="); pw.println(mBootComplete); pw.println(" mSubscriptions="); for (Uri conditionId : mSubscriptions) { pw.print(" "); pw.println(conditionId); } pw.println(" mTracker="); mTracker.dump(" ", pw); } @Override public void onBootComplete() { if (DEBUG) Slog.d(TAG, "onBootComplete"); if (mBootComplete) return; mBootComplete = true; evaluateSubscriptions(); } @Override Loading Loading @@ -98,9 +123,10 @@ public class EventConditionProvider extends SystemConditionProviderService { notifyCondition(conditionId, Condition.STATE_FALSE, "badCondition"); return; } mSubscriptions.add(conditionId); if (mSubscriptions.add(conditionId)) { evaluateSubscriptions(); } } @Override public void onUnsubscribe(Uri conditionId) { Loading @@ -121,9 +147,52 @@ public class EventConditionProvider extends SystemConditionProviderService { } private void evaluateSubscriptions() { if (DEBUG) Log.d(TAG, "evaluateSubscriptions"); if (!mBootComplete) { if (DEBUG) Log.d(TAG, "Skipping evaluate before boot complete"); return; } final long now = System.currentTimeMillis(); mTracker.setCallback(mSubscriptions.isEmpty() ? null : mTrackerCallback); setRegistered(!mSubscriptions.isEmpty()); long reevaluateAt = 0; for (Uri conditionId : mSubscriptions) { notifyCondition(conditionId, Condition.STATE_FALSE, "notImplemented"); final EventInfo event = ZenModeConfig.tryParseEventConditionId(conditionId); if (event == null) { notifyCondition(conditionId, Condition.STATE_FALSE, "badConditionId"); continue; } final CheckEventResult result = mTracker.checkEvent(event, now); if (result.recheckAt != 0 && (reevaluateAt == 0 || result.recheckAt < reevaluateAt)) { reevaluateAt = result.recheckAt; } if (!result.inEvent) { notifyCondition(conditionId, Condition.STATE_FALSE, "!inEventNow"); continue; } notifyCondition(conditionId, Condition.STATE_TRUE, "inEventNow"); } updateAlarm(now, reevaluateAt); if (DEBUG) Log.d(TAG, "evaluateSubscriptions took " + (System.currentTimeMillis() - now)); } private void updateAlarm(long now, long time) { final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, REQUEST_CODE_EVALUATE, new Intent(ACTION_EVALUATE) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) .putExtra(EXTRA_TIME, time), PendingIntent.FLAG_UPDATE_CURRENT); alarms.cancel(pendingIntent); if (time == 0 || time < now) { if (DEBUG) Slog.d(TAG, "Not scheduling evaluate: " + (time == 0 ? "no time specified" : "specified time in the past")); return; } if (DEBUG) Slog.d(TAG, String.format("Scheduling evaluate for %s, in %s, now=%s", ts(time), formatDuration(time - now), ts(now))); alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); } private void notifyCondition(Uri conditionId, int state, String reason) { Loading @@ -139,4 +208,34 @@ public class EventConditionProvider extends SystemConditionProviderService { return new Condition(id, summary, line1, line2, 0, state, Condition.FLAG_RELEVANT_ALWAYS); } private void setRegistered(boolean registered) { if (mRegistered == registered) return; if (DEBUG) Slog.d(TAG, "setRegistered " + registered); mRegistered = registered; if (mRegistered) { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_TIME_CHANGED); filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); filter.addAction(ACTION_EVALUATE); registerReceiver(mReceiver, filter); } else { unregisterReceiver(mReceiver); } } private final CalendarTracker.Callback mTrackerCallback = new CalendarTracker.Callback() { @Override public void onChanged() { if (DEBUG) Log.d(TAG, "mTrackerCallback.onChanged"); evaluateSubscriptions(); } }; private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction()); evaluateSubscriptions(); } }; } services/core/java/com/android/server/notification/ScheduleConditionProvider.java +11 −17 Original line number Diff line number Diff line Loading @@ -31,25 +31,24 @@ import android.service.notification.ZenModeConfig.ScheduleInfo; import android.util.ArraySet; import android.util.Log; import android.util.Slog; import android.util.TimeUtils; import com.android.server.notification.NotificationManagerService.DumpFilter; import java.io.PrintWriter; import java.util.Date; import java.util.TimeZone; /** * Built-in zen condition provider for daily scheduled time-based conditions. */ public class ScheduleConditionProvider extends SystemConditionProviderService { private static final String TAG = "ConditionProviders"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final String TAG = "ConditionProviders.SCP"; private static final boolean DEBUG = Log.isLoggable("ConditionProviders", Log.DEBUG); public static final ComponentName COMPONENT = new ComponentName("android", ScheduleConditionProvider.class.getName()); private static final String NOT_SHOWN = "..."; private static final String ACTION_EVALUATE = TAG + ".EVALUATE"; private static final String SIMPLE_NAME = ScheduleConditionProvider.class.getSimpleName(); private static final String ACTION_EVALUATE = SIMPLE_NAME + ".EVALUATE"; private static final int REQUEST_CODE_EVALUATE = 1; private static final String EXTRA_TIME = "time"; Loading @@ -60,7 +59,7 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { private boolean mRegistered; public ScheduleConditionProvider() { if (DEBUG) Slog.d(TAG, "new ScheduleConditionProvider()"); if (DEBUG) Slog.d(TAG, "new " + SIMPLE_NAME + "()"); } @Override Loading @@ -75,7 +74,7 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { @Override public void dump(PrintWriter pw, DumpFilter filter) { pw.println(" ScheduleConditionProvider:"); pw.print(" "); pw.print(SIMPLE_NAME); pw.println(":"); pw.print(" mConnected="); pw.println(mConnected); pw.print(" mRegistered="); pw.println(mRegistered); pw.println(" mSubscriptions="); Loading @@ -93,6 +92,11 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { mConnected = true; } @Override public void onBootComplete() { // noop } @Override public void onDestroy() { super.onDestroy(); Loading Loading @@ -175,16 +179,6 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { } } private static String ts(long time) { return new Date(time) + " (" + time + ")"; } private static String formatDuration(long millis) { final StringBuilder sb = new StringBuilder(); TimeUtils.formatDuration(millis, sb); return sb.toString(); } private static boolean meetsSchedule(Uri conditionId, long time) { final ScheduleCalendar cal = toScheduleCalendar(conditionId); return cal != null && cal.isInSchedule(time); Loading Loading
services/core/java/com/android/server/notification/CalendarTracker.java 0 → 100644 +289 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.notification; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; import android.provider.BaseColumns; import android.provider.CalendarContract.Attendees; import android.provider.CalendarContract.Instances; import android.service.notification.ZenModeConfig.EventInfo; import android.util.Log; import java.io.PrintWriter; import java.util.Date; import java.util.Objects; public class CalendarTracker { private static final String TAG = "ConditionProviders.CT"; private static final boolean DEBUG = Log.isLoggable("ConditionProviders", Log.DEBUG); private static final boolean DEBUG_ATTENDEES = false; private static final int EVENT_CHECK_LOOKAHEAD = 24 * 60 * 60 * 1000; private static final String[] INSTANCE_PROJECTION = { Instances.BEGIN, Instances.END, Instances.TITLE, Instances.VISIBLE, Instances.EVENT_ID, Instances.OWNER_ACCOUNT, Instances.CALENDAR_ID, }; private static final String INSTANCE_ORDER_BY = Instances.BEGIN + " ASC"; private static final String[] ATTENDEE_PROJECTION = { Attendees.EVENT_ID, Attendees.ATTENDEE_EMAIL, Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_TYPE, }; private static final String ATTENDEE_SELECTION = Attendees.EVENT_ID + " = ? AND " + Attendees.ATTENDEE_EMAIL + " = ?"; private final Context mContext; private Callback mCallback; private boolean mRegistered; public CalendarTracker(Context context) { mContext = context; } public void setCallback(Callback callback) { if (mCallback == callback) return; mCallback = callback; setRegistered(mCallback != null); } public void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("mCallback="); pw.println(mCallback); pw.print(prefix); pw.print("mRegistered="); pw.println(mRegistered); } public void dumpContent(Uri uri) { Log.d(TAG, "dumpContent: " + uri); final Cursor cursor = mContext.getContentResolver().query(uri, null, null, null, null); try { int r = 0; while (cursor.moveToNext()) { Log.d(TAG, "Row " + (++r) + ": id=" + cursor.getInt(cursor.getColumnIndex(BaseColumns._ID))); for (int i = 0; i < cursor.getColumnCount(); i++) { final String name = cursor.getColumnName(i); final int type = cursor.getType(i); Object o = null; String typeName = null; switch (type) { case Cursor.FIELD_TYPE_INTEGER: o = cursor.getLong(i); typeName = "INTEGER"; break; case Cursor.FIELD_TYPE_STRING: o = cursor.getString(i); typeName = "STRING"; break; case Cursor.FIELD_TYPE_NULL: o = null; typeName = "NULL"; break; default: throw new UnsupportedOperationException("type: " + type); } if (name.equals(BaseColumns._ID) || name.toLowerCase().contains("sync") || o == null) { continue; } Log.d(TAG, " " + name + "(" + typeName + ")=" + o); } } Log.d(TAG, " " + uri + " " + r + " rows"); } finally { cursor.close(); } } public CheckEventResult checkEvent(EventInfo filter, long time) { final Uri.Builder uriBuilder = Instances.CONTENT_URI.buildUpon(); ContentUris.appendId(uriBuilder, time); ContentUris.appendId(uriBuilder, time + EVENT_CHECK_LOOKAHEAD); final Uri uri = uriBuilder.build(); final Cursor cursor = mContext.getContentResolver().query(uri, INSTANCE_PROJECTION, null, null, INSTANCE_ORDER_BY); final CheckEventResult result = new CheckEventResult(); result.recheckAt = time + EVENT_CHECK_LOOKAHEAD; try { while (cursor.moveToNext()) { final long begin = cursor.getLong(0); final long end = cursor.getLong(1); final String title = cursor.getString(2); final boolean visible = cursor.getInt(3) == 1; final int eventId = cursor.getInt(4); final String owner = cursor.getString(5); final long calendarId = cursor.getLong(6); if (DEBUG) Log.d(TAG, String.format("%s %s-%s v=%s eid=%s o=%s cid=%s", title, new Date(begin), new Date(end), visible, eventId, owner, calendarId)); final boolean meetsTime = time >= begin && time < end; final boolean meetsCalendar = visible && (filter.calendar == 0 || filter.calendar == calendarId); if (meetsCalendar) { if (DEBUG) Log.d(TAG, " MEETS CALENDAR"); final boolean meetsAttendee = meetsAttendee(filter, eventId, owner); if (meetsAttendee) { if (DEBUG) Log.d(TAG, " MEETS ATTENDEE"); if (meetsTime) { if (DEBUG) Log.d(TAG, " MEETS TIME"); result.inEvent = true; } if (begin > time && begin < result.recheckAt) { result.recheckAt = begin; } else if (end > time && end < result.recheckAt) { result.recheckAt = end; } } } } } finally { cursor.close(); } return result; } private boolean meetsAttendee(EventInfo filter, int eventId, String email) { String selection = ATTENDEE_SELECTION; String[] selectionArgs = { Integer.toString(eventId), email }; if (DEBUG_ATTENDEES) { selection = null; selectionArgs = null; } final Cursor cursor = mContext.getContentResolver().query(Attendees.CONTENT_URI, ATTENDEE_PROJECTION, selection, selectionArgs, null); try { if (cursor.getCount() == 0) { if (DEBUG) Log.d(TAG, "No attendees found"); return true; } boolean rt = false; while (cursor.moveToNext()) { final long rowEventId = cursor.getLong(0); final String rowEmail = cursor.getString(1); final int status = cursor.getInt(2); final int type = cursor.getInt(3); final boolean meetsReply = meetsReply(filter.reply, status); final boolean meetsAttendance = meetsAttendance(filter.attendance, type); if (DEBUG) Log.d(TAG, (DEBUG_ATTENDEES ? String.format( "rowEventId=%s, rowEmail=%s, ", rowEventId, rowEmail) : "") + String.format("status=%s, type=%s, meetsReply=%s, meetsAttendance=%s", attendeeStatusToString(status), attendeeTypeToString(type), meetsReply, meetsAttendance)); final boolean eventMeets = rowEventId == eventId && Objects.equals(rowEmail, email) && meetsReply && meetsAttendance; rt |= eventMeets; } return rt; } finally { cursor.close(); } } private void setRegistered(boolean registered) { if (mRegistered == registered) return; final ContentResolver cr = mContext.getContentResolver(); if (mRegistered) { cr.unregisterContentObserver(mObserver); } mRegistered = registered; if (mRegistered) { cr.registerContentObserver(Instances.CONTENT_URI, false, mObserver); } } private static String attendeeStatusToString(int status) { switch (status) { case Attendees.ATTENDEE_STATUS_NONE: return "ATTENDEE_STATUS_NONE"; case Attendees.ATTENDEE_STATUS_ACCEPTED: return "ATTENDEE_STATUS_ACCEPTED"; case Attendees.ATTENDEE_STATUS_DECLINED: return "ATTENDEE_STATUS_DECLINED"; case Attendees.ATTENDEE_STATUS_INVITED: return "ATTENDEE_STATUS_INVITED"; case Attendees.ATTENDEE_STATUS_TENTATIVE: return "ATTENDEE_STATUS_TENTATIVE"; default: return "ATTENDEE_STATUS_UNKNOWN_" + status; } } private static String attendeeTypeToString(int type) { switch (type) { case Attendees.TYPE_NONE: return "TYPE_NONE"; case Attendees.TYPE_REQUIRED: return "TYPE_REQUIRED"; case Attendees.TYPE_OPTIONAL: return "TYPE_OPTIONAL"; case Attendees.TYPE_RESOURCE: return "TYPE_RESOURCE"; default: return "TYPE_" + type; } } private static boolean meetsAttendance(int attendance, int attendeeType) { switch (attendance) { case EventInfo.ATTENDANCE_OPTIONAL: return attendeeType == Attendees.TYPE_OPTIONAL; case EventInfo.ATTENDANCE_REQUIRED: return attendeeType == Attendees.TYPE_REQUIRED; default: // EventInfo.ATTENDANCE_REQUIRED_OR_OPTIONAL return true; } } private static boolean meetsReply(int reply, int attendeeStatus) { switch (reply) { case EventInfo.REPLY_YES: return attendeeStatus == Attendees.ATTENDEE_STATUS_ACCEPTED; case EventInfo.REPLY_ANY_EXCEPT_NO: return attendeeStatus != Attendees.ATTENDEE_STATUS_DECLINED; default: // EventInfo.REPLY_ANY return true; } } private final ContentObserver mObserver = new ContentObserver(null) { @Override public void onChange(boolean selfChange, Uri u) { if (DEBUG) Log.d(TAG, "onChange selfChange=" + selfChange + " uri=" + u); mCallback.onChanged(); } @Override public void onChange(boolean selfChange) { if (DEBUG) Log.d(TAG, "onChange selfChange=" + selfChange); } }; public static class CheckEventResult { public boolean inEvent; public long recheckAt; } public interface Callback { void onChanged(); } }
services/core/java/com/android/server/notification/ConditionProviders.java +3 −0 Original line number Diff line number Diff line Loading @@ -121,6 +121,9 @@ public class ConditionProviders extends ManagedServices { @Override public void onBootPhaseAppsCanStart() { super.onBootPhaseAppsCanStart(); for (int i = 0; i < mSystemConditionProviders.size(); i++) { mSystemConditionProviders.valueAt(i).onBootComplete(); } if (mCallback != null) { mCallback.onBootComplete(); } Loading
services/core/java/com/android/server/notification/CountdownConditionProvider.java +7 −7 Original line number Diff line number Diff line Loading @@ -34,12 +34,11 @@ import android.util.Slog; import com.android.server.notification.NotificationManagerService.DumpFilter; import java.io.PrintWriter; import java.util.Date; /** Built-in zen condition provider for simple time-based conditions */ public class CountdownConditionProvider extends SystemConditionProviderService { private static final String TAG = "ConditionProviders"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final String TAG = "ConditionProviders.CCP"; private static final boolean DEBUG = Log.isLoggable("ConditionProviders", Log.DEBUG); public static final ComponentName COMPONENT = new ComponentName("android", CountdownConditionProvider.class.getName()); Loading Loading @@ -73,6 +72,11 @@ public class CountdownConditionProvider extends SystemConditionProviderService { attachBaseContext(base); } @Override public void onBootComplete() { // noop } @Override public IConditionProvider asInterface() { return (IConditionProvider) onBind(null); Loading Loading @@ -170,8 +174,4 @@ public class CountdownConditionProvider extends SystemConditionProviderService { ts(time), time - now, span, ts(now)); } private static String ts(long time) { return new Date(time) + " (" + time + ")"; } }
services/core/java/com/android/server/notification/EventConditionProvider.java +106 −7 Original line number Diff line number Diff line Loading @@ -16,16 +16,23 @@ package com.android.server.notification; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.service.notification.Condition; import android.service.notification.IConditionProvider; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.EventInfo; import android.util.ArraySet; import android.util.Log; import android.util.Slog; import com.android.server.notification.CalendarTracker.CheckEventResult; import com.android.server.notification.NotificationManagerService.DumpFilter; import java.io.PrintWriter; Loading @@ -34,20 +41,27 @@ import java.io.PrintWriter; * Built-in zen condition provider for calendar event-based conditions. */ public class EventConditionProvider extends SystemConditionProviderService { private static final String TAG = "ConditionProviders"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final String TAG = "ConditionProviders.ECP"; private static final boolean DEBUG = Log.isLoggable("ConditionProviders", Log.DEBUG); public static final ComponentName COMPONENT = new ComponentName("android", EventConditionProvider.class.getName()); private static final String NOT_SHOWN = "..."; private static final String SIMPLE_NAME = EventConditionProvider.class.getSimpleName(); private static final String ACTION_EVALUATE = SIMPLE_NAME + ".EVALUATE"; private static final int REQUEST_CODE_EVALUATE = 1; private static final String EXTRA_TIME = "time"; private final Context mContext = this; private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>(); private final CalendarTracker mTracker = new CalendarTracker(mContext); private boolean mConnected; private boolean mRegistered; private boolean mBootComplete; // don't hammer the calendar provider until boot completes. public EventConditionProvider() { if (DEBUG) Slog.d(TAG, "new EventConditionProvider()"); if (DEBUG) Slog.d(TAG, "new " + SIMPLE_NAME + "()"); } @Override Loading @@ -62,14 +76,25 @@ public class EventConditionProvider extends SystemConditionProviderService { @Override public void dump(PrintWriter pw, DumpFilter filter) { pw.println(" EventConditionProvider:"); pw.print(" "); pw.print(SIMPLE_NAME); pw.println(":"); pw.print(" mConnected="); pw.println(mConnected); pw.print(" mRegistered="); pw.println(mRegistered); pw.print(" mBootComplete="); pw.println(mBootComplete); pw.println(" mSubscriptions="); for (Uri conditionId : mSubscriptions) { pw.print(" "); pw.println(conditionId); } pw.println(" mTracker="); mTracker.dump(" ", pw); } @Override public void onBootComplete() { if (DEBUG) Slog.d(TAG, "onBootComplete"); if (mBootComplete) return; mBootComplete = true; evaluateSubscriptions(); } @Override Loading Loading @@ -98,9 +123,10 @@ public class EventConditionProvider extends SystemConditionProviderService { notifyCondition(conditionId, Condition.STATE_FALSE, "badCondition"); return; } mSubscriptions.add(conditionId); if (mSubscriptions.add(conditionId)) { evaluateSubscriptions(); } } @Override public void onUnsubscribe(Uri conditionId) { Loading @@ -121,9 +147,52 @@ public class EventConditionProvider extends SystemConditionProviderService { } private void evaluateSubscriptions() { if (DEBUG) Log.d(TAG, "evaluateSubscriptions"); if (!mBootComplete) { if (DEBUG) Log.d(TAG, "Skipping evaluate before boot complete"); return; } final long now = System.currentTimeMillis(); mTracker.setCallback(mSubscriptions.isEmpty() ? null : mTrackerCallback); setRegistered(!mSubscriptions.isEmpty()); long reevaluateAt = 0; for (Uri conditionId : mSubscriptions) { notifyCondition(conditionId, Condition.STATE_FALSE, "notImplemented"); final EventInfo event = ZenModeConfig.tryParseEventConditionId(conditionId); if (event == null) { notifyCondition(conditionId, Condition.STATE_FALSE, "badConditionId"); continue; } final CheckEventResult result = mTracker.checkEvent(event, now); if (result.recheckAt != 0 && (reevaluateAt == 0 || result.recheckAt < reevaluateAt)) { reevaluateAt = result.recheckAt; } if (!result.inEvent) { notifyCondition(conditionId, Condition.STATE_FALSE, "!inEventNow"); continue; } notifyCondition(conditionId, Condition.STATE_TRUE, "inEventNow"); } updateAlarm(now, reevaluateAt); if (DEBUG) Log.d(TAG, "evaluateSubscriptions took " + (System.currentTimeMillis() - now)); } private void updateAlarm(long now, long time) { final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, REQUEST_CODE_EVALUATE, new Intent(ACTION_EVALUATE) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) .putExtra(EXTRA_TIME, time), PendingIntent.FLAG_UPDATE_CURRENT); alarms.cancel(pendingIntent); if (time == 0 || time < now) { if (DEBUG) Slog.d(TAG, "Not scheduling evaluate: " + (time == 0 ? "no time specified" : "specified time in the past")); return; } if (DEBUG) Slog.d(TAG, String.format("Scheduling evaluate for %s, in %s, now=%s", ts(time), formatDuration(time - now), ts(now))); alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); } private void notifyCondition(Uri conditionId, int state, String reason) { Loading @@ -139,4 +208,34 @@ public class EventConditionProvider extends SystemConditionProviderService { return new Condition(id, summary, line1, line2, 0, state, Condition.FLAG_RELEVANT_ALWAYS); } private void setRegistered(boolean registered) { if (mRegistered == registered) return; if (DEBUG) Slog.d(TAG, "setRegistered " + registered); mRegistered = registered; if (mRegistered) { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_TIME_CHANGED); filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); filter.addAction(ACTION_EVALUATE); registerReceiver(mReceiver, filter); } else { unregisterReceiver(mReceiver); } } private final CalendarTracker.Callback mTrackerCallback = new CalendarTracker.Callback() { @Override public void onChanged() { if (DEBUG) Log.d(TAG, "mTrackerCallback.onChanged"); evaluateSubscriptions(); } }; private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction()); evaluateSubscriptions(); } }; }
services/core/java/com/android/server/notification/ScheduleConditionProvider.java +11 −17 Original line number Diff line number Diff line Loading @@ -31,25 +31,24 @@ import android.service.notification.ZenModeConfig.ScheduleInfo; import android.util.ArraySet; import android.util.Log; import android.util.Slog; import android.util.TimeUtils; import com.android.server.notification.NotificationManagerService.DumpFilter; import java.io.PrintWriter; import java.util.Date; import java.util.TimeZone; /** * Built-in zen condition provider for daily scheduled time-based conditions. */ public class ScheduleConditionProvider extends SystemConditionProviderService { private static final String TAG = "ConditionProviders"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final String TAG = "ConditionProviders.SCP"; private static final boolean DEBUG = Log.isLoggable("ConditionProviders", Log.DEBUG); public static final ComponentName COMPONENT = new ComponentName("android", ScheduleConditionProvider.class.getName()); private static final String NOT_SHOWN = "..."; private static final String ACTION_EVALUATE = TAG + ".EVALUATE"; private static final String SIMPLE_NAME = ScheduleConditionProvider.class.getSimpleName(); private static final String ACTION_EVALUATE = SIMPLE_NAME + ".EVALUATE"; private static final int REQUEST_CODE_EVALUATE = 1; private static final String EXTRA_TIME = "time"; Loading @@ -60,7 +59,7 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { private boolean mRegistered; public ScheduleConditionProvider() { if (DEBUG) Slog.d(TAG, "new ScheduleConditionProvider()"); if (DEBUG) Slog.d(TAG, "new " + SIMPLE_NAME + "()"); } @Override Loading @@ -75,7 +74,7 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { @Override public void dump(PrintWriter pw, DumpFilter filter) { pw.println(" ScheduleConditionProvider:"); pw.print(" "); pw.print(SIMPLE_NAME); pw.println(":"); pw.print(" mConnected="); pw.println(mConnected); pw.print(" mRegistered="); pw.println(mRegistered); pw.println(" mSubscriptions="); Loading @@ -93,6 +92,11 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { mConnected = true; } @Override public void onBootComplete() { // noop } @Override public void onDestroy() { super.onDestroy(); Loading Loading @@ -175,16 +179,6 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { } } private static String ts(long time) { return new Date(time) + " (" + time + ")"; } private static String formatDuration(long millis) { final StringBuilder sb = new StringBuilder(); TimeUtils.formatDuration(millis, sb); return sb.toString(); } private static boolean meetsSchedule(Uri conditionId, long time) { final ScheduleCalendar cal = toScheduleCalendar(conditionId); return cal != null && cal.isInSchedule(time); Loading