From 4db0d98b42a723f2e16c6595e85e866fd26c6d98 Mon Sep 17 00:00:00 2001 From: John Spurlock Date: Wed, 13 Aug 2014 09:19:03 -0400 Subject: [PATCH] Zen: Downtime now a condition provider, persist conditions. - Persist the entire exit condition instead of only the id. - Make downtime a proper condition provider (similar to the existing countdown provider for time-based conditions) - Move all downtime-related items out of ZenModeHelper and into the new condition provider. - Reevaluate downtime more often, when any of its inputs change. - Make sure downtime appears as an available condition in the condition panel when applicable. Bug:16296125 Bug:16211189 Bug:17031767 Change-Id: I1d8269a4e6fe170ce776bf932dbbdfb29dd25dd7 --- .../android/app/INotificationManager.aidl | 2 +- .../service/notification/ZenModeConfig.java | 143 +++++++-- core/res/res/values/strings.xml | 3 + core/res/res/values/symbols.xml | 1 + .../statusbar/policy/ZenModeController.java | 7 +- .../policy/ZenModeControllerImpl.java | 14 +- .../android/systemui/volume/ZenModePanel.java | 150 +++++---- .../notification/ConditionProviders.java | 90 ++++-- .../CountdownConditionProvider.java | 5 +- .../DowntimeConditionProvider.java | 289 ++++++++++++++++++ .../server/notification/ManagedServices.java | 3 +- .../NotificationManagerService.java | 4 +- .../android/server/notification/ZenLog.java | 35 ++- .../server/notification/ZenModeHelper.java | 108 +------ 14 files changed, 631 insertions(+), 223 deletions(-) create mode 100644 services/core/java/com/android/server/notification/DowntimeConditionProvider.java diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 4b659347e80ab..e8f6818724500 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -66,7 +66,7 @@ interface INotificationManager boolean setZenModeConfig(in ZenModeConfig config); oneway void notifyConditions(String pkg, in IConditionProvider provider, in Condition[] conditions); oneway void requestZenModeConditions(in IConditionListener callback, int relevance); - oneway void setZenModeCondition(in Uri conditionId); + oneway void setZenModeCondition(in Condition condition); oneway void setAutomaticZenModeConditions(in Uri[] conditionIds); Condition[] getAutomaticZenModeConditions(); } \ No newline at end of file diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index cc09653aa9330..872f911c03eff 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -73,9 +73,14 @@ public class ZenModeConfig implements Parcelable { private static final String CONDITION_TAG = "condition"; private static final String CONDITION_ATT_COMPONENT = "component"; private static final String CONDITION_ATT_ID = "id"; + private static final String CONDITION_ATT_SUMMARY = "summary"; + private static final String CONDITION_ATT_LINE1 = "line1"; + private static final String CONDITION_ATT_LINE2 = "line2"; + private static final String CONDITION_ATT_ICON = "icon"; + private static final String CONDITION_ATT_STATE = "state"; + private static final String CONDITION_ATT_FLAGS = "flags"; private static final String EXIT_CONDITION_TAG = "exitCondition"; - private static final String EXIT_CONDITION_ATT_ID = "id"; private static final String EXIT_CONDITION_ATT_COMPONENT = "component"; public boolean allowCalls; @@ -83,13 +88,13 @@ public class ZenModeConfig implements Parcelable { public int allowFrom = SOURCE_ANYONE; public String sleepMode; - public int sleepStartHour; - public int sleepStartMinute; + public int sleepStartHour; // 0-23 + public int sleepStartMinute; // 0-59 public int sleepEndHour; public int sleepEndMinute; public ComponentName[] conditionComponents; public Uri[] conditionIds; - public Uri exitConditionId; + public Condition exitCondition; public ComponentName exitConditionComponent; public ZenModeConfig() { } @@ -115,7 +120,7 @@ public class ZenModeConfig implements Parcelable { source.readTypedArray(conditionIds, Uri.CREATOR); } allowFrom = source.readInt(); - exitConditionId = source.readParcelable(null); + exitCondition = source.readParcelable(null); exitConditionComponent = source.readParcelable(null); } @@ -146,7 +151,7 @@ public class ZenModeConfig implements Parcelable { dest.writeInt(0); } dest.writeInt(allowFrom); - dest.writeParcelable(exitConditionId, 0); + dest.writeParcelable(exitCondition, 0); dest.writeParcelable(exitConditionComponent, 0); } @@ -163,7 +168,7 @@ public class ZenModeConfig implements Parcelable { .append(conditionComponents == null ? null : TextUtils.join(",", conditionComponents)) .append(",conditionIds=") .append(conditionIds == null ? null : TextUtils.join(",", conditionIds)) - .append(",exitConditionId=").append(exitConditionId) + .append(",exitCondition=").append(exitCondition) .append(",exitConditionComponent=").append(exitConditionComponent) .append(']').toString(); } @@ -196,7 +201,7 @@ public class ZenModeConfig implements Parcelable { && other.sleepEndMinute == sleepEndMinute && Objects.deepEquals(other.conditionComponents, conditionComponents) && Objects.deepEquals(other.conditionIds, conditionIds) - && Objects.equals(other.exitConditionId, exitConditionId) + && Objects.equals(other.exitCondition, exitCondition) && Objects.equals(other.exitConditionComponent, exitConditionComponent); } @@ -205,7 +210,7 @@ public class ZenModeConfig implements Parcelable { return Objects.hash(allowCalls, allowMessages, allowFrom, sleepMode, sleepStartHour, sleepStartMinute, sleepEndHour, sleepEndMinute, Arrays.hashCode(conditionComponents), Arrays.hashCode(conditionIds), - exitConditionId, exitConditionComponent); + exitCondition, exitConditionComponent); } public boolean isValid() { @@ -294,9 +299,11 @@ public class ZenModeConfig implements Parcelable { conditionIds.add(conditionId); } } else if (EXIT_CONDITION_TAG.equals(tag)) { - rt.exitConditionId = safeUri(parser, EXIT_CONDITION_ATT_ID); - rt.exitConditionComponent = - safeComponentName(parser, EXIT_CONDITION_ATT_COMPONENT); + rt.exitCondition = readConditionXml(parser); + if (rt.exitCondition != null) { + rt.exitConditionComponent = + safeComponentName(parser, EXIT_CONDITION_ATT_COMPONENT); + } } } } @@ -333,16 +340,42 @@ public class ZenModeConfig implements Parcelable { out.endTag(null, CONDITION_TAG); } } - if (exitConditionId != null && exitConditionComponent != null) { + if (exitCondition != null && exitConditionComponent != null) { out.startTag(null, EXIT_CONDITION_TAG); - out.attribute(null, EXIT_CONDITION_ATT_ID, exitConditionId.toString()); out.attribute(null, EXIT_CONDITION_ATT_COMPONENT, exitConditionComponent.flattenToString()); + writeConditionXml(exitCondition, out); out.endTag(null, EXIT_CONDITION_TAG); } out.endTag(null, ZEN_TAG); } + public static Condition readConditionXml(XmlPullParser parser) { + final Uri id = safeUri(parser, CONDITION_ATT_ID); + final String summary = parser.getAttributeValue(null, CONDITION_ATT_SUMMARY); + final String line1 = parser.getAttributeValue(null, CONDITION_ATT_LINE1); + final String line2 = parser.getAttributeValue(null, CONDITION_ATT_LINE2); + final int icon = safeInt(parser, CONDITION_ATT_ICON, -1); + final int state = safeInt(parser, CONDITION_ATT_STATE, -1); + final int flags = safeInt(parser, CONDITION_ATT_FLAGS, -1); + try { + return new Condition(id, summary, line1, line2, icon, state, flags); + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Unable to read condition xml", e); + return null; + } + } + + public static void writeConditionXml(Condition c, XmlSerializer out) throws IOException { + out.attribute(null, CONDITION_ATT_ID, c.id.toString()); + out.attribute(null, CONDITION_ATT_SUMMARY, c.summary); + out.attribute(null, CONDITION_ATT_LINE1, c.line1); + out.attribute(null, CONDITION_ATT_LINE2, c.line2); + out.attribute(null, CONDITION_ATT_ICON, Integer.toString(c.icon)); + out.attribute(null, CONDITION_ATT_STATE, Integer.toString(c.state)); + out.attribute(null, CONDITION_ATT_FLAGS, Integer.toString(c.flags)); + } + public static boolean isValidHour(int val) { return val >= 0 && val < 24; } @@ -403,21 +436,31 @@ public class ZenModeConfig implements Parcelable { } }; - // Built-in countdown conditions, e.g. condition://android/countdown/1399917958951 + public DowntimeInfo toDowntimeInfo() { + final DowntimeInfo downtime = new DowntimeInfo(); + downtime.startHour = sleepStartHour; + downtime.startMinute = sleepStartMinute; + downtime.endHour = sleepEndHour; + downtime.endMinute = sleepEndMinute; + return downtime; + } - private static final String COUNTDOWN_AUTHORITY = "android"; + // For built-in conditions + private static final String SYSTEM_AUTHORITY = "android"; + + // Built-in countdown conditions, e.g. condition://android/countdown/1399917958951 private static final String COUNTDOWN_PATH = "countdown"; public static Uri toCountdownConditionId(long time) { return new Uri.Builder().scheme(Condition.SCHEME) - .authority(COUNTDOWN_AUTHORITY) + .authority(SYSTEM_AUTHORITY) .appendPath(COUNTDOWN_PATH) .appendPath(Long.toString(time)) .build(); } public static long tryParseCountdownConditionId(Uri conditionId) { - if (!Condition.isValidId(conditionId, COUNTDOWN_AUTHORITY)) return 0; + if (!Condition.isValidId(conditionId, SYSTEM_AUTHORITY)) return 0; if (conditionId.getPathSegments().size() != 2 || !COUNTDOWN_PATH.equals(conditionId.getPathSegments().get(0))) return 0; try { @@ -431,4 +474,68 @@ public class ZenModeConfig implements Parcelable { public static boolean isValidCountdownConditionId(Uri conditionId) { return tryParseCountdownConditionId(conditionId) != 0; } + + // Built-in downtime conditions, e.g. condition://android/downtime?start=10.00&end=7.00 + private static final String DOWNTIME_PATH = "downtime"; + + public static Uri toDowntimeConditionId(DowntimeInfo downtime) { + return new Uri.Builder().scheme(Condition.SCHEME) + .authority(SYSTEM_AUTHORITY) + .appendPath(DOWNTIME_PATH) + .appendQueryParameter("start", downtime.startHour + "." + downtime.startMinute) + .appendQueryParameter("end", downtime.endHour + "." + downtime.endMinute) + .build(); + } + + public static DowntimeInfo tryParseDowntimeConditionId(Uri conditionId) { + if (!Condition.isValidId(conditionId, SYSTEM_AUTHORITY) + || conditionId.getPathSegments().size() != 1 + || !DOWNTIME_PATH.equals(conditionId.getPathSegments().get(0))) { + return null; + } + final int[] start = tryParseHourAndMinute(conditionId.getQueryParameter("start")); + final int[] end = tryParseHourAndMinute(conditionId.getQueryParameter("end")); + if (start == null || end == null) return null; + final DowntimeInfo downtime = new DowntimeInfo(); + downtime.startHour = start[0]; + downtime.startMinute = start[1]; + downtime.endHour = end[0]; + downtime.endMinute = end[1]; + return downtime; + } + + private static int[] tryParseHourAndMinute(String value) { + if (TextUtils.isEmpty(value)) return null; + final int i = value.indexOf('.'); + if (i < 1 || i >= value.length() - 1) return null; + final int hour = tryParseInt(value.substring(0, i), -1); + final int minute = tryParseInt(value.substring(i + 1), -1); + return isValidHour(hour) && isValidMinute(minute) ? new int[] { hour, minute } : null; + } + + public static boolean isValidDowntimeConditionId(Uri conditionId) { + return tryParseDowntimeConditionId(conditionId) != null; + } + + public static class DowntimeInfo { + public int startHour; // 0-23 + public int startMinute; // 0-59 + public int endHour; + public int endMinute; + + @Override + public int hashCode() { + return 0; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof DowntimeInfo)) return false; + final DowntimeInfo other = (DowntimeInfo) o; + return startHour == other.startHour + && startMinute == other.startMinute + && endHour == other.endHour + && endMinute == other.endMinute; + } + } } diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 74992ed4a82ec..8065a9c50a97b 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5523,4 +5523,7 @@ To help improve battery life, battery saver reduces your device’s performance and limits vibration and most background data. Email, messaging, and other apps that rely on syncing may not update unless you open them.\n\nBattery saver turns off automatically when your device is charging + + + Until your downtime ends at %1$s diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a4bec17d31122..2cd0948fa0bc8 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1903,6 +1903,7 @@ + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java index 6c4fb7a9f243a..7d102bad8dc29 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.policy; -import android.net.Uri; import android.service.notification.Condition; public interface ZenModeController { @@ -25,15 +24,15 @@ public interface ZenModeController { void setZen(int zen); int getZen(); void requestConditions(boolean request); - void setExitConditionId(Uri exitConditionId); - Uri getExitConditionId(); + void setExitCondition(Condition exitCondition); + Condition getExitCondition(); long getNextAlarm(); void setUserId(int userId); boolean isZenAvailable(); public static class Callback { public void onZenChanged(int zen) {} - public void onExitConditionChanged(Uri exitConditionId) {} + public void onExitConditionChanged(Condition exitCondition) {} public void onConditionsChanged(Condition[] conditions) {} public void onNextAlarmChanged() {} public void onZenAvailableChanged(boolean available) {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java index b33e502c86ab0..b0c8f26dbd9c3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java @@ -122,20 +122,20 @@ public class ZenModeControllerImpl implements ZenModeController { } @Override - public void setExitConditionId(Uri exitConditionId) { + public void setExitCondition(Condition exitCondition) { try { - mNoMan.setZenModeCondition(exitConditionId); + mNoMan.setZenModeCondition(exitCondition); } catch (RemoteException e) { // noop } } @Override - public Uri getExitConditionId() { + public Condition getExitCondition() { try { final ZenModeConfig config = mNoMan.getZenModeConfig(); if (config != null) { - return config.exitConditionId; + return config.exitCondition; } } catch (RemoteException e) { // noop @@ -186,10 +186,10 @@ public class ZenModeControllerImpl implements ZenModeController { } private void fireExitConditionChanged() { - final Uri exitConditionId = getExitConditionId(); - if (DEBUG) Slog.d(TAG, "exitConditionId changed: " + exitConditionId); + final Condition exitCondition = getExitCondition(); + if (DEBUG) Slog.d(TAG, "exitCondition changed: " + exitCondition); for (Callback cb : mCallbacks) { - cb.onExitConditionChanged(exitConditionId); + cb.onExitConditionChanged(exitCondition); } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java index 0d837c79b54c6..c99e1fd41de3c 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java +++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java @@ -97,14 +97,16 @@ public class ZenModePanel extends LinearLayout { private Callback mCallback; private ZenModeController mController; private boolean mRequestingConditions; - private Uri mExitConditionId; + private Condition mExitCondition; + private String mExitConditionText; private int mBucketIndex = -1; private boolean mExpanded; private boolean mHidden = false; private int mSessionZen; - private Uri mSessionExitConditionId; - private String mExitConditionText; + private Condition mSessionExitCondition; private long mNextAlarm; + private Condition[] mConditions; + private Condition mTimeCondition; public ZenModePanel(Context context, AttributeSet attrs) { super(context, attrs); @@ -161,7 +163,7 @@ public class ZenModePanel extends LinearLayout { super.onAttachedToWindow(); if (DEBUG) Log.d(mTag, "onAttachedToWindow"); mSessionZen = getSelectedZen(-1); - mSessionExitConditionId = mExitConditionId; + mSessionExitCondition = copy(mExitCondition); refreshExitConditionText(); refreshNextAlarm(); updateWidgets(); @@ -172,7 +174,7 @@ public class ZenModePanel extends LinearLayout { super.onDetachedFromWindow(); if (DEBUG) Log.d(mTag, "onDetachedFromWindow"); mSessionZen = -1; - mSessionExitConditionId = null; + mSessionExitCondition = null; setExpanded(false); } @@ -199,17 +201,16 @@ public class ZenModePanel extends LinearLayout { mController.requestConditions(mRequestingConditions); } if (mRequestingConditions) { - Condition timeCondition = parseExistingTimeCondition(mExitConditionId); - if (timeCondition != null) { + mTimeCondition = parseExistingTimeCondition(mExitCondition); + if (mTimeCondition != null) { mBucketIndex = -1; } else { mBucketIndex = DEFAULT_BUCKET_INDEX; - timeCondition = newTimeCondition(MINUTE_BUCKETS[mBucketIndex]); + mTimeCondition = newTimeCondition(MINUTE_BUCKETS[mBucketIndex]); } if (DEBUG) Log.d(mTag, "Initial bucket index: " + mBucketIndex); - handleUpdateConditions(new Condition[0]); // ensures forever exists - bind(timeCondition, mZenConditions.getChildAt(TIME_CONDITION_INDEX)); - checkForDefault(); + mConditions = null; // reset conditions + handleUpdateConditions(); } else { mZenConditions.removeAllViews(); } @@ -217,31 +218,47 @@ public class ZenModePanel extends LinearLayout { public void init(ZenModeController controller) { mController = controller; - setExitConditionId(mController.getExitConditionId()); + setExitCondition(mController.getExitCondition()); refreshExitConditionText(); mSessionZen = getSelectedZen(-1); handleUpdateZen(mController.getZen()); - if (DEBUG) Log.d(mTag, "init mExitConditionId=" + mExitConditionId); + if (DEBUG) Log.d(mTag, "init mExitCondition=" + mExitCondition); mZenConditions.removeAllViews(); mController.addCallback(mZenCallback); } - private void setExitConditionId(Uri exitConditionId) { - if (Objects.equals(mExitConditionId, exitConditionId)) return; - mExitConditionId = exitConditionId; + private void setExitCondition(Condition exitCondition) { + if (sameConditionId(mExitCondition, exitCondition)) return; + mExitCondition = exitCondition; refreshExitConditionText(); updateWidgets(); } + private Uri getExitConditionId() { + return getConditionId(mExitCondition); + } + + private static Uri getConditionId(Condition condition) { + return condition != null ? condition.id : null; + } + + private static boolean sameConditionId(Condition lhs, Condition rhs) { + return lhs == null ? rhs == null : rhs != null && lhs.id.equals(rhs.id); + } + + private static Condition copy(Condition condition) { + return condition == null ? null : condition.copy(); + } + private void refreshExitConditionText() { final String forever = mContext.getString(R.string.zen_mode_forever); - if (mExitConditionId == null) { + if (mExitCondition == null) { mExitConditionText = forever; - } else if (ZenModeConfig.isValidCountdownConditionId(mExitConditionId)) { - final Condition condition = parseExistingTimeCondition(mExitConditionId); + } else if (ZenModeConfig.isValidCountdownConditionId(mExitCondition.id)) { + final Condition condition = parseExistingTimeCondition(mExitCondition); mExitConditionText = condition != null ? condition.summary : forever; } else { - mExitConditionText = "(until condition ends)"; // TODO persist current description + mExitConditionText = mExitCondition.summary; } } @@ -296,7 +313,7 @@ public class ZenModePanel extends LinearLayout { mZenConditions.setVisibility(!zenOff && expanded ? VISIBLE : GONE); mAlarmWarning.setVisibility(zenNone && expanded && hasNextAlarm ? VISIBLE : GONE); if (showAlarmWarning) { - final long exitTime = ZenModeConfig.tryParseCountdownConditionId(mExitConditionId); + final long exitTime = ZenModeConfig.tryParseCountdownConditionId(getExitConditionId()); final long now = System.currentTimeMillis(); final boolean alarmToday = time(mNextAlarm).yearDay == time(now).yearDay; final String skeleton = (alarmToday ? "" : "E") @@ -330,8 +347,9 @@ public class ZenModePanel extends LinearLayout { return t; } - private Condition parseExistingTimeCondition(Uri conditionId) { - final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId); + private Condition parseExistingTimeCondition(Condition condition) { + if (condition == null) return null; + final long time = ZenModeConfig.tryParseCountdownConditionId(condition.id); if (time == 0) return null; final long span = time - System.currentTimeMillis(); if (span <= 0 || span > MAX_BUCKET_MINUTES * MINUTES_MS) return null; @@ -365,15 +383,36 @@ public class ZenModePanel extends LinearLayout { } private void handleUpdateConditions(Condition[] conditions) { - final int newCount = conditions == null ? 0 : conditions.length; - if (DEBUG) Log.d(mTag, "handleUpdateConditions newCount=" + newCount); - for (int i = mZenConditions.getChildCount(); i >= newCount + FIRST_CONDITION_INDEX; i--) { + mConditions = conditions; + handleUpdateConditions(); + } + + private void handleUpdateConditions() { + final int conditionCount = mConditions == null ? 0 : mConditions.length; + if (DEBUG) Log.d(mTag, "handleUpdateConditions conditionCount=" + conditionCount); + for (int i = mZenConditions.getChildCount() - 1; i >= FIRST_CONDITION_INDEX; i--) { mZenConditions.removeViewAt(i); } + // forever bind(null, mZenConditions.getChildAt(FOREVER_CONDITION_INDEX)); - for (int i = 0; i < newCount; i++) { - bind(conditions[i], mZenConditions.getChildAt(FIRST_CONDITION_INDEX + i)); + // countdown + bind(mTimeCondition, mZenConditions.getChildAt(TIME_CONDITION_INDEX)); + // provider conditions + boolean foundDowntime = false; + for (int i = 0; i < conditionCount; i++) { + bind(mConditions[i], mZenConditions.getChildAt(FIRST_CONDITION_INDEX + i)); + foundDowntime |= isDowntime(mConditions[i]); } + // ensure downtime exists, if active + if (isDowntime(mSessionExitCondition) && !foundDowntime) { + bind(mSessionExitCondition, null); + } + // ensure something is selected + checkForDefault(); + } + + private static boolean isDowntime(Condition c) { + return ZenModeConfig.isValidDowntimeConditionId(getConditionId(c)); } private ConditionTag getConditionTagAt(int index) { @@ -385,7 +424,7 @@ public class ZenModePanel extends LinearLayout { for (int i = 0; i < mZenConditions.getChildCount(); i++) { if (getConditionTagAt(i).rb.isChecked()) { if (DEBUG) Log.d(mTag, "Not selecting a default, checked=" - + getConditionTagAt(i).conditionId); + + getConditionTagAt(i).condition); return; } } @@ -394,20 +433,20 @@ public class ZenModePanel extends LinearLayout { if (favoriteIndex == -1) { getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true); } else { - final Condition c = newTimeCondition(MINUTE_BUCKETS[favoriteIndex]); + mTimeCondition = newTimeCondition(MINUTE_BUCKETS[favoriteIndex]); mBucketIndex = favoriteIndex; - bind(c, mZenConditions.getChildAt(TIME_CONDITION_INDEX)); + bind(mTimeCondition, mZenConditions.getChildAt(TIME_CONDITION_INDEX)); getConditionTagAt(TIME_CONDITION_INDEX).rb.setChecked(true); } } - private void handleExitConditionChanged(Uri exitCondition) { - setExitConditionId(exitCondition); - if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitConditionId); + private void handleExitConditionChanged(Condition exitCondition) { + setExitCondition(exitCondition); + if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitCondition); final int N = mZenConditions.getChildCount(); for (int i = 0; i < N; i++) { final ConditionTag tag = getConditionTagAt(i); - tag.rb.setChecked(Objects.equals(tag.conditionId, exitCondition)); + tag.rb.setChecked(sameConditionId(tag.condition, mExitCondition)); } } @@ -427,23 +466,23 @@ public class ZenModePanel extends LinearLayout { if (tag.rb == null) { tag.rb = (RadioButton) row.findViewById(android.R.id.checkbox); } - tag.conditionId = condition != null ? condition.id : null; + tag.condition = condition; tag.rb.setEnabled(enabled); - if (mSessionExitConditionId != null && mSessionExitConditionId.equals(tag.conditionId)) { + if (sameConditionId(mSessionExitCondition, tag.condition)) { tag.rb.setChecked(true); } tag.rb.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (mExpanded && isChecked) { - if (DEBUG) Log.d(mTag, "onCheckedChanged " + tag.conditionId); + if (DEBUG) Log.d(mTag, "onCheckedChanged " + tag.condition); final int N = mZenConditions.getChildCount(); for (int i = 0; i < N; i++) { ConditionTag childTag = getConditionTagAt(i); if (childTag == tag) continue; childTag.rb.setChecked(false); } - select(tag.conditionId); + select(tag.condition); fireInteraction(); } } @@ -479,7 +518,7 @@ public class ZenModePanel extends LinearLayout { } }); - final long time = ZenModeConfig.tryParseCountdownConditionId(tag.conditionId); + final long time = ZenModeConfig.tryParseCountdownConditionId(getConditionId(tag.condition)); if (time > 0) { if (mBucketIndex > -1) { button1.setEnabled(mBucketIndex > 0); @@ -504,7 +543,8 @@ public class ZenModePanel extends LinearLayout { final int N = MINUTE_BUCKETS.length; if (mBucketIndex == -1) { // not on a known index, search for the next or prev bucket by time - final long time = ZenModeConfig.tryParseCountdownConditionId(tag.conditionId); + final Uri conditionId = getConditionId(tag.condition); + final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId); final long now = System.currentTimeMillis(); for (int i = 0; i < N; i++) { int j = up ? i : N - 1 - i; @@ -525,24 +565,25 @@ public class ZenModePanel extends LinearLayout { mBucketIndex = Math.max(0, Math.min(N - 1, mBucketIndex + (up ? 1 : -1))); newCondition = newTimeCondition(MINUTE_BUCKETS[mBucketIndex]); } - bind(newCondition, row); + mTimeCondition = newCondition; + bind(mTimeCondition, row); tag.rb.setChecked(true); - select(newCondition.id); + select(mTimeCondition); fireInteraction(); } - private void select(Uri conditionId) { - if (DEBUG) Log.d(mTag, "select " + conditionId); + private void select(Condition condition) { + if (DEBUG) Log.d(mTag, "select " + condition); if (mController != null) { - mController.setExitConditionId(conditionId); + mController.setExitCondition(condition); } - setExitConditionId(conditionId); - if (conditionId == null) { + setExitCondition(condition); + if (condition == null) { mFavorites.setMinuteIndex(-1); - } else if (ZenModeConfig.isValidCountdownConditionId(conditionId) && mBucketIndex != -1) { + } else if (ZenModeConfig.isValidCountdownConditionId(condition.id) && mBucketIndex != -1) { mFavorites.setMinuteIndex(mBucketIndex); } - mSessionExitConditionId = conditionId; + mSessionExitCondition = copy(condition); } private void fireMoreSettings() { @@ -574,8 +615,8 @@ public class ZenModePanel extends LinearLayout { } @Override - public void onExitConditionChanged(Uri exitConditionId) { - mHandler.obtainMessage(H.EXIT_CONDITION_CHANGED, exitConditionId).sendToTarget(); + public void onExitConditionChanged(Condition exitCondition) { + mHandler.obtainMessage(H.EXIT_CONDITION_CHANGED, exitCondition).sendToTarget(); } @Override @@ -598,9 +639,8 @@ public class ZenModePanel extends LinearLayout { public void handleMessage(Message msg) { if (msg.what == UPDATE_CONDITIONS) { handleUpdateConditions((Condition[]) msg.obj); - checkForDefault(); } else if (msg.what == EXIT_CONDITION_CHANGED) { - handleExitConditionChanged((Uri) msg.obj); + handleExitConditionChanged((Condition) msg.obj); } else if (msg.what == UPDATE_ZEN) { handleUpdateZen(msg.arg1); } else if (msg.what == NEXT_ALARM_CHANGED) { @@ -618,7 +658,7 @@ public class ZenModePanel extends LinearLayout { // used as the view tag on condition rows private static class ConditionTag { RadioButton rb; - Uri conditionId; + Condition condition; } private final class Favorites implements OnSharedPreferenceChangeListener { diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java index a06daf6374962..189131c557f4d 100644 --- a/services/core/java/com/android/server/notification/ConditionProviders.java +++ b/services/core/java/com/android/server/notification/ConditionProviders.java @@ -51,8 +51,9 @@ public class ConditionProviders extends ManagedServices { = new ArrayMap(); private final ArrayList mRecords = new ArrayList(); private final CountdownConditionProvider mCountdown = new CountdownConditionProvider(); + private final DowntimeConditionProvider mDowntime = new DowntimeConditionProvider(); - private Uri mExitConditionId; + private Condition mExitCondition; private ComponentName mExitConditionComponent; public ConditionProviders(Context context, Handler handler, @@ -97,6 +98,7 @@ public class ConditionProviders extends ManagedServices { } } mCountdown.dump(pw, filter); + mDowntime.dump(pw, filter); } @Override @@ -110,6 +112,10 @@ public class ConditionProviders extends ManagedServices { mCountdown.attachBase(mContext); registerService(mCountdown.asInterface(), CountdownConditionProvider.COMPONENT, UserHandle.USER_OWNER); + mDowntime.attachBase(mContext); + registerService(mDowntime.asInterface(), DowntimeConditionProvider.COMPONENT, + UserHandle.USER_OWNER); + mDowntime.setCallback(new DowntimeCallback()); } @Override @@ -125,7 +131,7 @@ public class ConditionProviders extends ManagedServices { if (info.component.equals(mExitConditionComponent)) { // ensure record exists, we'll wire it up and subscribe below final ConditionRecord manualRecord = - getRecordLocked(mExitConditionId, mExitConditionComponent); + getRecordLocked(mExitCondition.id, mExitConditionComponent); manualRecord.isManual = true; } final int N = mRecords.size(); @@ -149,11 +155,11 @@ public class ConditionProviders extends ManagedServices { if (!r.component.equals(removed.component)) continue; if (r.isManual) { // removing the current manual condition, exit zen - mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF); + mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "manualServiceRemoved"); } if (r.isAutomatic) { // removing an automatic condition, exit zen - mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF); + mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "automaticServiceRemoved"); } mRecords.remove(i); } @@ -249,7 +255,8 @@ public class ConditionProviders extends ManagedServices { } else if (DEBUG) { Slog.d(TAG, "Exit zen: manual condition false: " + c); } - mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF); + mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF, + "manualConditionExit"); unsubscribeLocked(r); r.isManual = false; } @@ -263,33 +270,46 @@ public class ConditionProviders extends ManagedServices { } else if (DEBUG) { Slog.d(TAG, "Exit zen: automatic condition false: " + c); } - mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF); + mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF, + "automaticConditionExit"); } else if (c.state == Condition.STATE_TRUE) { Slog.d(TAG, "Enter zen: automatic condition true: " + c); - mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS); + mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, + "automaticConditionEnter"); } } } } } - public void setZenModeCondition(Uri conditionId, String reason) { - if (DEBUG) Slog.d(TAG, "setZenModeCondition " + conditionId); + public void setZenModeCondition(Condition condition, String reason) { + if (DEBUG) Slog.d(TAG, "setZenModeCondition " + condition); synchronized(mMutex) { ComponentName conditionComponent = null; - if (ZenModeConfig.isValidCountdownConditionId(conditionId)) { - // constructed by the client, make sure the record exists... - final ConditionRecord r = getRecordLocked(conditionId, - CountdownConditionProvider.COMPONENT); - if (r.info == null) { - // ... and is associated with the in-process service - r.info = checkServiceTokenLocked(mCountdown.asInterface()); + if (condition != null) { + if (ZenModeConfig.isValidCountdownConditionId(condition.id)) { + // constructed by the client, make sure the record exists... + final ConditionRecord r = getRecordLocked(condition.id, + CountdownConditionProvider.COMPONENT); + if (r.info == null) { + // ... and is associated with the in-process service + r.info = checkServiceTokenLocked(mCountdown.asInterface()); + } + } + if (ZenModeConfig.isValidDowntimeConditionId(condition.id)) { + // constructed by the client, make sure the record exists... + final ConditionRecord r = getRecordLocked(condition.id, + DowntimeConditionProvider.COMPONENT); + if (r.info == null) { + // ... and is associated with the in-process service + r.info = checkServiceTokenLocked(mDowntime.asInterface()); + } } } final int N = mRecords.size(); for (int i = 0; i < N; i++) { final ConditionRecord r = mRecords.get(i); - final boolean idEqual = r.id.equals(conditionId); + final boolean idEqual = condition != null && r.id.equals(condition.id); if (r.isManual && !idEqual) { // was previous manual condition, unsubscribe unsubscribeLocked(r); @@ -303,10 +323,10 @@ public class ConditionProviders extends ManagedServices { conditionComponent = r.component; } } - if (!Objects.equals(mExitConditionId, conditionId)) { - mExitConditionId = conditionId; + if (!Objects.equals(mExitCondition, condition)) { + mExitCondition = condition; mExitConditionComponent = conditionComponent; - ZenLog.traceExitCondition(mExitConditionId, mExitConditionComponent, reason); + ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, reason); saveZenConfigLocked(); } } @@ -318,6 +338,7 @@ public class ConditionProviders extends ManagedServices { RemoteException re = null; if (provider != null) { try { + Slog.d(TAG, "Subscribing to " + r.id + " with " + provider); provider.onSubscribe(r.id); } catch (RemoteException e) { Slog.w(TAG, "Error subscribing to " + r, e); @@ -436,12 +457,13 @@ public class ConditionProviders extends ManagedServices { return; } synchronized (mMutex) { - final boolean changingExit = !Objects.equals(mExitConditionId, config.exitConditionId); - mExitConditionId = config.exitConditionId; + final boolean changingExit = !Objects.equals(mExitCondition, config.exitCondition); + mExitCondition = config.exitCondition; mExitConditionComponent = config.exitConditionComponent; if (changingExit) { - ZenLog.traceExitCondition(mExitConditionId, mExitConditionComponent, "config"); + ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, "config"); } + mDowntime.setConfig(config); if (config.conditionComponents == null || config.conditionIds == null || config.conditionComponents.length != config.conditionIds.length) { if (DEBUG) Slog.d(TAG, "loadZenConfig: no conditions"); @@ -488,7 +510,7 @@ public class ConditionProviders extends ManagedServices { config.conditionIds[i] = r.id; } } - config.exitConditionId = mExitConditionId; + config.exitCondition = mExitCondition; config.exitConditionComponent = mExitConditionComponent; if (DEBUG) Slog.d(TAG, "Setting zen config to: " + config); mZenModeHelper.setConfig(config); @@ -510,6 +532,26 @@ public class ConditionProviders extends ManagedServices { } } + private class DowntimeCallback implements DowntimeConditionProvider.Callback { + @Override + public void onDowntimeChanged(boolean inDowntime) { + final int mode = mZenModeHelper.getZenMode(); + final ZenModeConfig config = mZenModeHelper.getConfig(); + // enter downtime + if (inDowntime && mode == Global.ZEN_MODE_OFF && config != null) { + final Condition condition = mDowntime.createCondition(config.toDowntimeInfo(), + Condition.STATE_TRUE); + mZenModeHelper.setZenMode(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, "downtimeEnter"); + setZenModeCondition(condition, "downtime"); + } + // exit downtime + if (!inDowntime && mode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS + && mDowntime.isDowntimeCondition(mExitCondition)) { + mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "downtimeExit"); + } + } + } + private static class ConditionRecord { public final Uri id; public final ComponentName component; diff --git a/services/core/java/com/android/server/notification/CountdownConditionProvider.java b/services/core/java/com/android/server/notification/CountdownConditionProvider.java index aaf7cfc73fa86..37aacaa880bd9 100644 --- a/services/core/java/com/android/server/notification/CountdownConditionProvider.java +++ b/services/core/java/com/android/server/notification/CountdownConditionProvider.java @@ -29,6 +29,7 @@ import android.service.notification.ConditionProviderService; import android.service.notification.IConditionProvider; import android.service.notification.ZenModeConfig; import android.text.format.DateUtils; +import android.util.Log; import android.util.Slog; import com.android.server.notification.NotificationManagerService.DumpFilter; @@ -38,8 +39,8 @@ import java.util.Date; /** Built-in zen condition provider for simple time-based conditions */ public class CountdownConditionProvider extends ConditionProviderService { - private static final String TAG = "CountdownConditionProvider"; - private static final boolean DEBUG = false; + private static final String TAG = "CountdownConditions"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); public static final ComponentName COMPONENT = new ComponentName("android", CountdownConditionProvider.class.getName()); diff --git a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java new file mode 100644 index 0000000000000..317ebefc02bb3 --- /dev/null +++ b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java @@ -0,0 +1,289 @@ +/** + * Copyright (c) 2014, 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.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.ConditionProviderService; +import android.service.notification.IConditionProvider; +import android.service.notification.ZenModeConfig; +import android.service.notification.ZenModeConfig.DowntimeInfo; +import android.text.format.DateFormat; +import android.util.ArraySet; +import android.util.Log; +import android.util.Slog; + +import com.android.internal.R; +import com.android.server.notification.NotificationManagerService.DumpFilter; + +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; +import java.util.Objects; + +/** Built-in zen condition provider for managing downtime */ +public class DowntimeConditionProvider extends ConditionProviderService { + private static final String TAG = "DowntimeConditions"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + public static final ComponentName COMPONENT = + new ComponentName("android", DowntimeConditionProvider.class.getName()); + + private static final String ENTER_ACTION = TAG + ".enter"; + private static final int ENTER_CODE = 100; + private static final String EXIT_ACTION = TAG + ".exit"; + private static final int EXIT_CODE = 101; + private static final String EXTRA_TIME = "time"; + + private final Calendar mCalendar = Calendar.getInstance(); + private final Context mContext = this; + private final ArraySet mDays = new ArraySet(); + + private boolean mConnected; + private boolean mInDowntime; + private ZenModeConfig mConfig; + private Callback mCallback; + + public DowntimeConditionProvider() { + if (DEBUG) Slog.d(TAG, "new DowntimeConditionProvider()"); + } + + public void dump(PrintWriter pw, DumpFilter filter) { + pw.println(" DowntimeConditionProvider:"); + pw.print(" mConnected="); pw.println(mConnected); + pw.print(" mInDowntime="); pw.println(mInDowntime); + } + + public void attachBase(Context base) { + attachBaseContext(base); + } + + public IConditionProvider asInterface() { + return (IConditionProvider) onBind(null); + } + + public void setCallback(Callback callback) { + mCallback = callback; + } + + @Override + public void onConnected() { + if (DEBUG) Slog.d(TAG, "onConnected"); + mConnected = true; + final IntentFilter filter = new IntentFilter(); + filter.addAction(ENTER_ACTION); + filter.addAction(EXIT_ACTION); + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + mContext.registerReceiver(mReceiver, filter); + init(); + } + + @Override + public void onDestroy() { + if (DEBUG) Slog.d(TAG, "onDestroy"); + mConnected = false; + } + + @Override + public void onRequestConditions(int relevance) { + if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance); + if ((relevance & Condition.FLAG_RELEVANT_NOW) != 0) { + if (mInDowntime && mConfig != null) { + notifyCondition(createCondition(mConfig.toDowntimeInfo(), Condition.STATE_TRUE)); + } + } + } + + @Override + public void onSubscribe(Uri conditionId) { + if (DEBUG) Slog.d(TAG, "onSubscribe conditionId=" + conditionId); + final DowntimeInfo downtime = ZenModeConfig.tryParseDowntimeConditionId(conditionId); + if (downtime != null && mConfig != null) { + final int state = mConfig.toDowntimeInfo().equals(downtime) && mInDowntime + ? Condition.STATE_TRUE : Condition.STATE_FALSE; + if (DEBUG) Slog.d(TAG, "notify condition state: " + Condition.stateToString(state)); + notifyCondition(createCondition(downtime, state)); + } + } + + @Override + public void onUnsubscribe(Uri conditionId) { + if (DEBUG) Slog.d(TAG, "onUnsubscribe conditionId=" + conditionId); + } + + public void setConfig(ZenModeConfig config) { + if (Objects.equals(mConfig, config)) return; + if (DEBUG) Slog.d(TAG, "setConfig"); + mConfig = config; + if (mConnected) { + init(); + } + } + + public boolean isInDowntime() { + return mInDowntime; + } + + public Condition createCondition(DowntimeInfo downtime, int state) { + if (downtime == null) return null; + final Uri id = ZenModeConfig.toDowntimeConditionId(downtime); + final String skeleton = DateFormat.is24HourFormat(mContext) ? "Hm" : "hma"; + final Locale locale = Locale.getDefault(); + final String pattern = DateFormat.getBestDateTimePattern(locale, skeleton); + final long time = getTime(System.currentTimeMillis(), downtime.endHour, downtime.endMinute); + final String formatted = new SimpleDateFormat(pattern, locale).format(new Date(time)); + final String summary = mContext.getString(R.string.downtime_condition_summary, formatted); + return new Condition(id, summary, "", "", 0, state, Condition.FLAG_RELEVANT_NOW); + } + + public boolean isDowntimeCondition(Condition condition) { + return condition != null && ZenModeConfig.isValidDowntimeConditionId(condition.id); + } + + private void init() { + updateDays(); + reevaluateDowntime(); + updateAlarms(); + } + + private void updateDays() { + mDays.clear(); + if (mConfig != null) { + final int[] days = ZenModeConfig.tryParseDays(mConfig.sleepMode); + for (int i = 0; days != null && i < days.length; i++) { + mDays.add(days[i]); + } + } + } + + private boolean isInDowntime(long time) { + if (mConfig == null || mDays.size() == 0) return false; + final long start = getTime(time, mConfig.sleepStartHour, mConfig.sleepStartMinute); + long end = getTime(time, mConfig.sleepEndHour, mConfig.sleepEndMinute); + if (start == end) return false; + if (end < start) { + end = addDays(end, 1); + } + return isInDowntime(-1, time, start, end) || isInDowntime(0, time, start, end); + } + + private boolean isInDowntime(int daysOffset, long time, long start, long end) { + final int day = ((getDayOfWeek(time) + daysOffset - 1) % Calendar.SATURDAY) + 1; + start = addDays(start, daysOffset); + end = addDays(end, daysOffset); + return mDays.contains(day) && time >= start && time < end; + } + + private void reevaluateDowntime() { + final boolean inDowntime = isInDowntime(System.currentTimeMillis()); + if (DEBUG) Slog.d(TAG, "inDowntime=" + inDowntime); + if (inDowntime == mInDowntime) return; + Slog.i(TAG, (inDowntime ? "Entering" : "Exiting" ) + " downtime"); + mInDowntime = inDowntime; + ZenLog.traceDowntime(mInDowntime, getDayOfWeek(System.currentTimeMillis()), mDays); + fireDowntimeChanged(); + } + + private void fireDowntimeChanged() { + if (mCallback != null) { + mCallback.onDowntimeChanged(mInDowntime); + } + } + + private void updateAlarms() { + if (mConfig == null) return; + updateAlarm(ENTER_ACTION, ENTER_CODE, mConfig.sleepStartHour, mConfig.sleepStartMinute); + updateAlarm(EXIT_ACTION, EXIT_CODE, mConfig.sleepEndHour, mConfig.sleepEndMinute); + } + + private int getDayOfWeek(long time) { + mCalendar.setTimeInMillis(time); + return mCalendar.get(Calendar.DAY_OF_WEEK); + } + + private long getTime(long millis, int hour, int min) { + mCalendar.setTimeInMillis(millis); + mCalendar.set(Calendar.HOUR_OF_DAY, hour); + mCalendar.set(Calendar.MINUTE, min); + mCalendar.set(Calendar.SECOND, 0); + mCalendar.set(Calendar.MILLISECOND, 0); + return mCalendar.getTimeInMillis(); + } + + private long addDays(long time, int days) { + mCalendar.setTimeInMillis(time); + mCalendar.add(Calendar.DATE, days); + return mCalendar.getTimeInMillis(); + } + + private void updateAlarm(String action, int requestCode, int hr, int min) { + final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + final long now = System.currentTimeMillis(); + mCalendar.setTimeInMillis(now); + mCalendar.set(Calendar.HOUR_OF_DAY, hr); + mCalendar.set(Calendar.MINUTE, min); + mCalendar.set(Calendar.SECOND, 0); + mCalendar.set(Calendar.MILLISECOND, 0); + long time = mCalendar.getTimeInMillis(); + if (time <= now) { + time = addDays(time, 1); + } + final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, requestCode, + new Intent(action).putExtra(EXTRA_TIME, time), PendingIntent.FLAG_UPDATE_CURRENT); + alarms.cancel(pendingIntent); + if (mConfig.sleepMode != null) { + if (DEBUG) Slog.d(TAG, String.format("Scheduling %s for %s, %s in the future, now=%s", + action, ts(time), time - now, ts(now))); + alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); + } + } + + private static String ts(long time) { + return new Date(time) + " (" + time + ")"; + } + + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + final long now = System.currentTimeMillis(); + if (ENTER_ACTION.equals(action) || EXIT_ACTION.equals(action)) { + final long schTime = intent.getLongExtra(EXTRA_TIME, 0); + if (DEBUG) Slog.d(TAG, String.format("%s scheduled for %s, fired at %s, delta=%s", + action, ts(schTime), ts(now), now - schTime)); + } else { + if (DEBUG) Slog.d(TAG, action + " fired at " + now); + } + reevaluateDowntime(); + updateAlarms(); + } + }; + + public interface Callback { + void onDowntimeChanged(boolean inDowntime); + } +} diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 36be21fd28d5a..f647037e14d74 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -41,6 +41,7 @@ import android.os.UserManager; import android.provider.Settings; import android.text.TextUtils; import android.util.ArraySet; +import android.util.Log; import android.util.Slog; import android.util.SparseArray; @@ -64,7 +65,7 @@ import java.util.Set; */ abstract public class ManagedServices { protected final String TAG = getClass().getSimpleName(); - protected static final boolean DEBUG = true; + protected final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final String ENABLED_SERVICES_SEPARATOR = ":"; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index d6afe68fb213c..f2ac963a26d54 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1351,11 +1351,11 @@ public class NotificationManagerService extends SystemService { } @Override - public void setZenModeCondition(Uri conditionId) { + public void setZenModeCondition(Condition condition) { enforceSystemOrSystemUI("INotificationManager.setZenModeCondition"); final long identity = Binder.clearCallingIdentity(); try { - mConditionProviders.setZenModeCondition(conditionId, "binderCall"); + mConditionProviders.setZenModeCondition(condition, "binderCall"); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java index b22ed2dfe1e5a..525f5f858631b 100644 --- a/services/core/java/com/android/server/notification/ZenLog.java +++ b/services/core/java/com/android/server/notification/ZenLog.java @@ -22,8 +22,10 @@ import android.net.Uri; import android.os.Build; import android.os.RemoteException; import android.provider.Settings.Global; +import android.service.notification.Condition; import android.service.notification.IConditionProvider; import android.service.notification.ZenModeConfig; +import android.util.ArraySet; import android.util.Slog; import java.io.PrintWriter; @@ -52,13 +54,14 @@ public class ZenLog { private static final int TYPE_ALLOW_DISABLE = 2; private static final int TYPE_SET_RINGER_MODE = 3; private static final int TYPE_DOWNTIME = 4; - private static final int TYPE_ZEN_MODE = 5; - private static final int TYPE_EXIT_CONDITION = 6; - private static final int TYPE_SUBSCRIBE = 7; - private static final int TYPE_UNSUBSCRIBE = 8; - private static final int TYPE_CONFIG = 9; - private static final int TYPE_FOLLOW_RINGER_MODE = 10; - private static final int TYPE_NOT_INTERCEPTED = 11; + private static final int TYPE_SET_ZEN_MODE = 5; + private static final int TYPE_UPDATE_ZEN_MODE = 6; + private static final int TYPE_EXIT_CONDITION = 7; + private static final int TYPE_SUBSCRIBE = 8; + private static final int TYPE_UNSUBSCRIBE = 9; + private static final int TYPE_CONFIG = 10; + private static final int TYPE_FOLLOW_RINGER_MODE = 11; + private static final int TYPE_NOT_INTERCEPTED = 12; private static int sNext; private static int sSize; @@ -82,17 +85,20 @@ public class ZenLog { append(TYPE_SET_RINGER_MODE, ringerModeToString(ringerMode)); } - public static void traceDowntime(boolean enter, int day, int[] days) { - append(TYPE_DOWNTIME, enter + ",day=" + day + ",days=" + (days != null ? Arrays.asList(days) - : null)); + public static void traceDowntime(boolean inDowntime, int day, ArraySet days) { + append(TYPE_DOWNTIME, inDowntime + ",day=" + day + ",days=" + days); + } + + public static void traceSetZenMode(int mode, String reason) { + append(TYPE_SET_ZEN_MODE, zenModeToString(mode) + "," + reason); } public static void traceUpdateZenMode(int fromMode, int toMode) { - append(TYPE_ZEN_MODE, zenModeToString(fromMode) + " -> " + zenModeToString(toMode)); + append(TYPE_UPDATE_ZEN_MODE, zenModeToString(fromMode) + " -> " + zenModeToString(toMode)); } - public static void traceExitCondition(Uri id, ComponentName component, String reason) { - append(TYPE_EXIT_CONDITION, id + "," + componentToString(component) + "," + reason); + public static void traceExitCondition(Condition c, ComponentName component, String reason) { + append(TYPE_EXIT_CONDITION, c + "," + componentToString(component) + "," + reason); } public static void traceSubscribe(Uri uri, IConditionProvider provider, RemoteException e) { @@ -122,7 +128,8 @@ public class ZenLog { case TYPE_ALLOW_DISABLE: return "allow_disable"; case TYPE_SET_RINGER_MODE: return "set_ringer_mode"; case TYPE_DOWNTIME: return "downtime"; - case TYPE_ZEN_MODE: return "zen_mode"; + case TYPE_SET_ZEN_MODE: return "set_zen_mode"; + case TYPE_UPDATE_ZEN_MODE: return "update_zen_mode"; case TYPE_EXIT_CONDITION: return "exit_condition"; case TYPE_SUBSCRIBE: return "subscribe"; case TYPE_UNSUBSCRIBE: return "unsubscribe"; diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 9282283b87d8d..b7b5f98119e82 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -20,10 +20,8 @@ import static android.media.AudioAttributes.USAGE_ALARM; import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; import static android.media.AudioAttributes.USAGE_UNKNOWN; -import android.app.AlarmManager; import android.app.AppOpsManager; import android.app.Notification; -import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -44,6 +42,7 @@ import android.provider.Settings.Secure; import android.service.notification.NotificationListenerService; import android.service.notification.ZenModeConfig; import android.telecomm.TelecommManager; +import android.util.Log; import android.util.Slog; import com.android.internal.R; @@ -57,8 +56,6 @@ import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; import java.util.Objects; /** @@ -66,12 +63,7 @@ import java.util.Objects; */ public class ZenModeHelper { private static final String TAG = "ZenModeHelper"; - - private static final String ACTION_ENTER_ZEN = "enter_zen"; - private static final int REQUEST_CODE_ENTER = 100; - private static final String ACTION_EXIT_ZEN = "exit_zen"; - private static final int REQUEST_CODE_EXIT = 101; - private static final String EXTRA_TIME = "time"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final Context mContext; private final Handler mHandler; @@ -96,10 +88,8 @@ public class ZenModeHelper { mSettingsObserver.observe(); final IntentFilter filter = new IntentFilter(); - filter.addAction(ACTION_ENTER_ZEN); - filter.addAction(ACTION_EXIT_ZEN); filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); - mContext.registerReceiver(new ZenBroadcastReceiver(), filter); + mContext.registerReceiver(mReceiver, filter); } public static ZenModeConfig readDefaultConfig(Resources resources) { @@ -156,7 +146,7 @@ public class ZenModeHelper { public void requestFromListener(int hints) { final int newZen = zenFromListenerHint(hints, -1); if (newZen != -1) { - setZenMode(newZen); + setZenMode(newZen, "listener"); } } @@ -208,7 +198,8 @@ public class ZenModeHelper { return mZenMode; } - public void setZenMode(int zenModeValue) { + public void setZenMode(int zenModeValue, String reason) { + ZenLog.traceSetZenMode(zenModeValue, reason); Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zenModeValue); } @@ -216,9 +207,6 @@ public class ZenModeHelper { final int mode = Global.getInt(mContext.getContentResolver(), Global.ZEN_MODE, Global.ZEN_MODE_OFF); if (mode != mZenMode) { - Slog.d(TAG, String.format("updateZenMode: %s -> %s", - Global.zenModeToString(mZenMode), - Global.zenModeToString(mode))); ZenLog.traceUpdateZenMode(mZenMode, mode); } mZenMode = mode; @@ -255,12 +243,12 @@ public class ZenModeHelper { if (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) { if (ringerMode != AudioManager.RINGER_MODE_SILENT) { mPreviousRingerMode = ringerMode; - Slog.d(TAG, "Silencing ringer"); + if (DEBUG) Slog.d(TAG, "Silencing ringer"); forcedRingerMode = AudioManager.RINGER_MODE_SILENT; } } else { if (ringerMode == AudioManager.RINGER_MODE_SILENT) { - Slog.d(TAG, "Unsilencing ringer"); + if (DEBUG) Slog.d(TAG, "Unsilencing ringer"); forcedRingerMode = mPreviousRingerMode != -1 ? mPreviousRingerMode : AudioManager.RINGER_MODE_NORMAL; mPreviousRingerMode = -1; @@ -318,7 +306,6 @@ public class ZenModeHelper { dispatchOnConfigChanged(); final String val = Integer.toString(mConfig.hashCode()); Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val); - updateAlarms(); updateZenMode(); return true; } @@ -339,7 +326,7 @@ public class ZenModeHelper { } if (newZen != -1) { ZenLog.traceFollowRingerMode(ringerMode, mZenMode, newZen); - setZenMode(newZen); + setZenMode(newZen, "ringerMode"); } } } @@ -377,7 +364,7 @@ public class ZenModeHelper { final TelecommManager telecomm = (TelecommManager) mContext.getSystemService(Context.TELECOMM_SERVICE); mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null; - Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp); + if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp); } return pkg != null && mDefaultPhoneApp != null && pkg.equals(mDefaultPhoneApp.getPackageName()); @@ -409,40 +396,6 @@ public class ZenModeHelper { } } - private void updateAlarms() { - updateAlarm(ACTION_ENTER_ZEN, REQUEST_CODE_ENTER, - mConfig.sleepStartHour, mConfig.sleepStartMinute); - updateAlarm(ACTION_EXIT_ZEN, REQUEST_CODE_EXIT, - mConfig.sleepEndHour, mConfig.sleepEndMinute); - } - - private void updateAlarm(String action, int requestCode, int hr, int min) { - final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - final long now = System.currentTimeMillis(); - final Calendar c = Calendar.getInstance(); - c.setTimeInMillis(now); - c.set(Calendar.HOUR_OF_DAY, hr); - c.set(Calendar.MINUTE, min); - c.set(Calendar.SECOND, 0); - c.set(Calendar.MILLISECOND, 0); - if (c.getTimeInMillis() <= now) { - c.add(Calendar.DATE, 1); - } - final long time = c.getTimeInMillis(); - final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, requestCode, - new Intent(action).putExtra(EXTRA_TIME, time), PendingIntent.FLAG_UPDATE_CURRENT); - alarms.cancel(pendingIntent); - if (mConfig.sleepMode != null) { - Slog.d(TAG, String.format("Scheduling %s for %s, %s in the future, now=%s", - action, ts(time), time - now, ts(now))); - alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); - } - } - - private static String ts(long time) { - return new Date(time) + " (" + time + ")"; - } - private final Runnable mRingerModeChanged = new Runnable() { @Override public void run() { @@ -475,47 +428,12 @@ public class ZenModeHelper { } } - private class ZenBroadcastReceiver extends BroadcastReceiver { - private final Calendar mCalendar = Calendar.getInstance(); - + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (ACTION_ENTER_ZEN.equals(intent.getAction())) { - setZenMode(intent, Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS); - } else if (ACTION_EXIT_ZEN.equals(intent.getAction())) { - setZenMode(intent, Global.ZEN_MODE_OFF); - } else if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(intent.getAction())) { - mHandler.post(mRingerModeChanged); - } + mHandler.post(mRingerModeChanged); } - - private void setZenMode(Intent intent, int zenModeValue) { - final long schTime = intent.getLongExtra(EXTRA_TIME, 0); - final long now = System.currentTimeMillis(); - Slog.d(TAG, String.format("%s scheduled for %s, fired at %s, delta=%s", - intent.getAction(), ts(schTime), ts(now), now - schTime)); - - final int[] days = ZenModeConfig.tryParseDays(mConfig.sleepMode); - boolean enter = false; - final int day = getDayOfWeek(schTime); - if (days != null) { - for (int i = 0; i < days.length; i++) { - if (days[i] == day) { - enter = true; - ZenModeHelper.this.setZenMode(zenModeValue); - break; - } - } - } - ZenLog.traceDowntime(enter, day, days); - updateAlarms(); - } - - private int getDayOfWeek(long time) { - mCalendar.setTimeInMillis(time); - return mCalendar.get(Calendar.DAY_OF_WEEK); - } - } + }; public static class Callback { void onConfigChanged() {}