diff options
author | John Spurlock <jspurlock@google.com> | 2014-11-30 16:26:19 -0500 |
---|---|---|
committer | John Spurlock <jspurlock@google.com> | 2014-12-08 11:00:57 -0500 |
commit | 530052a2fe3b6a6a4246ce28ab0ced647fe7f470 (patch) | |
tree | a54342518eb9b94bf43b49fb50d2db1771463436 | |
parent | 78a7357787406220c4c2459f8d25a0175ac98347 (diff) | |
download | frameworks_base-530052a2fe3b6a6a4246ce28ab0ced647fe7f470.zip frameworks_base-530052a2fe3b6a6a4246ce28ab0ced647fe7f470.tar.gz frameworks_base-530052a2fe3b6a6a4246ce28ab0ced647fe7f470.tar.bz2 |
Zen: New behavior for built-in downtime + nextalarm conditions.
- Downtime: Allow user to enter downtime early, offer as an end
condition four hours before downtime starts. Available in
either none or priority, regardless of settings configuration.
- Downtime: Always exit before next alarm if zen=none.
- Downtime: Make more like any other condition provider, remove
special status (mostly).
- Downtime: New auto-triggering rules, allow triggering after a
manual condition ends, once.
- Decouple NextAlarm + Downtime providers, allow them to offer
their conditions at the same time.
- Downtime/NextAlarm: Update conditions if they change while being
requested, even if unsubscribed.
- Make all three built-in condition providers optional, via config.
- New internal helper for runtime config.
- Don't follow changes to next alarm, consider the condition false.
- Isolate downtime calendar logic into separate class (for testing).
- Allow a:bb -> a:bb as a valid downtime range (all day).
- Volume dialog: configuration establishes maximum number of visible
conditions, including built-ins.
- Zen mode panel: avoid widget updates during layout transition.
- Zen mode panel: move controller callers to background thread.
- Zen mode panel: hide/show/rebind rows instead of adding/removing.
- ZenLog: Add downtime autotrigger results.
- Volume panel: Smarter refresh on ringer/zen changes.
Bug: 16373455
Change-Id: I4f801018ddb0beb6eb9fa03a81c79f7949888a3f
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() { |