summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/INotificationManager.aidl1
-rw-r--r--core/java/android/app/NotificationManager.java12
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java24
-rwxr-xr-xcore/res/res/values/config.xml10
-rwxr-xr-xcore/res/res/values/symbols.xml2
-rw-r--r--packages/SystemUI/res/layout/zen_mode_panel.xml1
-rw-r--r--packages/SystemUI/res/values/config.xml4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java325
-rw-r--r--services/core/java/com/android/server/notification/ConditionProviders.java157
-rw-r--r--services/core/java/com/android/server/notification/DowntimeCalendar.java113
-rw-r--r--services/core/java/com/android/server/notification/DowntimeConditionProvider.java341
-rw-r--r--services/core/java/com/android/server/notification/NextAlarmConditionProvider.java124
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java6
-rw-r--r--services/core/java/com/android/server/notification/PropConfig.java33
-rw-r--r--services/core/java/com/android/server/notification/ZenLog.java8
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java10
17 files changed, 822 insertions, 384 deletions
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index bdcff38..88b9080 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -71,6 +71,7 @@ interface INotificationManager
ComponentName getEffectsSuppressor();
boolean matchesCallFilter(in Bundle extras);
+ boolean isSystemConditionProviderEnabled(String path);
ZenModeConfig getZenModeConfig();
boolean setZenModeConfig(in ZenModeConfig config);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 7dc1ad6..cf54107 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -264,5 +264,17 @@ public class NotificationManager
}
}
+ /**
+ * @hide
+ */
+ public boolean isSystemConditionProviderEnabled(String path) {
+ INotificationManager service = getService();
+ try {
+ return service.isSystemConditionProviderEnabled(path);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
private Context mContext;
}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index ce28d0a..979a01b 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -64,7 +64,7 @@ public class ZenModeConfig implements Parcelable {
public static final int[] MINUTE_BUCKETS = new int[] { 15, 30, 45, 60, 120, 180, 240, 480 };
private static final int SECONDS_MS = 1000;
private static final int MINUTES_MS = 60 * SECONDS_MS;
- private static final int ZERO_VALUE_MS = 20 * SECONDS_MS;
+ private static final int ZERO_VALUE_MS = 10 * SECONDS_MS;
private static final boolean DEFAULT_ALLOW_EVENTS = true;
@@ -471,6 +471,8 @@ public class ZenModeConfig implements Parcelable {
downtime.startMinute = sleepStartMinute;
downtime.endHour = sleepEndHour;
downtime.endMinute = sleepEndMinute;
+ downtime.mode = sleepMode;
+ downtime.none = sleepNone;
return downtime;
}
@@ -510,7 +512,7 @@ public class ZenModeConfig implements Parcelable {
public 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 final String COUNTDOWN_PATH = "countdown";
public static Uri toCountdownConditionId(long time) {
return new Uri.Builder().scheme(Condition.SCHEME)
@@ -536,8 +538,9 @@ public class ZenModeConfig implements Parcelable {
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";
+ // Built-in downtime conditions
+ // e.g. condition://android/downtime?start=10.00&end=7.00&mode=days%3A5%2C6&none=false
+ public static final String DOWNTIME_PATH = "downtime";
public static Uri toDowntimeConditionId(DowntimeInfo downtime) {
return new Uri.Builder().scheme(Condition.SCHEME)
@@ -545,6 +548,8 @@ public class ZenModeConfig implements Parcelable {
.appendPath(DOWNTIME_PATH)
.appendQueryParameter("start", downtime.startHour + "." + downtime.startMinute)
.appendQueryParameter("end", downtime.endHour + "." + downtime.endMinute)
+ .appendQueryParameter("mode", downtime.mode)
+ .appendQueryParameter("none", Boolean.toString(downtime.none))
.build();
}
@@ -562,6 +567,8 @@ public class ZenModeConfig implements Parcelable {
downtime.startMinute = start[1];
downtime.endHour = end[0];
downtime.endMinute = end[1];
+ downtime.mode = conditionId.getQueryParameter("mode");
+ downtime.none = Boolean.toString(true).equals(conditionId.getQueryParameter("none"));
return downtime;
}
@@ -583,6 +590,8 @@ public class ZenModeConfig implements Parcelable {
public int startMinute; // 0-59
public int endHour;
public int endMinute;
+ public String mode;
+ public boolean none;
@Override
public int hashCode() {
@@ -596,7 +605,12 @@ public class ZenModeConfig implements Parcelable {
return startHour == other.startHour
&& startMinute == other.startMinute
&& endHour == other.endHour
- && endMinute == other.endMinute;
+ && endMinute == other.endMinute
+ && Objects.equals(mode, other.mode)
+ && none == other.none;
}
}
+
+ // built-in next alarm conditions
+ public static final String NEXT_ALARM_PATH = "next_alarm";
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d013ee1..d0c612d 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1927,9 +1927,19 @@
-->
<integer name="config_LTE_RSRP_threshold_type">1</integer>
+ <!-- Enabled built-in zen mode condition providers -->
+ <string-array translatable="false" name="config_system_condition_providers">
+ <item>countdown</item>
+ <item>downtime</item>
+ <item>next_alarm</item>
+ </string-array>
+
<!-- Show the next-alarm as a zen exit condition if it occurs in the next n hours. -->
<integer name="config_next_alarm_condition_lookahead_threshold_hrs">12</integer>
+ <!-- Show downtime as a zen exit condition if it starts in the next n hours. -->
+ <integer name="config_downtime_condition_lookahead_threshold_hrs">4</integer>
+
<!-- Flags enabling default window features. See Window.java -->
<bool name="config_defaultWindowFeatureOptionsPanel">true</bool>
<bool name="config_defaultWindowFeatureContextMenu">true</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 5df3653..9845096 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2017,7 +2017,9 @@
<java-symbol type="string" name="zen_mode_until" />
<java-symbol type="string" name="zen_mode_next_alarm_summary" />
<java-symbol type="string" name="zen_mode_next_alarm_line_one" />
+ <java-symbol type="array" name="config_system_condition_providers" />
<java-symbol type="integer" name="config_next_alarm_condition_lookahead_threshold_hrs" />
+ <java-symbol type="integer" name="config_downtime_condition_lookahead_threshold_hrs" />
<java-symbol type="string" name="muted_by" />
<java-symbol type="string" name="item_is_selected" />
diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml
index 922f90d..27353ff 100644
--- a/packages/SystemUI/res/layout/zen_mode_panel.xml
+++ b/packages/SystemUI/res/layout/zen_mode_panel.xml
@@ -23,6 +23,7 @@
android:orientation="vertical" >
<FrameLayout
+ android:id="@+id/zen_buttons_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="8dp"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 98f03b5..4d76f38 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -287,8 +287,8 @@
<!-- Number of times to show the strong alarm warning text in the volume dialog -->
<integer name="zen_mode_alarm_warning_threshold">5</integer>
- <!-- Maximum number of optional conditions to display in the zen mode selection panel -->
- <integer name="zen_mode_max_conditions">3</integer>
+ <!-- Maximum number of total conditions to display in the zen mode selection panel -->
+ <integer name="zen_mode_max_conditions">5</integer>
<!-- Enable the default volume dialog -->
<bool name="enable_volume_ui">true</bool>
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index d3a8fc0..acdcfc1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -124,8 +124,7 @@ public class VolumePanel extends Handler implements DemoMode {
private static final int MSG_ZEN_MODE_AVAILABLE_CHANGED = 13;
private static final int MSG_USER_ACTIVITY = 14;
private static final int MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED = 15;
- private static final int MSG_ZEN_MODE_CHANGED = 16;
- private static final int MSG_INTERNAL_RINGER_MODE_CHANGED = 17;
+ private static final int MSG_INTERNAL_RINGER_MODE_CHANGED = 16;
// Pseudo stream type for master volume
private static final int STREAM_MASTER = -100;
@@ -511,6 +510,9 @@ public class VolumePanel extends Handler implements DemoMode {
pw.println();
}
}
+ if (mZenPanel != null) {
+ mZenPanel.dump(fd, pw, args);
+ }
}
private void initZenModePanel() {
@@ -723,7 +725,7 @@ public class VolumePanel extends Handler implements DemoMode {
mSliderPanel.addView(active.group);
mActiveStreamType = activeStreamType;
active.group.setVisibility(View.VISIBLE);
- updateSlider(active);
+ updateSlider(active, true /*forceReloadIcon*/);
updateTimeoutDelay();
updateZenPanelVisible();
}
@@ -799,11 +801,12 @@ public class VolumePanel extends Handler implements DemoMode {
}
/** Update the mute and progress state of a slider */
- private void updateSlider(StreamControl sc) {
+ private void updateSlider(StreamControl sc, boolean forceReloadIcon) {
updateSliderProgress(sc, -1);
final boolean muted = isMuted(sc.streamType);
- // Force reloading the image resource
- sc.icon.setImageDrawable(null);
+ if (forceReloadIcon) {
+ sc.icon.setImageDrawable(null);
+ }
updateSliderIcon(sc, muted);
updateSliderEnabled(sc, muted, false);
updateSliderSuppressor(sc);
@@ -907,11 +910,18 @@ public class VolumePanel extends Handler implements DemoMode {
}
}
- public void updateStates() {
+ private void updateStates() {
final int count = mSliderPanel.getChildCount();
for (int i = 0; i < count; i++) {
StreamControl sc = (StreamControl) mSliderPanel.getChildAt(i).getTag();
- updateSlider(sc);
+ updateSlider(sc, true /*forceReloadIcon*/);
+ }
+ }
+
+ private void updateActiveSlider() {
+ final StreamControl active = mStreamControls.get(mActiveStreamType);
+ if (active != null) {
+ updateSlider(active, false /*forceReloadIcon*/);
}
}
@@ -1449,12 +1459,11 @@ public class VolumePanel extends Handler implements DemoMode {
break;
}
- case MSG_ZEN_MODE_CHANGED:
case MSG_RINGER_MODE_CHANGED:
case MSG_INTERNAL_RINGER_MODE_CHANGED:
case MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED: {
if (isShowing()) {
- updateStates();
+ updateActiveSlider();
}
break;
}
@@ -1563,10 +1572,6 @@ public class VolumePanel extends Handler implements DemoMode {
mNotificationEffectsSuppressor = mZenController.getEffectsSuppressor();
sendEmptyMessage(MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED);
}
-
- public void onZenChanged(int zen) {
- sendEmptyMessage(MSG_ZEN_MODE_CHANGED);
- }
};
private final MediaController.Callback mMediaControllerCb = new MediaController.Callback() {
@@ -1591,6 +1596,7 @@ public class VolumePanel extends Handler implements DemoMode {
public void start(StreamControl sc) {
if (sc == null) throw new IllegalArgumentException();
+ if (LOGD) Log.d(mTag, "Secondary icon animation start");
if (mTarget != null) {
cancel();
}
@@ -1643,6 +1649,7 @@ public class VolumePanel extends Handler implements DemoMode {
@Override
public void run() {
if (mTarget == null) return;
+ if (LOGD) Log.d(mTag, "Secondary icon animation complete, show notification slider");
mAudioManager.forceVolumeControlStream(StreamResources.NotificationStream.streamType);
mAudioManager.adjustStreamVolume(StreamResources.NotificationStream.streamType,
AudioManager.ADJUST_SAME, AudioManager.FLAG_SHOW_UI);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index 6ed24e0..e250ec7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -17,13 +17,16 @@
package com.android.systemui.volume;
import android.animation.LayoutTransition;
+import android.animation.LayoutTransition.TransitionListener;
import android.app.ActivityManager;
+import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Resources;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -32,6 +35,7 @@ import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Log;
import android.util.MathUtils;
@@ -50,6 +54,8 @@ import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.ZenModeController;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Objects;
@@ -67,8 +73,7 @@ public class ZenModePanel extends LinearLayout {
private static final int MAX_BUCKET_MINUTES = MINUTE_BUCKETS[MINUTE_BUCKETS.length - 1];
private static final int DEFAULT_BUCKET_INDEX = Arrays.binarySearch(MINUTE_BUCKETS, 60);
private static final int FOREVER_CONDITION_INDEX = 0;
- private static final int TIME_CONDITION_INDEX = 1;
- private static final int FIRST_CONDITION_INDEX = 2;
+ private static final int COUNTDOWN_CONDITION_INDEX = 1;
public static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
@@ -81,6 +86,10 @@ public class ZenModePanel extends LinearLayout {
private final int mSubheadColor;
private final Interpolator mInterpolator;
private final int mMaxConditions;
+ private final int mMaxOptionalConditions;
+ private final boolean mCountdownConditionSupported;
+ private final int mFirstConditionIndex;
+ private final TransitionHelper mTransitionHelper = new TransitionHelper();
private String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this));
@@ -98,7 +107,7 @@ public class ZenModePanel extends LinearLayout {
private String mExitConditionText;
private int mBucketIndex = -1;
private boolean mExpanded;
- private boolean mHidden = false;
+ private boolean mHidden;
private int mSessionZen;
private int mAttachedZen;
private boolean mAttached;
@@ -117,11 +126,30 @@ public class ZenModePanel extends LinearLayout {
mSubheadColor = res.getColor(R.color.qs_subhead);
mInterpolator = AnimationUtils.loadInterpolator(mContext,
com.android.internal.R.interpolator.fast_out_slow_in);
+ mCountdownConditionSupported = NotificationManager.from(mContext)
+ .isSystemConditionProviderEnabled(ZenModeConfig.COUNTDOWN_PATH);
+ final int countdownDelta = mCountdownConditionSupported ? 1 : 0;
+ mFirstConditionIndex = COUNTDOWN_CONDITION_INDEX + countdownDelta;
+ final int minConditions = 1 /*forever*/ + countdownDelta;
mMaxConditions = MathUtils.constrain(res.getInteger(R.integer.zen_mode_max_conditions),
- 1, 100);
+ minConditions, 100);
+ mMaxOptionalConditions = mMaxConditions - minConditions;
if (DEBUG) Log.d(mTag, "new ZenModePanel");
}
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("ZenModePanel state:");
+ pw.print(" mCountdownConditionSupported="); pw.println(mCountdownConditionSupported);
+ pw.print(" mMaxConditions="); pw.println(mMaxConditions);
+ pw.print(" mRequestingConditions="); pw.println(mRequestingConditions);
+ pw.print(" mAttached="); pw.println(mAttached);
+ pw.print(" mHidden="); pw.println(mHidden);
+ pw.print(" mExpanded="); pw.println(mExpanded);
+ pw.print(" mSessionZen="); pw.println(mSessionZen);
+ pw.print(" mAttachedZen="); pw.println(mAttachedZen);
+ mTransitionHelper.dump(fd, pw, args);
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -135,6 +163,9 @@ public class ZenModePanel extends LinearLayout {
Global.ZEN_MODE_OFF);
mZenButtons.setCallback(mZenButtonsCallback);
+ final ViewGroup zenButtonsContainer = (ViewGroup) findViewById(R.id.zen_buttons_container);
+ zenButtonsContainer.setLayoutTransition(newLayoutTransition(null));
+
mZenSubhead = findViewById(R.id.zen_subhead);
mZenSubheadCollapsed = (TextView) findViewById(R.id.zen_subhead_collapsed);
@@ -159,15 +190,22 @@ public class ZenModePanel extends LinearLayout {
Interaction.register(mMoreSettings, mInteractionCallback);
mZenConditions = (LinearLayout) findViewById(R.id.zen_conditions);
- setLayoutTransition(newLayoutTransition());
+ for (int i = 0; i < mMaxConditions; i++) {
+ mZenConditions.addView(mInflater.inflate(R.layout.zen_mode_condition, this, false));
+ }
+
+ setLayoutTransition(newLayoutTransition(mTransitionHelper));
}
- private LayoutTransition newLayoutTransition() {
+ private LayoutTransition newLayoutTransition(TransitionListener listener) {
final LayoutTransition transition = new LayoutTransition();
transition.disableTransitionType(LayoutTransition.DISAPPEARING);
transition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
- transition.setInterpolator(LayoutTransition.APPEARING, mInterpolator);
+ transition.disableTransitionType(LayoutTransition.APPEARING);
transition.setInterpolator(LayoutTransition.CHANGE_APPEARING, mInterpolator);
+ if (listener != null) {
+ transition.addTransitionListener(listener);
+ }
return transition;
}
@@ -175,11 +213,11 @@ public class ZenModePanel extends LinearLayout {
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (DEBUG) Log.d(mTag, "onAttachedToWindow");
- ((ViewGroup) getParent()).setLayoutTransition(newLayoutTransition());
mAttached = true;
mAttachedZen = getSelectedZen(-1);
mSessionZen = mAttachedZen;
- mSessionExitCondition = copy(mExitCondition);
+ mTransitionHelper.clear();
+ setSessionExitCondition(copy(mExitCondition));
refreshExitConditionText();
updateWidgets();
setRequestingConditions(!mHidden);
@@ -193,9 +231,16 @@ public class ZenModePanel extends LinearLayout {
mAttached = false;
mAttachedZen = -1;
mSessionZen = -1;
- mSessionExitCondition = null;
+ setSessionExitCondition(null);
setExpanded(false);
setRequestingConditions(false);
+ mTransitionHelper.clear();
+ }
+
+ private void setSessionExitCondition(Condition condition) {
+ if (Objects.equals(condition, mSessionExitCondition)) return;
+ if (DEBUG) Log.d(mTag, "mSessionExitCondition=" + getConditionId(condition));
+ mSessionExitCondition = condition;
}
public void setHidden(boolean hidden) {
@@ -228,12 +273,17 @@ public class ZenModePanel extends LinearLayout {
}
/** Start or stop requesting relevant zen mode exit conditions */
- private void setRequestingConditions(boolean requesting) {
+ private void setRequestingConditions(final boolean requesting) {
if (mRequestingConditions == requesting) return;
if (DEBUG) Log.d(mTag, "setRequestingConditions " + requesting);
mRequestingConditions = requesting;
if (mController != null) {
- mController.requestConditions(mRequestingConditions);
+ AsyncTask.execute(new Runnable() {
+ @Override
+ public void run() {
+ mController.requestConditions(requesting);
+ }
+ });
}
if (mRequestingConditions) {
mTimeCondition = parseExistingTimeCondition(mExitCondition);
@@ -248,7 +298,7 @@ public class ZenModePanel extends LinearLayout {
mConditions = null; // reset conditions
handleUpdateConditions();
} else {
- mZenConditions.removeAllViews();
+ hideAllConditions();
}
}
@@ -259,7 +309,7 @@ public class ZenModePanel extends LinearLayout {
mSessionZen = getSelectedZen(-1);
handleUpdateZen(mController.getZen());
if (DEBUG) Log.d(mTag, "init mExitCondition=" + mExitCondition);
- mZenConditions.removeAllViews();
+ hideAllConditions();
mController.addCallback(mZenCallback);
}
@@ -270,6 +320,7 @@ public class ZenModePanel extends LinearLayout {
private void setExitCondition(Condition exitCondition) {
if (Objects.equals(mExitCondition, exitCondition)) return;
mExitCondition = exitCondition;
+ if (DEBUG) Log.d(mTag, "mExitCondition=" + getConditionId(mExitCondition));
refreshExitConditionText();
updateWidgets();
}
@@ -290,7 +341,7 @@ public class ZenModePanel extends LinearLayout {
final String forever = mContext.getString(com.android.internal.R.string.zen_mode_forever);
if (mExitCondition == null) {
mExitConditionText = forever;
- } else if (ZenModeConfig.isValidCountdownConditionId(mExitCondition.id)) {
+ } else if (isCountdown(mExitCondition)) {
final Condition condition = parseExistingTimeCondition(mExitCondition);
mExitConditionText = condition != null ? condition.summary : forever;
} else {
@@ -316,6 +367,24 @@ public class ZenModePanel extends LinearLayout {
}
mZenButtons.setSelectedValue(zen);
updateWidgets();
+ handleUpdateConditions();
+ if (mExpanded) {
+ final Condition selected = getSelectedCondition();
+ if (!Objects.equals(mExitCondition, selected)) {
+ select(selected);
+ }
+ }
+ }
+
+ private Condition getSelectedCondition() {
+ final int N = getVisibleConditions();
+ for (int i = 0; i < N; i++) {
+ final ConditionTag tag = getConditionTagAt(i);
+ if (tag != null && tag.rb.isChecked()) {
+ return tag.condition;
+ }
+ }
+ return null;
}
private int getSelectedZen(int defValue) {
@@ -324,6 +393,10 @@ public class ZenModePanel extends LinearLayout {
}
private void updateWidgets() {
+ if (mTransitionHelper.isTransitioning()) {
+ mTransitionHelper.pendingUpdateWidgets();
+ return;
+ }
final int zen = getSelectedZen(Global.ZEN_MODE_OFF);
final boolean zenOff = zen == Global.ZEN_MODE_OFF;
final boolean zenImportant = zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
@@ -371,7 +444,7 @@ public class ZenModePanel extends LinearLayout {
}
private Condition[] trimConditions(Condition[] conditions) {
- if (conditions == null || conditions.length <= mMaxConditions) {
+ if (conditions == null || conditions.length <= mMaxOptionalConditions) {
// no need to trim
return conditions;
}
@@ -384,33 +457,34 @@ public class ZenModePanel extends LinearLayout {
break;
}
}
- final Condition[] rt = Arrays.copyOf(conditions, mMaxConditions);
- if (found >= mMaxConditions) {
+ final Condition[] rt = Arrays.copyOf(conditions, mMaxOptionalConditions);
+ if (found >= mMaxOptionalConditions) {
// found after the first N, promote to the end of the first N
- rt[mMaxConditions - 1] = conditions[found];
+ rt[mMaxOptionalConditions - 1] = conditions[found];
}
return rt;
}
private void handleUpdateConditions() {
+ if (mTransitionHelper.isTransitioning()) {
+ mTransitionHelper.pendingUpdateConditions();
+ return;
+ }
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));
// countdown
- bind(mTimeCondition, mZenConditions.getChildAt(TIME_CONDITION_INDEX));
+ if (mCountdownConditionSupported) {
+ bind(mTimeCondition, mZenConditions.getChildAt(COUNTDOWN_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]);
+ bind(mConditions[i], mZenConditions.getChildAt(mFirstConditionIndex + i));
}
- // ensure downtime exists, if active
- if (isDowntime(mSessionExitCondition) && !foundDowntime) {
- bind(mSessionExitCondition, null);
+ // hide the rest
+ for (int i = mZenConditions.getChildCount() - 1; i > mFirstConditionIndex + conditionCount; i--) {
+ mZenConditions.getChildAt(i).setVisibility(GONE);
}
// ensure something is selected
if (mExpanded) {
@@ -418,78 +492,101 @@ public class ZenModePanel extends LinearLayout {
}
}
- private static boolean isDowntime(Condition c) {
- return ZenModeConfig.isValidDowntimeConditionId(getConditionId(c));
- }
-
private ConditionTag getConditionTagAt(int index) {
return (ConditionTag) mZenConditions.getChildAt(index).getTag();
}
+ private int getVisibleConditions() {
+ int rt = 0;
+ final int N = mZenConditions.getChildCount();
+ for (int i = 0; i < N; i++) {
+ rt += mZenConditions.getChildAt(i).getVisibility() == VISIBLE ? 1 : 0;
+ }
+ return rt;
+ }
+
+ private void hideAllConditions() {
+ final int N = mZenConditions.getChildCount();
+ for (int i = 0; i < N; i++) {
+ mZenConditions.getChildAt(i).setVisibility(GONE);
+ }
+ }
+
private void ensureSelection() {
// are we left without anything selected? if so, set a default
- if (mZenConditions.getChildCount() == 0) return;
- 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).condition);
+ final int visibleConditions = getVisibleConditions();
+ if (visibleConditions == 0) return;
+ for (int i = 0; i < visibleConditions; i++) {
+ final ConditionTag tag = getConditionTagAt(i);
+ if (tag != null && tag.rb.isChecked()) {
+ if (DEBUG) Log.d(mTag, "Not selecting a default, checked=" + tag.condition);
return;
}
}
+ final ConditionTag foreverTag = getConditionTagAt(FOREVER_CONDITION_INDEX);
+ if (foreverTag == null) return;
if (DEBUG) Log.d(mTag, "Selecting a default");
final int favoriteIndex = mPrefs.getMinuteIndex();
- if (favoriteIndex == -1) {
- getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true);
+ if (favoriteIndex == -1 || !mCountdownConditionSupported) {
+ foreverTag.rb.setChecked(true);
} else {
mTimeCondition = ZenModeConfig.toTimeCondition(mContext,
MINUTE_BUCKETS[favoriteIndex], ActivityManager.getCurrentUser());
mBucketIndex = favoriteIndex;
- bind(mTimeCondition, mZenConditions.getChildAt(TIME_CONDITION_INDEX));
- getConditionTagAt(TIME_CONDITION_INDEX).rb.setChecked(true);
+ bind(mTimeCondition, mZenConditions.getChildAt(COUNTDOWN_CONDITION_INDEX));
+ getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.setChecked(true);
}
}
private void handleExitConditionChanged(Condition exitCondition) {
setExitCondition(exitCondition);
if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitCondition);
- final int N = mZenConditions.getChildCount();
+ final int N = getVisibleConditions();
for (int i = 0; i < N; i++) {
final ConditionTag tag = getConditionTagAt(i);
- tag.rb.setChecked(sameConditionId(tag.condition, mExitCondition));
+ if (tag != null) {
+ if (sameConditionId(tag.condition, mExitCondition)) {
+ bind(exitCondition, mZenConditions.getChildAt(i));
+ }
+ }
}
}
- private void bind(final Condition condition, View convertView) {
+ private boolean isCountdown(Condition c) {
+ return c != null && ZenModeConfig.isValidCountdownConditionId(c.id);
+ }
+
+ private void bind(final Condition condition, final View row) {
final boolean enabled = condition == null || condition.state == Condition.STATE_TRUE;
- final View row;
- if (convertView == null) {
- row = mInflater.inflate(R.layout.zen_mode_condition, this, false);
- if (DEBUG) Log.d(mTag, "Adding new condition view for: " + condition);
- mZenConditions.addView(row);
- } else {
- row = convertView;
- }
final ConditionTag tag =
row.getTag() != null ? (ConditionTag) row.getTag() : new ConditionTag();
row.setTag(tag);
+ final boolean first = tag.rb == null;
if (tag.rb == null) {
tag.rb = (RadioButton) row.findViewById(android.R.id.checkbox);
}
tag.condition = condition;
+ final Uri conditionId = getConditionId(tag.condition);
+ if (DEBUG) Log.d(mTag, "bind i=" + mZenConditions.indexOfChild(row) + " first=" + first
+ + " condition=" + conditionId);
tag.rb.setEnabled(enabled);
- if ((mSessionExitCondition != null || mAttachedZen != Global.ZEN_MODE_OFF)
- && sameConditionId(mSessionExitCondition, tag.condition)) {
- tag.rb.setChecked(true);
+ final boolean checked = (mSessionExitCondition != null
+ || mAttachedZen != Global.ZEN_MODE_OFF)
+ && (sameConditionId(mSessionExitCondition, tag.condition)
+ || isCountdown(mSessionExitCondition) && isCountdown(tag.condition));
+ if (checked != tag.rb.isChecked()) {
+ if (DEBUG) Log.d(mTag, "bind checked=" + checked + " condition=" + conditionId);
+ tag.rb.setChecked(checked);
}
tag.rb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (mExpanded && isChecked) {
- if (DEBUG) Log.d(mTag, "onCheckedChanged " + tag.condition);
- final int N = mZenConditions.getChildCount();
+ if (DEBUG) Log.d(mTag, "onCheckedChanged " + conditionId);
+ final int N = getVisibleConditions();
for (int i = 0; i < N; i++) {
- ConditionTag childTag = getConditionTagAt(i);
- if (childTag == tag) continue;
+ final ConditionTag childTag = getConditionTagAt(i);
+ if (childTag == null || childTag == tag) continue;
childTag.rb.setChecked(false);
}
select(tag.condition);
@@ -547,8 +644,10 @@ public class ZenModePanel extends LinearLayout {
}
});
- final long time = ZenModeConfig.tryParseCountdownConditionId(getConditionId(tag.condition));
+ final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
if (time > 0) {
+ button1.setVisibility(VISIBLE);
+ button2.setVisibility(VISIBLE);
if (mBucketIndex > -1) {
button1.setEnabled(mBucketIndex > 0);
button2.setEnabled(mBucketIndex < MINUTE_BUCKETS.length - 1);
@@ -563,16 +662,17 @@ public class ZenModePanel extends LinearLayout {
button1.setAlpha(button1.isEnabled() ? 1f : .5f);
button2.setAlpha(button2.isEnabled() ? 1f : .5f);
} else {
- button1.setVisibility(View.GONE);
- button2.setVisibility(View.GONE);
+ button1.setVisibility(GONE);
+ button2.setVisibility(GONE);
}
// wire up interaction callbacks for newly-added condition rows
- if (convertView == null) {
+ if (first) {
Interaction.register(tag.rb, mInteractionCallback);
Interaction.register(tag.lines, mInteractionCallback);
Interaction.register(button1, mInteractionCallback);
Interaction.register(button2, mInteractionCallback);
}
+ row.setVisibility(VISIBLE);
}
private void announceConditionSelection(ConditionTag tag) {
@@ -629,18 +729,23 @@ public class ZenModePanel extends LinearLayout {
announceConditionSelection(tag);
}
- private void select(Condition condition) {
+ private void select(final Condition condition) {
if (DEBUG) Log.d(mTag, "select " + condition);
if (mController != null) {
- mController.setExitCondition(condition);
+ AsyncTask.execute(new Runnable() {
+ @Override
+ public void run() {
+ mController.setExitCondition(condition);
+ }
+ });
}
setExitCondition(condition);
if (condition == null) {
mPrefs.setMinuteIndex(-1);
- } else if (ZenModeConfig.isValidCountdownConditionId(condition.id) && mBucketIndex != -1) {
+ } else if (isCountdown(condition) && mBucketIndex != -1) {
mPrefs.setMinuteIndex(mBucketIndex);
}
- mSessionExitCondition = copy(condition);
+ setSessionExitCondition(copy(condition));
}
private void fireMoreSettings() {
@@ -784,10 +889,15 @@ public class ZenModePanel extends LinearLayout {
private final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() {
@Override
- public void onSelected(Object value) {
+ public void onSelected(final Object value) {
if (value != null && mZenButtons.isShown()) {
if (DEBUG) Log.d(mTag, "mZenButtonsCallback selected=" + value);
- mController.setZen((Integer) value);
+ AsyncTask.execute(new Runnable() {
+ @Override
+ public void run() {
+ mController.setZen((Integer) value);
+ }
+ });
}
}
@@ -803,4 +913,79 @@ public class ZenModePanel extends LinearLayout {
fireInteraction();
}
};
+
+ private final class TransitionHelper implements TransitionListener, Runnable {
+ private final ArraySet<View> mTransitioningViews = new ArraySet<View>();
+
+ private boolean mTransitioning;
+ private boolean mPendingUpdateConditions;
+ private boolean mPendingUpdateWidgets;
+
+ public void clear() {
+ mTransitioningViews.clear();
+ mPendingUpdateConditions = mPendingUpdateWidgets = false;
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(" TransitionHelper state:");
+ pw.print(" mPendingUpdateConditions="); pw.println(mPendingUpdateConditions);
+ pw.print(" mPendingUpdateWidgets="); pw.println(mPendingUpdateWidgets);
+ pw.print(" mTransitioning="); pw.println(mTransitioning);
+ pw.print(" mTransitioningViews="); pw.println(mTransitioningViews);
+ }
+
+ public void pendingUpdateConditions() {
+ mPendingUpdateConditions = true;
+ }
+
+ public void pendingUpdateWidgets() {
+ mPendingUpdateWidgets = true;
+ }
+
+ public boolean isTransitioning() {
+ return !mTransitioningViews.isEmpty();
+ }
+
+ @Override
+ public void startTransition(LayoutTransition transition,
+ ViewGroup container, View view, int transitionType) {
+ mTransitioningViews.add(view);
+ updateTransitioning();
+ }
+
+ @Override
+ public void endTransition(LayoutTransition transition,
+ ViewGroup container, View view, int transitionType) {
+ mTransitioningViews.remove(view);
+ updateTransitioning();
+ }
+
+ @Override
+ public void run() {
+ if (DEBUG) Log.d(mTag, "TransitionHelper run"
+ + " mPendingUpdateWidgets=" + mPendingUpdateWidgets
+ + " mPendingUpdateConditions=" + mPendingUpdateConditions);
+ if (mPendingUpdateWidgets) {
+ updateWidgets();
+ }
+ if (mPendingUpdateConditions) {
+ handleUpdateConditions();
+ }
+ mPendingUpdateWidgets = mPendingUpdateConditions = false;
+ }
+
+ private void updateTransitioning() {
+ final boolean transitioning = isTransitioning();
+ if (mTransitioning == transitioning) return;
+ mTransitioning = transitioning;
+ if (DEBUG) Log.d(mTag, "TransitionHelper mTransitioning=" + mTransitioning);
+ if (!mTransitioning) {
+ if (mPendingUpdateConditions || mPendingUpdateWidgets) {
+ mHandler.post(this);
+ } else {
+ mPendingUpdateConditions = mPendingUpdateWidgets = false;
+ }
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 5de1a64..64d77c1 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -50,10 +50,11 @@ public class ConditionProviders extends ManagedServices {
private final ArrayMap<IBinder, IConditionListener> mListeners
= new ArrayMap<IBinder, IConditionListener>();
private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>();
- private final CountdownConditionProvider mCountdown = new CountdownConditionProvider();
+ private final ArraySet<String> mSystemConditionProviders;
+ private final CountdownConditionProvider mCountdown;
+ private final DowntimeConditionProvider mDowntime;
+ private final NextAlarmConditionProvider mNextAlarm;
private final NextAlarmTracker mNextAlarmTracker;
- private final DowntimeConditionProvider mDowntime = new DowntimeConditionProvider();
- private final NextAlarmConditionProvider mNextAlarm = new NextAlarmConditionProvider();
private Condition mExitCondition;
private ComponentName mExitConditionComponent;
@@ -63,8 +64,22 @@ public class ConditionProviders extends ManagedServices {
super(context, handler, new Object(), userProfiles);
mZenModeHelper = zenModeHelper;
mZenModeHelper.addCallback(new ZenModeHelperCallback());
+ mSystemConditionProviders = safeSet(PropConfig.getStringArray(mContext,
+ "system.condition.providers",
+ R.array.config_system_condition_providers));
+ final boolean countdown = mSystemConditionProviders.contains(ZenModeConfig.COUNTDOWN_PATH);
+ final boolean downtime = mSystemConditionProviders.contains(ZenModeConfig.DOWNTIME_PATH);
+ final boolean nextAlarm = mSystemConditionProviders.contains(ZenModeConfig.NEXT_ALARM_PATH);
+ mNextAlarmTracker = (downtime || nextAlarm) ? new NextAlarmTracker(mContext) : null;
+ mCountdown = countdown ? new CountdownConditionProvider() : null;
+ mDowntime = downtime ? new DowntimeConditionProvider(this, mNextAlarmTracker,
+ mZenModeHelper) : null;
+ mNextAlarm = nextAlarm ? new NextAlarmConditionProvider(mNextAlarmTracker) : null;
loadZenConfig();
- mNextAlarmTracker = new NextAlarmTracker(context);
+ }
+
+ public boolean isSystemConditionProviderEnabled(String path) {
+ return mSystemConditionProviders.contains(path);
}
@Override
@@ -100,10 +115,19 @@ public class ConditionProviders extends ManagedServices {
}
}
}
- mCountdown.dump(pw, filter);
- mDowntime.dump(pw, filter);
- mNextAlarm.dump(pw, filter);
- mNextAlarmTracker.dump(pw, filter);
+ pw.print(" mSystemConditionProviders: "); pw.println(mSystemConditionProviders);
+ if (mCountdown != null) {
+ mCountdown.dump(pw, filter);
+ }
+ if (mDowntime != null) {
+ mDowntime.dump(pw, filter);
+ }
+ if (mNextAlarm != null) {
+ mNextAlarm.dump(pw, filter);
+ }
+ if (mNextAlarmTracker != null) {
+ mNextAlarmTracker.dump(pw, filter);
+ }
}
@Override
@@ -114,24 +138,32 @@ public class ConditionProviders extends ManagedServices {
@Override
public void onBootPhaseAppsCanStart() {
super.onBootPhaseAppsCanStart();
- mNextAlarmTracker.init();
- 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());
- mNextAlarm.attachBase(mContext);
- registerService(mNextAlarm.asInterface(), NextAlarmConditionProvider.COMPONENT,
- UserHandle.USER_OWNER);
- mNextAlarm.setCallback(new NextAlarmCallback());
+ if (mNextAlarmTracker != null) {
+ mNextAlarmTracker.init();
+ }
+ if (mCountdown != null) {
+ mCountdown.attachBase(mContext);
+ registerService(mCountdown.asInterface(), CountdownConditionProvider.COMPONENT,
+ UserHandle.USER_OWNER);
+ }
+ if (mDowntime != null) {
+ mDowntime.attachBase(mContext);
+ registerService(mDowntime.asInterface(), DowntimeConditionProvider.COMPONENT,
+ UserHandle.USER_OWNER);
+ }
+ if (mNextAlarm != null) {
+ mNextAlarm.attachBase(mContext);
+ registerService(mNextAlarm.asInterface(), NextAlarmConditionProvider.COMPONENT,
+ UserHandle.USER_OWNER);
+ }
}
@Override
public void onUserSwitched() {
super.onUserSwitched();
- mNextAlarmTracker.onUserSwitched();
+ if (mNextAlarmTracker != null) {
+ mNextAlarmTracker.onUserSwitched();
+ }
}
@Override
@@ -171,6 +203,7 @@ public class ConditionProviders extends ManagedServices {
if (!r.component.equals(removed.component)) continue;
if (r.isManual) {
// removing the current manual condition, exit zen
+ onManualConditionClearing();
mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "manualServiceRemoved");
}
if (r.isAutomatic) {
@@ -273,6 +306,7 @@ public class ConditionProviders extends ManagedServices {
} else if (DEBUG) {
Slog.d(TAG, "Exit zen: manual condition false: " + c);
}
+ onManualConditionClearing();
mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF,
"manualConditionExit");
unsubscribeLocked(r);
@@ -304,28 +338,28 @@ public class ConditionProviders extends ManagedServices {
}
}
+ private void ensureRecordExists(Condition condition, IConditionProvider provider,
+ ComponentName component) {
+ // constructed by convention, make sure the record exists...
+ final ConditionRecord r = getRecordLocked(condition.id, component);
+ if (r.info == null) {
+ // ... and is associated with the in-process service
+ r.info = checkServiceTokenLocked(provider);
+ }
+ }
+
public void setZenModeCondition(Condition condition, String reason) {
- if (DEBUG) Slog.d(TAG, "setZenModeCondition " + condition);
+ if (DEBUG) Slog.d(TAG, "setZenModeCondition " + condition + " reason=" + reason);
synchronized(mMutex) {
ComponentName conditionComponent = null;
if (condition != null) {
- if (ZenModeConfig.isValidCountdownConditionId(condition.id)) {
- // constructed by the client, make sure the record exists...
- final ConditionRecord r = getRecordLocked(condition.id,
+ if (mCountdown != null && ZenModeConfig.isValidCountdownConditionId(condition.id)) {
+ ensureRecordExists(condition, mCountdown.asInterface(),
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,
+ if (mDowntime != null && ZenModeConfig.isValidDowntimeConditionId(condition.id)) {
+ ensureRecordExists(condition, mDowntime.asInterface(),
DowntimeConditionProvider.COMPONENT);
- if (r.info == null) {
- // ... and is associated with the in-process service
- r.info = checkServiceTokenLocked(mDowntime.asInterface());
- }
}
}
final int N = mRecords.size();
@@ -370,6 +404,7 @@ public class ConditionProviders extends ManagedServices {
ZenLog.traceSubscribe(r != null ? r.id : null, provider, re);
}
+ @SafeVarargs
private static <T> ArraySet<T> safeSet(T... items) {
final ArraySet<T> rt = new ArraySet<T>();
if (items == null || items.length == 0) return rt;
@@ -485,7 +520,9 @@ public class ConditionProviders extends ManagedServices {
if (changingExit) {
ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, "config");
}
- mDowntime.setConfig(config);
+ if (mDowntime != null) {
+ mDowntime.setConfig(config);
+ }
if (config.conditionComponents == null || config.conditionIds == null
|| config.conditionComponents.length != config.conditionIds.length) {
if (DEBUG) Slog.d(TAG, "loadZenConfig: no conditions");
@@ -538,6 +575,12 @@ public class ConditionProviders extends ManagedServices {
mZenModeHelper.setConfig(config);
}
+ private void onManualConditionClearing() {
+ if (mDowntime != null) {
+ mDowntime.onManualConditionClearing();
+ }
+ }
+
private class ZenModeHelperCallback extends ZenModeHelper.Callback {
@Override
void onConfigChanged() {
@@ -554,46 +597,6 @@ public class ConditionProviders extends ManagedServices {
}
}
- private class DowntimeCallback implements DowntimeConditionProvider.Callback {
- @Override
- public void onDowntimeChanged(int downtimeMode) {
- final int mode = mZenModeHelper.getZenMode();
- final ZenModeConfig config = mZenModeHelper.getConfig();
- final boolean inDowntime = downtimeMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
- || downtimeMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
- final boolean downtimeCurrent = mDowntime.isDowntimeCondition(mExitCondition);
- // enter downtime, or update mode if reconfigured during an active downtime
- if (inDowntime && (mode == Global.ZEN_MODE_OFF || downtimeCurrent) && config != null) {
- final Condition condition = mDowntime.createCondition(config.toDowntimeInfo(),
- config.sleepNone, Condition.STATE_TRUE);
- mZenModeHelper.setZenMode(downtimeMode, "downtimeEnter");
- setZenModeCondition(condition, "downtime");
- }
- // exit downtime
- if (!inDowntime && downtimeCurrent && (mode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
- || mode == Global.ZEN_MODE_NO_INTERRUPTIONS)) {
- mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "downtimeExit");
- }
- }
-
- @Override
- public NextAlarmTracker getNextAlarmTracker() {
- return mNextAlarmTracker;
- }
- }
-
- private class NextAlarmCallback implements NextAlarmConditionProvider.Callback {
- @Override
- public boolean isInDowntime() {
- return mDowntime.isInDowntime();
- }
-
- @Override
- public NextAlarmTracker getNextAlarmTracker() {
- return mNextAlarmTracker;
- }
- }
-
private static class ConditionRecord {
public final Uri id;
public final ComponentName component;
diff --git a/services/core/java/com/android/server/notification/DowntimeCalendar.java b/services/core/java/com/android/server/notification/DowntimeCalendar.java
new file mode 100644
index 0000000..d14fd40
--- /dev/null
+++ b/services/core/java/com/android/server/notification/DowntimeCalendar.java
@@ -0,0 +1,113 @@
+/**
+ * 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 java.util.Calendar;
+import java.util.Objects;
+import java.util.TimeZone;
+
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.DowntimeInfo;
+import android.util.ArraySet;
+
+public class DowntimeCalendar {
+
+ private final ArraySet<Integer> mDays = new ArraySet<Integer>();
+ private final Calendar mCalendar = Calendar.getInstance();
+
+ private DowntimeInfo mInfo;
+
+ @Override
+ public String toString() {
+ return "DowntimeCalendar[mDays=" + mDays + "]";
+ }
+
+ public void setDowntimeInfo(DowntimeInfo info) {
+ if (Objects.equals(mInfo, info)) return;
+ mInfo = info;
+ updateDays();
+ }
+
+ public long nextDowntimeStart(long time) {
+ if (mInfo == null || mDays.size() == 0) return Long.MAX_VALUE;
+ final long start = getTime(time, mInfo.startHour, mInfo.startMinute);
+ for (int i = 0; i < Calendar.SATURDAY; i++) {
+ final long t = addDays(start, i);
+ if (t > time && isInDowntime(t)) {
+ return t;
+ }
+ }
+ return Long.MAX_VALUE;
+ }
+
+ public void setTimeZone(TimeZone tz) {
+ mCalendar.setTimeZone(tz);
+ }
+
+ public long getNextTime(long now, int hr, int min) {
+ final long time = getTime(now, hr, min);
+ return time <= now ? addDays(time, 1) : time;
+ }
+
+ 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();
+ }
+
+ public boolean isInDowntime(long time) {
+ if (mInfo == null || mDays.size() == 0) return false;
+ final long start = getTime(time, mInfo.startHour, mInfo.startMinute);
+ long end = getTime(time, mInfo.endHour, mInfo.endMinute);
+ 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 n = Calendar.SATURDAY;
+ final int day = ((getDayOfWeek(time) - 1) + (daysOffset % n) + n) % n + 1;
+ start = addDays(start, daysOffset);
+ end = addDays(end, daysOffset);
+ return mDays.contains(day) && time >= start && time < end;
+ }
+
+ private int getDayOfWeek(long time) {
+ mCalendar.setTimeInMillis(time);
+ return mCalendar.get(Calendar.DAY_OF_WEEK);
+ }
+
+ private void updateDays() {
+ mDays.clear();
+ if (mInfo != null) {
+ final int[] days = ZenModeConfig.tryParseDays(mInfo.mode);
+ for (int i = 0; days != null && i < days.length; i++) {
+ mDays.add(days[i]);
+ }
+ }
+ }
+
+ private long addDays(long time, int days) {
+ mCalendar.setTimeInMillis(time);
+ mCalendar.add(Calendar.DATE, days);
+ return mCalendar.getTimeInMillis();
+ }
+}
diff --git a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java
index 097589a..df4ecfd 100644
--- a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java
+++ b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java
@@ -35,13 +35,13 @@ import android.text.format.DateFormat;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
+import android.util.TimeUtils;
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;
@@ -61,26 +61,44 @@ public class DowntimeConditionProvider extends ConditionProviderService {
private static final int EXIT_CODE = 101;
private static final String EXTRA_TIME = "time";
- private final Calendar mCalendar = Calendar.getInstance();
+ private static final long SECONDS = 1000;
+ private static final long MINUTES = 60 * SECONDS;
+ private static final long HOURS = 60 * MINUTES;
+
private final Context mContext = this;
- private final ArraySet<Integer> mDays = new ArraySet<Integer>();
- private final ArraySet<Long> mFiredAlarms = new ArraySet<Long>();
+ private final DowntimeCalendar mCalendar = new DowntimeCalendar();
+ private final FiredAlarms mFiredAlarms = new FiredAlarms();
+ private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>();
+ private final ConditionProviders mConditionProviders;
+ private final NextAlarmTracker mTracker;
+ private final ZenModeHelper mZenModeHelper;
private boolean mConnected;
- private NextAlarmTracker mTracker;
- private int mDowntimeMode;
+ private long mLookaheadThreshold;
private ZenModeConfig mConfig;
- private Callback mCallback;
+ private boolean mDowntimed;
+ private boolean mConditionClearing;
+ private boolean mRequesting;
- public DowntimeConditionProvider() {
+ public DowntimeConditionProvider(ConditionProviders conditionProviders,
+ NextAlarmTracker tracker, ZenModeHelper zenModeHelper) {
if (DEBUG) Slog.d(TAG, "new DowntimeConditionProvider()");
+ mConditionProviders = conditionProviders;
+ mTracker = tracker;
+ mZenModeHelper = zenModeHelper;
}
public void dump(PrintWriter pw, DumpFilter filter) {
pw.println(" DowntimeConditionProvider:");
pw.print(" mConnected="); pw.println(mConnected);
- pw.print(" mDowntimeMode="); pw.println(Global.zenModeToString(mDowntimeMode));
+ pw.print(" mSubscriptions="); pw.println(mSubscriptions);
+ pw.print(" mLookaheadThreshold="); pw.print(mLookaheadThreshold);
+ pw.print(" ("); TimeUtils.formatDuration(mLookaheadThreshold, pw); pw.println(")");
+ pw.print(" mCalendar="); pw.println(mCalendar);
pw.print(" mFiredAlarms="); pw.println(mFiredAlarms);
+ pw.print(" mDowntimed="); pw.println(mDowntimed);
+ pw.print(" mConditionClearing="); pw.println(mConditionClearing);
+ pw.print(" mRequesting="); pw.println(mRequesting);
}
public void attachBase(Context base) {
@@ -91,22 +109,20 @@ public class DowntimeConditionProvider extends ConditionProviderService {
return (IConditionProvider) onBind(null);
}
- public void setCallback(Callback callback) {
- mCallback = callback;
- }
-
@Override
public void onConnected() {
if (DEBUG) Slog.d(TAG, "onConnected");
mConnected = true;
+ mLookaheadThreshold = PropConfig.getInt(mContext, "downtime.condition.lookahead",
+ R.integer.config_downtime_condition_lookahead_threshold_hrs) * HOURS;
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);
- mTracker = mCallback.getNextAlarmTracker();
mTracker.addCallback(mTrackerCallback);
+ mZenModeHelper.addCallback(mZenCallback);
init();
}
@@ -114,59 +130,125 @@ public class DowntimeConditionProvider extends ConditionProviderService {
public void onDestroy() {
if (DEBUG) Slog.d(TAG, "onDestroy");
mTracker.removeCallback(mTrackerCallback);
+ mZenModeHelper.removeCallback(mZenCallback);
mConnected = false;
}
@Override
public void onRequestConditions(int relevance) {
if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance);
- if ((relevance & Condition.FLAG_RELEVANT_NOW) != 0) {
- if (isInDowntime() && mConfig != null) {
- notifyCondition(createCondition(mConfig.toDowntimeInfo(), mConfig.sleepNone,
- Condition.STATE_TRUE));
- }
- }
+ if (!mConnected) return;
+ mRequesting = (relevance & Condition.FLAG_RELEVANT_NOW) != 0;
+ evaluateSubscriptions();
}
@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) && isInDowntime()
- ? Condition.STATE_TRUE : Condition.STATE_FALSE;
- if (DEBUG) Slog.d(TAG, "notify condition state: " + Condition.stateToString(state));
- notifyCondition(createCondition(downtime, mConfig.sleepNone, state));
+ if (downtime == null) return;
+ mFiredAlarms.clear();
+ mSubscriptions.add(conditionId);
+ notifyCondition(downtime);
+ }
+
+ private boolean shouldShowCondition() {
+ final long now = System.currentTimeMillis();
+ if (DEBUG) Slog.d(TAG, "shouldShowCondition now=" + mCalendar.isInDowntime(now)
+ + " lookahead="
+ + (mCalendar.nextDowntimeStart(now) <= (now + mLookaheadThreshold)));
+ return mCalendar.isInDowntime(now)
+ || mCalendar.nextDowntimeStart(now) <= (now + mLookaheadThreshold);
+ }
+
+ private void notifyCondition(DowntimeInfo downtime) {
+ if (mConfig == null) {
+ // we don't know yet
+ notifyCondition(createCondition(downtime, Condition.STATE_UNKNOWN));
+ return;
+ }
+ if (!downtime.equals(mConfig.toDowntimeInfo())) {
+ // not the configured downtime, consider it false
+ notifyCondition(createCondition(downtime, Condition.STATE_FALSE));
+ return;
+ }
+ if (!shouldShowCondition()) {
+ // configured downtime, but not within the time range
+ notifyCondition(createCondition(downtime, Condition.STATE_FALSE));
+ return;
+ }
+ if (isZenNone() && mFiredAlarms.findBefore(System.currentTimeMillis())) {
+ // within the configured time range, but wake up if none and the next alarm is fired
+ notifyCondition(createCondition(downtime, Condition.STATE_FALSE));
+ return;
+ }
+ // within the configured time range, condition still valid
+ notifyCondition(createCondition(downtime, Condition.STATE_TRUE));
+ }
+
+ private boolean isZenNone() {
+ return mZenModeHelper.getZenMode() == Global.ZEN_MODE_NO_INTERRUPTIONS;
+ }
+
+ private boolean isZenOff() {
+ return mZenModeHelper.getZenMode() == Global.ZEN_MODE_OFF;
+ }
+
+ private void evaluateSubscriptions() {
+ ArraySet<Uri> conditions = mSubscriptions;
+ if (mConfig != null && mRequesting && shouldShowCondition()) {
+ final Uri id = ZenModeConfig.toDowntimeConditionId(mConfig.toDowntimeInfo());
+ if (!conditions.contains(id)) {
+ conditions = new ArraySet<Uri>(conditions);
+ conditions.add(id);
+ }
+ }
+ for (Uri conditionId : conditions) {
+ final DowntimeInfo downtime = ZenModeConfig.tryParseDowntimeConditionId(conditionId);
+ if (downtime != null) {
+ notifyCondition(downtime);
+ }
}
}
@Override
public void onUnsubscribe(Uri conditionId) {
- if (DEBUG) Slog.d(TAG, "onUnsubscribe conditionId=" + conditionId);
+ final boolean current = mSubscriptions.contains(conditionId);
+ if (DEBUG) Slog.d(TAG, "onUnsubscribe conditionId=" + conditionId + " current=" + current);
+ mSubscriptions.remove(conditionId);
+ mFiredAlarms.clear();
}
public void setConfig(ZenModeConfig config) {
if (Objects.equals(mConfig, config)) return;
- if (DEBUG) Slog.d(TAG, "setConfig");
+ final boolean downtimeChanged = mConfig == null || config == null
+ || !mConfig.toDowntimeInfo().equals(config.toDowntimeInfo());
mConfig = config;
- if (mConnected) {
+ if (DEBUG) Slog.d(TAG, "setConfig downtimeChanged=" + downtimeChanged);
+ if (mConnected && downtimeChanged) {
+ mDowntimed = false;
init();
}
+ // when active, mark downtime as entered for today
+ if (mConfig != null && mConfig.exitCondition != null
+ && ZenModeConfig.isValidDowntimeConditionId(mConfig.exitCondition.id)) {
+ mDowntimed = true;
+ }
}
- public boolean isInDowntime() {
- return mDowntimeMode != Global.ZEN_MODE_OFF;
+ public void onManualConditionClearing() {
+ mConditionClearing = true;
}
- public Condition createCondition(DowntimeInfo downtime, boolean orAlarm, int state) {
+ private 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 now = System.currentTimeMillis();
- long endTime = getTime(now, downtime.endHour, downtime.endMinute);
- if (orAlarm) {
+ long endTime = mCalendar.getNextTime(now, downtime.endHour, downtime.endMinute);
+ if (isZenNone()) {
final AlarmClockInfo nextAlarm = mTracker.getNextAlarm();
final long nextAlarmTime = nextAlarm != null ? nextAlarm.getTriggerTime() : 0;
if (nextAlarmTime > now && nextAlarmTime < endTime) {
@@ -179,79 +261,11 @@ public class DowntimeConditionProvider extends ConditionProviderService {
return new Condition(id, summary, line1, formatted, 0, state, Condition.FLAG_RELEVANT_NOW);
}
- public boolean isDowntimeCondition(Condition condition) {
- return condition != null && ZenModeConfig.isValidDowntimeConditionId(condition.id);
- }
-
private void init() {
- updateDays();
- reevaluateDowntime();
+ mCalendar.setDowntimeInfo(mConfig != null ? mConfig.toDowntimeInfo() : null);
+ evaluateSubscriptions();
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);
- }
- final boolean orAlarm = mConfig.sleepNone;
- return isInDowntime(-1, time, start, end, orAlarm)
- || isInDowntime(0, time, start, end, orAlarm);
- }
-
- private boolean isInDowntime(int daysOffset, long time, long start, long end, boolean orAlarm) {
- final int n = Calendar.SATURDAY;
- final int day = ((getDayOfWeek(time) - 1) + (daysOffset % n) + n) % n + 1;
- start = addDays(start, daysOffset);
- end = addDays(end, daysOffset);
- if (orAlarm) {
- end = findFiredAlarm(start, end);
- }
- return mDays.contains(day) && time >= start && time < end;
- }
-
- private long findFiredAlarm(long start, long end) {
- final int N = mFiredAlarms.size();
- for (int i = 0; i < N; i++) {
- final long firedAlarm = mFiredAlarms.valueAt(i);
- if (firedAlarm > start && firedAlarm < end) {
- return firedAlarm;
- }
- }
- return end;
- }
-
- private void reevaluateDowntime() {
- final long now = System.currentTimeMillis();
- final boolean inDowntimeNow = isInDowntime(now);
- final int downtimeMode = inDowntimeNow ? (mConfig.sleepNone
- ? Global.ZEN_MODE_NO_INTERRUPTIONS : Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
- : Global.ZEN_MODE_OFF;
- if (DEBUG) Slog.d(TAG, "downtimeMode=" + downtimeMode);
- if (downtimeMode == mDowntimeMode) return;
- mDowntimeMode = downtimeMode;
- Slog.i(TAG, (isInDowntime() ? "Entering" : "Exiting" ) + " downtime");
- ZenLog.traceDowntime(mDowntimeMode, getDayOfWeek(now), mDays);
- fireDowntimeChanged();
- }
-
- private void fireDowntimeChanged() {
- if (mCallback != null) {
- mCallback.onDowntimeChanged(mDowntimeMode);
- }
+ evaluateAutotrigger();
}
private void updateAlarms() {
@@ -260,38 +274,11 @@ public class DowntimeConditionProvider extends ConditionProviderService {
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 long time = mCalendar.getNextTime(now, hr, min);
final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, requestCode,
new Intent(action)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
@@ -311,31 +298,34 @@ public class DowntimeConditionProvider extends ConditionProviderService {
private void onEvaluateNextAlarm(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
if (!booted) return; // we don't know yet
- // update condition description if we're in downtime (mode = none)
- if (isInDowntime() && mConfig != null && mConfig.sleepNone) {
- notifyCondition(createCondition(mConfig.toDowntimeInfo(), true /*orAlarm*/,
- Condition.STATE_TRUE));
- }
- if (nextAlarm == null) return; // not fireable
if (DEBUG) Slog.d(TAG, "onEvaluateNextAlarm " + mTracker.formatAlarmDebug(nextAlarm));
- if (System.currentTimeMillis() > wakeupTime) {
+ if (nextAlarm != null && wakeupTime > 0 && System.currentTimeMillis() > wakeupTime) {
if (DEBUG) Slog.d(TAG, "Alarm fired: " + mTracker.formatAlarmDebug(wakeupTime));
- trimFiredAlarms();
mFiredAlarms.add(wakeupTime);
}
- reevaluateDowntime();
+ evaluateSubscriptions();
}
- private void trimFiredAlarms() {
- // remove fired alarms over 2 days old
- final long keepAfter = System.currentTimeMillis() - 2 * 24 * 60 * 60 * 1000;
- final int N = mFiredAlarms.size();
- for (int i = N - 1; i >= 0; i--) {
- final long firedAlarm = mFiredAlarms.valueAt(i);
- if (firedAlarm < keepAfter) {
- mFiredAlarms.removeAt(i);
- }
+ private void evaluateAutotrigger() {
+ String skipReason = null;
+ if (mConfig == null) {
+ skipReason = "no config";
+ } else if (mDowntimed) {
+ skipReason = "already downtimed";
+ } else if (mZenModeHelper.getZenMode() != Global.ZEN_MODE_OFF) {
+ skipReason = "already in zen";
+ } else if (!mCalendar.isInDowntime(System.currentTimeMillis())) {
+ skipReason = "not in downtime";
}
+ if (skipReason != null) {
+ ZenLog.traceDowntimeAutotrigger("Autotrigger skipped: " + skipReason);
+ return;
+ }
+ ZenLog.traceDowntimeAutotrigger("Autotrigger fired");
+ mZenModeHelper.setZenMode(mConfig.sleepNone ? Global.ZEN_MODE_NO_INTERRUPTIONS
+ : Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, "downtime");
+ final Condition condition = createCondition(mConfig.toDowntimeInfo(), Condition.STATE_TRUE);
+ mConditionProviders.setZenModeCondition(condition, "downtime");
}
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -347,6 +337,12 @@ public class DowntimeConditionProvider extends ConditionProviderService {
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));
+ if (ENTER_ACTION.equals(action)) {
+ evaluateAutotrigger();
+ } else /*EXIT_ACTION*/ {
+ mDowntimed = false;
+ }
+ mFiredAlarms.clear();
} else if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
if (DEBUG) Slog.d(TAG, "timezone changed to " + TimeZone.getDefault());
mCalendar.setTimeZone(TimeZone.getDefault());
@@ -357,7 +353,7 @@ public class DowntimeConditionProvider extends ConditionProviderService {
} else {
if (DEBUG) Slog.d(TAG, action + " fired at " + now);
}
- reevaluateDowntime();
+ evaluateSubscriptions();
updateAlarms();
}
};
@@ -369,8 +365,45 @@ public class DowntimeConditionProvider extends ConditionProviderService {
}
};
- public interface Callback {
- void onDowntimeChanged(int downtimeMode);
- NextAlarmTracker getNextAlarmTracker();
+ private final ZenModeHelper.Callback mZenCallback = new ZenModeHelper.Callback() {
+ @Override
+ void onZenModeChanged() {
+ if (mConditionClearing && isZenOff()) {
+ evaluateAutotrigger();
+ }
+ mConditionClearing = false;
+ evaluateSubscriptions();
+ }
+ };
+
+ private class FiredAlarms {
+ private final ArraySet<Long> mFiredAlarms = new ArraySet<Long>();
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < mFiredAlarms.size(); i++) {
+ if (i > 0) sb.append(',');
+ sb.append(mTracker.formatAlarmDebug(mFiredAlarms.valueAt(i)));
+ }
+ return sb.toString();
+ }
+
+ public void add(long firedAlarm) {
+ mFiredAlarms.add(firedAlarm);
+ }
+
+ public void clear() {
+ mFiredAlarms.clear();
+ }
+
+ public boolean findBefore(long time) {
+ for (int i = 0; i < mFiredAlarms.size(); i++) {
+ if (mFiredAlarms.valueAt(i) < time) {
+ return true;
+ }
+ }
+ return false;
+ }
}
}
diff --git a/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java b/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java
index 35bbaa0..1634c65 100644
--- a/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java
+++ b/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java
@@ -25,6 +25,8 @@ import android.service.notification.Condition;
import android.service.notification.ConditionProviderService;
import android.service.notification.IConditionProvider;
import android.service.notification.ZenModeConfig;
+import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
import android.util.TimeUtils;
@@ -38,10 +40,7 @@ import java.io.PrintWriter;
* Built-in zen condition provider for alarm-clock-based conditions.
*
* <p>If the user's next alarm is within a lookahead threshold (config, default 12hrs), advertise
- * it as an exit condition for zen mode (unless the built-in downtime condition is also available).
- *
- * <p>When this next alarm is selected as the active exit condition, follow subsequent changes
- * to the user's next alarm, assuming it remains within the 12-hr window.
+ * it as an exit condition for zen mode.
*
* <p>The next alarm is defined as {@link AlarmManager#getNextAlarmClock(int)}, which does not
* survive a reboot. Maintain the illusion of a consistent next alarm value by holding on to
@@ -55,20 +54,22 @@ public class NextAlarmConditionProvider extends ConditionProviderService {
private static final long MINUTES = 60 * SECONDS;
private static final long HOURS = 60 * MINUTES;
- private static final String NEXT_ALARM_PATH = "next_alarm";
+ private static final long BAD_CONDITION = -1;
+
public static final ComponentName COMPONENT =
new ComponentName("android", NextAlarmConditionProvider.class.getName());
private final Context mContext = this;
+ private final NextAlarmTracker mTracker;
+ private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>();
- private NextAlarmTracker mTracker;
private boolean mConnected;
private long mLookaheadThreshold;
- private Callback mCallback;
- private Uri mCurrentSubscription;
+ private boolean mRequesting;
- public NextAlarmConditionProvider() {
+ public NextAlarmConditionProvider(NextAlarmTracker tracker) {
if (DEBUG) Slog.d(TAG, "new NextAlarmConditionProvider()");
+ mTracker = tracker;
}
public void dump(PrintWriter pw, DumpFilter filter) {
@@ -76,20 +77,16 @@ public class NextAlarmConditionProvider extends ConditionProviderService {
pw.print(" mConnected="); pw.println(mConnected);
pw.print(" mLookaheadThreshold="); pw.print(mLookaheadThreshold);
pw.print(" ("); TimeUtils.formatDuration(mLookaheadThreshold, pw); pw.println(")");
- pw.print(" mCurrentSubscription="); pw.println(mCurrentSubscription);
- }
-
- public void setCallback(Callback callback) {
- mCallback = callback;
+ pw.print(" mSubscriptions="); pw.println(mSubscriptions);
+ pw.print(" mRequesting="); pw.println(mRequesting);
}
@Override
public void onConnected() {
if (DEBUG) Slog.d(TAG, "onConnected");
- mLookaheadThreshold = mContext.getResources()
- .getInteger(R.integer.config_next_alarm_condition_lookahead_threshold_hrs) * HOURS;
+ mLookaheadThreshold = PropConfig.getInt(mContext, "nextalarm.condition.lookahead",
+ R.integer.config_next_alarm_condition_lookahead_threshold_hrs) * HOURS;
mConnected = true;
- mTracker = mCallback.getNextAlarmTracker();
mTracker.addCallback(mTrackerCallback);
}
@@ -103,34 +100,27 @@ public class NextAlarmConditionProvider extends ConditionProviderService {
@Override
public void onRequestConditions(int relevance) {
- if (!mConnected || (relevance & Condition.FLAG_RELEVANT_NOW) == 0) return;
-
- final AlarmClockInfo nextAlarm = mTracker.getNextAlarm();
- if (nextAlarm == null) return; // no next alarm
- if (mCallback != null && mCallback.isInDowntime()) return; // prefer downtime condition
- if (!isWithinLookaheadThreshold(nextAlarm)) return; // alarm not within window
-
- // next alarm exists, and is within the configured lookahead threshold
- notifyCondition(newConditionId(), nextAlarm, Condition.STATE_TRUE, "request");
+ if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance);
+ if (!mConnected) return;
+ mRequesting = (relevance & Condition.FLAG_RELEVANT_NOW) != 0;
+ mTracker.evaluate();
}
@Override
public void onSubscribe(Uri conditionId) {
if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
- if (!isNextAlarmCondition(conditionId)) {
+ if (tryParseNextAlarmCondition(conditionId) == BAD_CONDITION) {
notifyCondition(conditionId, null, Condition.STATE_FALSE, "badCondition");
return;
}
- mCurrentSubscription = conditionId;
+ mSubscriptions.add(conditionId);
mTracker.evaluate();
}
@Override
public void onUnsubscribe(Uri conditionId) {
if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId);
- if (conditionId != null && conditionId.equals(mCurrentSubscription)) {
- mCurrentSubscription = null;
- }
+ mSubscriptions.remove(conditionId);
}
public void attachBase(Context base) {
@@ -157,43 +147,72 @@ public class NextAlarmConditionProvider extends ConditionProviderService {
formattedAlarm, 0, state, Condition.FLAG_RELEVANT_NOW));
}
- private Uri newConditionId() {
+ private Uri newConditionId(AlarmClockInfo nextAlarm) {
return new Uri.Builder().scheme(Condition.SCHEME)
.authority(ZenModeConfig.SYSTEM_AUTHORITY)
- .appendPath(NEXT_ALARM_PATH)
+ .appendPath(ZenModeConfig.NEXT_ALARM_PATH)
.appendPath(Integer.toString(mTracker.getCurrentUserId()))
+ .appendPath(Long.toString(nextAlarm.getTriggerTime()))
.build();
}
- private boolean isNextAlarmCondition(Uri conditionId) {
+ private long tryParseNextAlarmCondition(Uri conditionId) {
return conditionId != null && conditionId.getScheme().equals(Condition.SCHEME)
&& conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY)
- && conditionId.getPathSegments().size() == 2
- && conditionId.getPathSegments().get(0).equals(NEXT_ALARM_PATH)
+ && conditionId.getPathSegments().size() == 3
+ && conditionId.getPathSegments().get(0).equals(ZenModeConfig.NEXT_ALARM_PATH)
&& conditionId.getPathSegments().get(1)
- .equals(Integer.toString(mTracker.getCurrentUserId()));
+ .equals(Integer.toString(mTracker.getCurrentUserId()))
+ ? tryParseLong(conditionId.getPathSegments().get(2), BAD_CONDITION)
+ : BAD_CONDITION;
+ }
+
+ private static long tryParseLong(String value, long defValue) {
+ if (TextUtils.isEmpty(value)) return defValue;
+ try {
+ return Long.valueOf(value);
+ } catch (NumberFormatException e) {
+ return defValue;
+ }
}
private void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
final boolean withinThreshold = isWithinLookaheadThreshold(nextAlarm);
- if (DEBUG) Slog.d(TAG, "onEvaluate mCurrentSubscription=" + mCurrentSubscription
+ final long nextAlarmTime = nextAlarm != null ? nextAlarm.getTriggerTime() : 0;
+ if (DEBUG) Slog.d(TAG, "onEvaluate mSubscriptions=" + mSubscriptions
+ + " nextAlarmTime=" + mTracker.formatAlarmDebug(nextAlarmTime)
+ " nextAlarmWakeup=" + mTracker.formatAlarmDebug(wakeupTime)
+ " withinThreshold=" + withinThreshold
+ " booted=" + booted);
- if (mCurrentSubscription == null) return; // no one cares
- if (!booted) {
- // we don't know yet
- notifyCondition(mCurrentSubscription, nextAlarm, Condition.STATE_UNKNOWN, "!booted");
- return;
+
+ ArraySet<Uri> conditions = mSubscriptions;
+ if (mRequesting && nextAlarm != null && withinThreshold) {
+ final Uri id = newConditionId(nextAlarm);
+ if (!conditions.contains(id)) {
+ conditions = new ArraySet<Uri>(conditions);
+ conditions.add(id);
+ }
}
- if (!withinThreshold) {
- // next alarm outside threshold or in the past, condition = false
- notifyCondition(mCurrentSubscription, nextAlarm, Condition.STATE_FALSE, "!within");
- mCurrentSubscription = null;
- return;
+ for (Uri conditionId : conditions) {
+ final long time = tryParseNextAlarmCondition(conditionId);
+ if (time == BAD_CONDITION) {
+ notifyCondition(conditionId, nextAlarm, Condition.STATE_FALSE, "badCondition");
+ } else if (!booted) {
+ // we don't know yet
+ if (mSubscriptions.contains(conditionId)) {
+ notifyCondition(conditionId, nextAlarm, Condition.STATE_UNKNOWN, "!booted");
+ }
+ } else if (time != nextAlarmTime) {
+ // next alarm changed since subscription, consider obsolete
+ notifyCondition(conditionId, nextAlarm, Condition.STATE_FALSE, "changed");
+ } else if (!withinThreshold) {
+ // next alarm outside threshold or in the past, condition = false
+ notifyCondition(conditionId, nextAlarm, Condition.STATE_FALSE, "!within");
+ } else {
+ // next alarm within threshold and in the future, condition = true
+ notifyCondition(conditionId, nextAlarm, Condition.STATE_TRUE, "within");
+ }
}
- // next alarm in the future and within threshold, condition = true
- notifyCondition(mCurrentSubscription, nextAlarm, Condition.STATE_TRUE, "within");
}
private final NextAlarmTracker.Callback mTrackerCallback = new NextAlarmTracker.Callback() {
@@ -202,9 +221,4 @@ public class NextAlarmConditionProvider extends ConditionProviderService {
NextAlarmConditionProvider.this.onEvaluate(nextAlarm, wakeupTime, booted);
}
};
-
- public interface Callback {
- boolean isInDowntime();
- NextAlarmTracker getNextAlarmTracker();
- }
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index aec20bc..bb99916 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1551,6 +1551,12 @@ public class NotificationManagerService extends SystemService {
MATCHES_CALL_FILTER_CONTACTS_TIMEOUT_MS,
MATCHES_CALL_FILTER_TIMEOUT_AFFINITY);
}
+
+ @Override
+ public boolean isSystemConditionProviderEnabled(String path) {
+ enforceSystemOrSystemUI("INotificationManager.isSystemConditionProviderEnabled");
+ return mConditionProviders.isSystemConditionProviderEnabled(path);
+ }
};
private String[] getActiveNotificationKeys(INotificationListener token) {
diff --git a/services/core/java/com/android/server/notification/PropConfig.java b/services/core/java/com/android/server/notification/PropConfig.java
new file mode 100644
index 0000000..97bf90d
--- /dev/null
+++ b/services/core/java/com/android/server/notification/PropConfig.java
@@ -0,0 +1,33 @@
+/*
+ * 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.content.Context;
+import android.os.SystemProperties;
+
+public class PropConfig {
+ private static final String UNSET = "UNSET";
+
+ public static int getInt(Context context, String propName, int resId) {
+ return SystemProperties.getInt(propName, context.getResources().getInteger(resId));
+ }
+
+ public static String[] getStringArray(Context context, String propName, int resId) {
+ final String prop = SystemProperties.get(propName, UNSET);
+ return !UNSET.equals(prop) ? prop.split(",") : context.getResources().getStringArray(resId);
+ }
+}
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index 6960159..dda0b37 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -89,12 +89,12 @@ public class ZenLog {
ringerModeToString(ringerModeExternalOut));
}
- public static void traceDowntime(int downtimeMode, int day, ArraySet<Integer> days) {
- append(TYPE_DOWNTIME, zenModeToString(downtimeMode) + ",day=" + day + ",days=" + days);
+ public static void traceDowntimeAutotrigger(String result) {
+ append(TYPE_DOWNTIME, result);
}
- public static void traceSetZenMode(int mode, String reason) {
- append(TYPE_SET_ZEN_MODE, zenModeToString(mode) + "," + reason);
+ public static void traceSetZenMode(int zenMode, String reason) {
+ append(TYPE_SET_ZEN_MODE, zenModeToString(zenMode) + "," + reason);
}
public static void traceUpdateZenMode(int fromMode, int toMode) {
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 5ceb503..012e22f 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -108,6 +108,10 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
mCallbacks.add(callback);
}
+ public void removeCallback(Callback callback) {
+ mCallbacks.remove(callback);
+ }
+
public void onSystemReady() {
mAudioManager = LocalServices.getService(AudioManagerInternal.class);
if (mAudioManager != null) {
@@ -208,9 +212,9 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
return mZenMode;
}
- public void setZenMode(int zenModeValue, String reason) {
- ZenLog.traceSetZenMode(zenModeValue, reason);
- Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zenModeValue);
+ public void setZenMode(int zenMode, String reason) {
+ ZenLog.traceSetZenMode(zenMode, reason);
+ Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zenMode);
}
public void updateZenMode() {