An update on Downtime.
The update is that Downtime is obsolete. Replaced by the
ability to define multiple named schedule calendars.
- Make changes to ZenModeConfig to properly model manual
and automatic rules.
- Refactor the zen mode helper (and supporting classes) to
properly handle / report multiple claims on zen mode.
The "manual" rule (specified by the user in the UI) vs
one or more automatic rules.
- Automatic rules are still backed by condition providers,
but the layering is now cleaner. ConditionProviders is now
completely generic, has no ties to zen mode.
- Specifically, the new layering for zen mode (below noman) is:
ZenModeHelper: Source of truth for zen state
ZenModeFiltering: Subhelper dedicated to filtering rules.
ZenModeConditions: Subhelper dedicated to managing automatic rules.
ConditionProviders: Underlying engine for reporting named boolean state.
- Migration story for users with existing downtime config, migrated
to a single new calendar named downtime.
- For users with no existing downtime, two default calendars are created
for weeknights + weekends (icu4j for all locales will be done in a followup).
- Remove obsolete DowntimeConditionProvider/NextAlarmConditionProvider and tracking.
- Clean up obsolete resources.
- Add common zen summary description string computation.
- Add proper noman wrappers for the new model.
- Change the semantics of the global zen setting. It is now read-only. Setters
must call noman, added a "reason" to all calls for better attribution.
- Update zenmodepanel + volumedialog to the new model.
- Display the one or more automatic rules in the new zen footer summary.
- "Snooze" the automatic rules when the user explicitly turns zen off.
Bug: 20064962
Change-Id: Idd9deb865a6035ad0cfae660198dccb517e6d7cc
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index ab53fbc..fc2eced 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -25,12 +25,10 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
-import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.ConditionProviderService;
import android.service.notification.IConditionListener;
import android.service.notification.IConditionProvider;
-import android.service.notification.ZenModeConfig;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -41,50 +39,44 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Objects;
public class ConditionProviders extends ManagedServices {
- private static final Condition[] NO_CONDITIONS = new Condition[0];
+ private final ArrayList<ConditionRecord> mRecords = new ArrayList<>();
+ private final ArrayMap<IBinder, IConditionListener> mListeners = new ArrayMap<>();
+ private final ArraySet<String> mSystemConditionProviderNames;
+ private final ArraySet<SystemConditionProviderService> mSystemConditionProviders
+ = new ArraySet<>();
- private final ZenModeHelper mZenModeHelper;
- private final ArrayMap<IBinder, IConditionListener> mListeners
- = new ArrayMap<IBinder, IConditionListener>();
- private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>();
- private final ArraySet<String> mSystemConditionProviders;
- private final CountdownConditionProvider mCountdown;
- private final DowntimeConditionProvider mDowntime;
- private final NextAlarmConditionProvider mNextAlarm;
- private final NextAlarmTracker mNextAlarmTracker;
+ private Callback mCallback;
- private Condition mExitCondition;
- private ComponentName mExitConditionComponent;
-
- public ConditionProviders(Context context, Handler handler,
- UserProfiles userProfiles, ZenModeHelper zenModeHelper) {
+ public ConditionProviders(Context context, Handler handler, UserProfiles userProfiles) {
super(context, handler, new Object(), userProfiles);
- mZenModeHelper = zenModeHelper;
- mZenModeHelper.addCallback(new ZenModeHelperCallback());
- mSystemConditionProviders = safeSet(PropConfig.getStringArray(mContext,
+ mSystemConditionProviderNames = 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();
}
- public boolean isSystemConditionProviderEnabled(String path) {
- return mSystemConditionProviders.contains(path);
+ public void setCallback(Callback callback) {
+ mCallback = callback;
+ }
+
+ public boolean isSystemProviderEnabled(String path) {
+ return mSystemConditionProviderNames.contains(path);
+ }
+
+ public void addSystemProvider(SystemConditionProviderService service) {
+ mSystemConditionProviders.add(service);
+ service.attachBase(mContext);
+ registerService(service.asInterface(), service.getComponent(), UserHandle.USER_OWNER);
+ }
+
+ public Iterable<SystemConditionProviderService> getSystemProviders() {
+ return mSystemConditionProviders;
}
@Override
protected Config getConfig() {
- Config c = new Config();
+ final Config c = new Config();
c.caption = "condition provider";
c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE;
c.secureSettingName = Settings.Secure.ENABLED_CONDITION_PROVIDERS;
@@ -98,12 +90,6 @@
public void dump(PrintWriter pw, DumpFilter filter) {
super.dump(pw, filter);
synchronized(mMutex) {
- if (filter == null) {
- pw.print(" mListeners("); pw.print(mListeners.size()); pw.println("):");
- for (int i = 0; i < mListeners.size(); i++) {
- pw.print(" "); pw.println(mListeners.keyAt(i));
- }
- }
pw.print(" mRecords("); pw.print(mRecords.size()); pw.println("):");
for (int i = 0; i < mRecords.size(); i++) {
final ConditionRecord r = mRecords.get(i);
@@ -115,18 +101,15 @@
}
}
}
- pw.print(" mSystemConditionProviders: "); pw.println(mSystemConditionProviders);
- if (mCountdown != null) {
- mCountdown.dump(pw, filter);
+ if (filter == null) {
+ pw.print(" mListeners("); pw.print(mListeners.size()); pw.println("):");
+ for (int i = 0; i < mListeners.size(); i++) {
+ pw.print(" "); pw.println(mListeners.keyAt(i));
+ }
}
- if (mDowntime != null) {
- mDowntime.dump(pw, filter);
- }
- if (mNextAlarm != null) {
- mNextAlarm.dump(pw, filter);
- }
- if (mNextAlarmTracker != null) {
- mNextAlarmTracker.dump(pw, filter);
+ pw.print(" mSystemConditionProviders: "); pw.println(mSystemConditionProviderNames);
+ for (int i = 0; i < mSystemConditionProviders.size(); i++) {
+ mSystemConditionProviders.valueAt(i).dump(pw, filter);
}
}
@@ -138,31 +121,16 @@
@Override
public void onBootPhaseAppsCanStart() {
super.onBootPhaseAppsCanStart();
- 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);
+ if (mCallback != null) {
+ mCallback.onBootComplete();
}
}
@Override
public void onUserSwitched() {
super.onUserSwitched();
- if (mNextAlarmTracker != null) {
- mNextAlarmTracker.onUserSwitched();
+ if (mCallback != null) {
+ mCallback.onUserSwitched();
}
}
@@ -174,24 +142,6 @@
} catch (RemoteException e) {
// we tried
}
- synchronized (mMutex) {
- if (info.component.equals(mExitConditionComponent)) {
- // ensure record exists, we'll wire it up and subscribe below
- final ConditionRecord manualRecord =
- getRecordLocked(mExitCondition.id, mExitConditionComponent);
- manualRecord.isManual = true;
- }
- final int N = mRecords.size();
- for(int i = 0; i < N; i++) {
- final ConditionRecord r = mRecords.get(i);
- if (!r.component.equals(info.component)) continue;
- r.info = info;
- // if automatic or manual, auto-subscribe
- if (r.isAutomatic || r.isManual) {
- subscribeLocked(r);
- }
- }
- }
}
@Override
@@ -200,15 +150,6 @@
for (int i = mRecords.size() - 1; i >= 0; i--) {
final ConditionRecord r = mRecords.get(i);
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) {
- // removing an automatic condition, exit zen
- mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "automaticServiceRemoved");
- }
mRecords.remove(i);
}
}
@@ -219,9 +160,9 @@
}
}
- public void requestZenModeConditions(IConditionListener callback, int relevance) {
+ public void requestConditions(IConditionListener callback, int relevance) {
synchronized(mMutex) {
- if (DEBUG) Slog.d(TAG, "requestZenModeConditions callback=" + callback
+ if (DEBUG) Slog.d(TAG, "requestConditions callback=" + callback
+ " relevance=" + Condition.relevanceToString(relevance));
if (callback == null) return;
relevance = relevance & (Condition.FLAG_RELEVANT_NOW | Condition.FLAG_RELEVANT_ALWAYS);
@@ -262,7 +203,8 @@
return rt;
}
- private ConditionRecord getRecordLocked(Uri id, ComponentName component) {
+ private ConditionRecord getRecordLocked(Uri id, ComponentName component, boolean create) {
+ if (id == null || component == null) return null;
final int N = mRecords.size();
for (int i = 0; i < N; i++) {
final ConditionRecord r = mRecords.get(i);
@@ -270,9 +212,12 @@
return r;
}
}
- final ConditionRecord r = new ConditionRecord(id, component);
- mRecords.add(r);
- return r;
+ if (create) {
+ final ConditionRecord r = new ConditionRecord(id, component);
+ mRecords.add(r);
+ return r;
+ }
+ return null;
}
public void notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions) {
@@ -291,99 +236,48 @@
}
for (int i = 0; i < N; i++) {
final Condition c = conditions[i];
- final ConditionRecord r = getRecordLocked(c.id, info.component);
- final Condition oldCondition = r.condition;
- final boolean conditionUpdate = oldCondition != null && !oldCondition.equals(c);
+ final ConditionRecord r = getRecordLocked(c.id, info.component, true /*create*/);
r.info = info;
r.condition = c;
- // if manual, exit zen if false (or failed), update if true (and changed)
- if (r.isManual) {
- if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
- final boolean failed = c.state == Condition.STATE_ERROR;
- if (failed) {
- Slog.w(TAG, "Exit zen: manual condition failed: " + c);
- } else if (DEBUG) {
- Slog.d(TAG, "Exit zen: manual condition false: " + c);
- }
- onManualConditionClearing();
- mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF,
- "manualConditionExit");
- unsubscribeLocked(r);
- r.isManual = false;
- } else if (c.state == Condition.STATE_TRUE && conditionUpdate) {
- if (DEBUG) Slog.d(TAG, "Current condition updated, still true. old="
- + oldCondition + " new=" + c);
- setZenModeCondition(c, "conditionUpdate");
- }
- }
- // if automatic, exit zen if false (or failed), enter zen if true
- if (r.isAutomatic) {
- if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
- final boolean failed = c.state == Condition.STATE_ERROR;
- if (failed) {
- Slog.w(TAG, "Exit zen: automatic condition failed: " + c);
- } else if (DEBUG) {
- Slog.d(TAG, "Exit zen: automatic condition false: " + c);
- }
- mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF,
- "automaticConditionExit");
- } else if (c.state == Condition.STATE_TRUE) {
- Slog.d(TAG, "Enter zen: automatic condition true: " + c);
- mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
- "automaticConditionEnter");
- }
+ if (mCallback != null) {
+ mCallback.onConditionChanged(c.id, c);
}
}
}
}
- private void ensureRecordExists(Condition condition, IConditionProvider provider,
- ComponentName component) {
+ public void ensureRecordExists(ComponentName component, Uri conditionId,
+ IConditionProvider provider) {
// constructed by convention, make sure the record exists...
- final ConditionRecord r = getRecordLocked(condition.id, component);
+ final ConditionRecord r = getRecordLocked(conditionId, component, true /*create*/);
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 + " reason=" + reason);
- synchronized(mMutex) {
- ComponentName conditionComponent = null;
- if (condition != null) {
- if (mCountdown != null && ZenModeConfig.isValidCountdownConditionId(condition.id)) {
- ensureRecordExists(condition, mCountdown.asInterface(),
- CountdownConditionProvider.COMPONENT);
- }
- if (mDowntime != null && ZenModeConfig.isValidDowntimeConditionId(condition.id)) {
- ensureRecordExists(condition, mDowntime.asInterface(),
- DowntimeConditionProvider.COMPONENT);
- }
+ public boolean subscribeIfNecessary(ComponentName component, Uri conditionId) {
+ synchronized (mMutex) {
+ final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
+ if (r == null) {
+ Slog.w(TAG, "Unable to subscribe to " + component + " " + conditionId);
+ return false;
}
- final int N = mRecords.size();
- for (int i = 0; i < N; i++) {
- final ConditionRecord r = mRecords.get(i);
- final boolean idEqual = condition != null && r.id.equals(condition.id);
- if (r.isManual && !idEqual) {
- // was previous manual condition, unsubscribe
- unsubscribeLocked(r);
- r.isManual = false;
- } else if (idEqual && !r.isManual) {
- // is new manual condition, subscribe
- subscribeLocked(r);
- r.isManual = true;
- }
- if (idEqual) {
- conditionComponent = r.component;
- }
+ if (r.subscribed) return true;
+ subscribeLocked(r);
+ return r.subscribed;
+ }
+ }
+
+ public void unsubscribeIfNecessary(ComponentName component, Uri conditionId) {
+ synchronized (mMutex) {
+ final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
+ if (r == null) {
+ Slog.w(TAG, "Unable to unsubscribe to " + component + " " + conditionId);
+ return;
}
- if (!Objects.equals(mExitCondition, condition)) {
- mExitCondition = condition;
- mExitConditionComponent = conditionComponent;
- ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, reason);
- saveZenConfigLocked();
- }
+ if (!r.subscribed) return;
+ unsubscribeLocked(r);;
}
}
@@ -393,8 +287,9 @@
RemoteException re = null;
if (provider != null) {
try {
- Slog.d(TAG, "Subscribing to " + r.id + " with " + provider);
+ Slog.d(TAG, "Subscribing to " + r.id + " with " + r.component);
provider.onSubscribe(r.id);
+ r.subscribed = true;
} catch (RemoteException e) {
Slog.w(TAG, "Error subscribing to " + r, e);
re = e;
@@ -417,53 +312,6 @@
return rt;
}
- public void setAutomaticZenModeConditions(Uri[] conditionIds) {
- setAutomaticZenModeConditions(conditionIds, true /*save*/);
- }
-
- private void setAutomaticZenModeConditions(Uri[] conditionIds, boolean save) {
- if (DEBUG) Slog.d(TAG, "setAutomaticZenModeConditions "
- + (conditionIds == null ? null : Arrays.asList(conditionIds)));
- synchronized(mMutex) {
- final ArraySet<Uri> newIds = safeSet(conditionIds);
- final int N = mRecords.size();
- boolean changed = false;
- for (int i = 0; i < N; i++) {
- final ConditionRecord r = mRecords.get(i);
- final boolean automatic = newIds.contains(r.id);
- if (!r.isAutomatic && automatic) {
- // subscribe to new automatic
- subscribeLocked(r);
- r.isAutomatic = true;
- changed = true;
- } else if (r.isAutomatic && !automatic) {
- // unsubscribe from old automatic
- unsubscribeLocked(r);
- r.isAutomatic = false;
- changed = true;
- }
- }
- if (save && changed) {
- saveZenConfigLocked();
- }
- }
- }
-
- public Condition[] getAutomaticZenModeConditions() {
- synchronized(mMutex) {
- final int N = mRecords.size();
- ArrayList<Condition> rt = null;
- for (int i = 0; i < N; i++) {
- final ConditionRecord r = mRecords.get(i);
- if (r.isAutomatic && r.condition != null) {
- if (rt == null) rt = new ArrayList<Condition>();
- rt.add(r.condition);
- }
- }
- return rt == null ? NO_CONDITIONS : rt.toArray(new Condition[rt.size()]);
- }
- }
-
private void unsubscribeLocked(ConditionRecord r) {
if (DEBUG) Slog.d(TAG, "unsubscribeLocked " + r);
final IConditionProvider provider = provider(r);
@@ -475,6 +323,7 @@
Slog.w(TAG, "Error unsubscribing to " + r, e);
re = e;
}
+ r.subscribed = false;
}
ZenLog.traceUnsubscribe(r != null ? r.id : null, provider, re);
}
@@ -495,7 +344,7 @@
for (int i = mRecords.size() - 1; i >= 0; i--) {
final ConditionRecord r = mRecords.get(i);
if (r.info != info) continue;
- if (r.isManual || r.isAutomatic) continue;
+ if (r.subscribed) continue;
mRecords.remove(i);
}
try {
@@ -506,103 +355,12 @@
}
}
- private void loadZenConfig() {
- final ZenModeConfig config = mZenModeHelper.getConfig();
- if (config == null) {
- if (DEBUG) Slog.d(TAG, "loadZenConfig: no config");
- return;
- }
- synchronized (mMutex) {
- final boolean changingExit = !Objects.equals(mExitCondition, config.exitCondition);
- mExitCondition = config.exitCondition;
- mExitConditionComponent = config.exitConditionComponent;
- if (changingExit) {
- ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, "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");
- setAutomaticZenModeConditions(null, false /*save*/);
- return;
- }
- final ArraySet<Uri> newIds = new ArraySet<Uri>();
- final int N = config.conditionComponents.length;
- for (int i = 0; i < N; i++) {
- final ComponentName component = config.conditionComponents[i];
- final Uri id = config.conditionIds[i];
- if (component != null && id != null) {
- getRecordLocked(id, component); // ensure record exists
- newIds.add(id);
- }
- }
- if (DEBUG) Slog.d(TAG, "loadZenConfig: N=" + N);
- setAutomaticZenModeConditions(newIds.toArray(new Uri[newIds.size()]), false /*save*/);
- }
- }
-
- private void saveZenConfigLocked() {
- ZenModeConfig config = mZenModeHelper.getConfig();
- if (config == null) return;
- config = config.copy();
- final ArrayList<ConditionRecord> automatic = new ArrayList<ConditionRecord>();
- final int automaticN = mRecords.size();
- for (int i = 0; i < automaticN; i++) {
- final ConditionRecord r = mRecords.get(i);
- if (r.isAutomatic) {
- automatic.add(r);
- }
- }
- if (automatic.isEmpty()) {
- config.conditionComponents = null;
- config.conditionIds = null;
- } else {
- final int N = automatic.size();
- config.conditionComponents = new ComponentName[N];
- config.conditionIds = new Uri[N];
- for (int i = 0; i < N; i++) {
- final ConditionRecord r = automatic.get(i);
- config.conditionComponents[i] = r.component;
- config.conditionIds[i] = r.id;
- }
- }
- config.exitCondition = mExitCondition;
- config.exitConditionComponent = mExitConditionComponent;
- if (DEBUG) Slog.d(TAG, "Setting zen config to: " + config);
- mZenModeHelper.setConfig(config);
- }
-
- private void onManualConditionClearing() {
- if (mDowntime != null) {
- mDowntime.onManualConditionClearing();
- }
- }
-
- private class ZenModeHelperCallback extends ZenModeHelper.Callback {
- @Override
- void onConfigChanged() {
- loadZenConfig();
- }
-
- @Override
- void onZenModeChanged() {
- final int mode = mZenModeHelper.getZenMode();
- if (mode == Global.ZEN_MODE_OFF) {
- // ensure any manual condition is cleared
- setZenModeCondition(null, "zenOff");
- }
- }
- }
-
private static class ConditionRecord {
public final Uri id;
public final ComponentName component;
public Condition condition;
public ManagedServiceInfo info;
- public boolean isAutomatic;
- public boolean isManual;
+ public boolean subscribed;
private ConditionRecord(Uri id, ComponentName component) {
this.id = id;
@@ -612,10 +370,16 @@
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("ConditionRecord[id=")
- .append(id).append(",component=").append(component);
- if (isAutomatic) sb.append(",automatic");
- if (isManual) sb.append(",manual");
+ .append(id).append(",component=").append(component)
+ .append(",subscribed=").append(subscribed);
return sb.append(']').toString();
}
}
+
+ public interface Callback {
+ void onBootComplete();
+ void onConditionChanged(Uri id, Condition condition);
+ void onUserSwitched();
+ }
+
}
diff --git a/services/core/java/com/android/server/notification/CountdownConditionProvider.java b/services/core/java/com/android/server/notification/CountdownConditionProvider.java
index 37aacaa..d223353 100644
--- a/services/core/java/com/android/server/notification/CountdownConditionProvider.java
+++ b/services/core/java/com/android/server/notification/CountdownConditionProvider.java
@@ -25,7 +25,6 @@
import android.content.IntentFilter;
import android.net.Uri;
import android.service.notification.Condition;
-import android.service.notification.ConditionProviderService;
import android.service.notification.IConditionProvider;
import android.service.notification.ZenModeConfig;
import android.text.format.DateUtils;
@@ -38,7 +37,7 @@
import java.util.Date;
/** Built-in zen condition provider for simple time-based conditions */
-public class CountdownConditionProvider extends ConditionProviderService {
+public class CountdownConditionProvider extends SystemConditionProviderService {
private static final String TAG = "CountdownConditions";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -59,6 +58,27 @@
if (DEBUG) Slog.d(TAG, "new CountdownConditionProvider()");
}
+ @Override
+ public ComponentName getComponent() {
+ return COMPONENT;
+ }
+
+ @Override
+ public boolean isValidConditionid(Uri id) {
+ return ZenModeConfig.isValidCountdownConditionId(id);
+ }
+
+ @Override
+ public void attachBase(Context base) {
+ attachBaseContext(base);
+ }
+
+ @Override
+ public IConditionProvider asInterface() {
+ return (IConditionProvider) onBind(null);
+ }
+
+ @Override
public void dump(PrintWriter pw, DumpFilter filter) {
pw.println(" CountdownConditionProvider:");
pw.print(" mConnected="); pw.println(mConnected);
@@ -154,11 +174,4 @@
return new Date(time) + " (" + time + ")";
}
- public void attachBase(Context base) {
- attachBaseContext(base);
- }
-
- public IConditionProvider asInterface() {
- return (IConditionProvider) onBind(null);
- }
}
diff --git a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java
deleted file mode 100644
index df4ecfd..0000000
--- a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java
+++ /dev/null
@@ -1,409 +0,0 @@
-/**
- * Copyright (c) 2014, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.notification;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.app.AlarmManager.AlarmClockInfo;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.Uri;
-import android.provider.Settings.Global;
-import android.service.notification.Condition;
-import android.service.notification.ConditionProviderService;
-import android.service.notification.IConditionProvider;
-import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.DowntimeInfo;
-import android.text.format.DateFormat;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import 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.Date;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.TimeZone;
-
-/** Built-in zen condition provider for managing downtime */
-public class DowntimeConditionProvider extends ConditionProviderService {
- private static final String TAG = "DowntimeConditions";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- public static final ComponentName COMPONENT =
- new ComponentName("android", DowntimeConditionProvider.class.getName());
-
- private static final String ENTER_ACTION = TAG + ".enter";
- private static final int ENTER_CODE = 100;
- private static final String EXIT_ACTION = TAG + ".exit";
- private static final int EXIT_CODE = 101;
- private static final String EXTRA_TIME = "time";
-
- private 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 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 long mLookaheadThreshold;
- private ZenModeConfig mConfig;
- private boolean mDowntimed;
- private boolean mConditionClearing;
- private boolean mRequesting;
-
- 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(" 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) {
- attachBaseContext(base);
- }
-
- public IConditionProvider asInterface() {
- return (IConditionProvider) onBind(null);
- }
-
- @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.addCallback(mTrackerCallback);
- mZenModeHelper.addCallback(mZenCallback);
- init();
- }
-
- @Override
- 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 (!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) 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) {
- 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;
- final boolean downtimeChanged = mConfig == null || config == null
- || !mConfig.toDowntimeInfo().equals(config.toDowntimeInfo());
- mConfig = config;
- 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 void onManualConditionClearing() {
- mConditionClearing = true;
- }
-
- 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 = 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) {
- endTime = nextAlarmTime;
- }
- }
- final String formatted = new SimpleDateFormat(pattern, locale).format(new Date(endTime));
- final String summary = mContext.getString(R.string.downtime_condition_summary, formatted);
- final String line1 = mContext.getString(R.string.downtime_condition_line_one);
- return new Condition(id, summary, line1, formatted, 0, state, Condition.FLAG_RELEVANT_NOW);
- }
-
- private void init() {
- mCalendar.setDowntimeInfo(mConfig != null ? mConfig.toDowntimeInfo() : null);
- evaluateSubscriptions();
- updateAlarms();
- evaluateAutotrigger();
- }
-
- private void updateAlarms() {
- if (mConfig == null) return;
- updateAlarm(ENTER_ACTION, ENTER_CODE, mConfig.sleepStartHour, mConfig.sleepStartMinute);
- updateAlarm(EXIT_ACTION, EXIT_CODE, mConfig.sleepEndHour, mConfig.sleepEndMinute);
- }
-
-
- private void updateAlarm(String action, int requestCode, int hr, int min) {
- final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- final long now = System.currentTimeMillis();
- final long time = mCalendar.getNextTime(now, hr, min);
- final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, requestCode,
- new Intent(action)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
- .putExtra(EXTRA_TIME, time),
- PendingIntent.FLAG_UPDATE_CURRENT);
- alarms.cancel(pendingIntent);
- if (mConfig.sleepMode != null) {
- if (DEBUG) Slog.d(TAG, String.format("Scheduling %s for %s, in %s, now=%s",
- action, ts(time), NextAlarmTracker.formatDuration(time - now), ts(now)));
- alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
- }
- }
-
- private static String ts(long time) {
- return new Date(time) + " (" + time + ")";
- }
-
- private void onEvaluateNextAlarm(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
- if (!booted) return; // we don't know yet
- if (DEBUG) Slog.d(TAG, "onEvaluateNextAlarm " + mTracker.formatAlarmDebug(nextAlarm));
- if (nextAlarm != null && wakeupTime > 0 && System.currentTimeMillis() > wakeupTime) {
- if (DEBUG) Slog.d(TAG, "Alarm fired: " + mTracker.formatAlarmDebug(wakeupTime));
- mFiredAlarms.add(wakeupTime);
- }
- evaluateSubscriptions();
- }
-
- 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() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- final long now = System.currentTimeMillis();
- if (ENTER_ACTION.equals(action) || EXIT_ACTION.equals(action)) {
- final long schTime = intent.getLongExtra(EXTRA_TIME, 0);
- if (DEBUG) Slog.d(TAG, String.format("%s scheduled for %s, fired at %s, delta=%s",
- action, ts(schTime), ts(now), now - schTime));
- 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());
- mFiredAlarms.clear();
- } else if (Intent.ACTION_TIME_CHANGED.equals(action)) {
- if (DEBUG) Slog.d(TAG, "time changed to " + now);
- mFiredAlarms.clear();
- } else {
- if (DEBUG) Slog.d(TAG, action + " fired at " + now);
- }
- evaluateSubscriptions();
- updateAlarms();
- }
- };
-
- private final NextAlarmTracker.Callback mTrackerCallback = new NextAlarmTracker.Callback() {
- @Override
- public void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
- DowntimeConditionProvider.this.onEvaluateNextAlarm(nextAlarm, wakeupTime, booted);
- }
- };
-
- 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
deleted file mode 100644
index 1634c65..0000000
--- a/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.notification;
-
-import android.app.AlarmManager;
-import android.app.AlarmManager.AlarmClockInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.net.Uri;
-import android.service.notification.Condition;
-import android.service.notification.ConditionProviderService;
-import android.service.notification.IConditionProvider;
-import android.service.notification.ZenModeConfig;
-import android.text.TextUtils;
-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;
-
-/**
- * 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.
- *
- * <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
- * a persisted condition until we receive the first value after reboot, or timeout with no value.
- */
-public class NextAlarmConditionProvider extends ConditionProviderService {
- private static final String TAG = "NextAlarmConditions";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- private static final long SECONDS = 1000;
- private static final long MINUTES = 60 * SECONDS;
- private static final long HOURS = 60 * MINUTES;
-
- 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 boolean mConnected;
- private long mLookaheadThreshold;
- private boolean mRequesting;
-
- public NextAlarmConditionProvider(NextAlarmTracker tracker) {
- if (DEBUG) Slog.d(TAG, "new NextAlarmConditionProvider()");
- mTracker = tracker;
- }
-
- public void dump(PrintWriter pw, DumpFilter filter) {
- pw.println(" NextAlarmConditionProvider:");
- pw.print(" mConnected="); pw.println(mConnected);
- pw.print(" mLookaheadThreshold="); pw.print(mLookaheadThreshold);
- pw.print(" ("); TimeUtils.formatDuration(mLookaheadThreshold, pw); pw.println(")");
- pw.print(" mSubscriptions="); pw.println(mSubscriptions);
- pw.print(" mRequesting="); pw.println(mRequesting);
- }
-
- @Override
- public void onConnected() {
- if (DEBUG) Slog.d(TAG, "onConnected");
- mLookaheadThreshold = PropConfig.getInt(mContext, "nextalarm.condition.lookahead",
- R.integer.config_next_alarm_condition_lookahead_threshold_hrs) * HOURS;
- mConnected = true;
- mTracker.addCallback(mTrackerCallback);
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (DEBUG) Slog.d(TAG, "onDestroy");
- mTracker.removeCallback(mTrackerCallback);
- mConnected = false;
- }
-
- @Override
- public void onRequestConditions(int relevance) {
- 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 (tryParseNextAlarmCondition(conditionId) == BAD_CONDITION) {
- notifyCondition(conditionId, null, Condition.STATE_FALSE, "badCondition");
- return;
- }
- mSubscriptions.add(conditionId);
- mTracker.evaluate();
- }
-
- @Override
- public void onUnsubscribe(Uri conditionId) {
- if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId);
- mSubscriptions.remove(conditionId);
- }
-
- public void attachBase(Context base) {
- attachBaseContext(base);
- }
-
- public IConditionProvider asInterface() {
- return (IConditionProvider) onBind(null);
- }
-
- private boolean isWithinLookaheadThreshold(AlarmClockInfo alarm) {
- if (alarm == null) return false;
- final long delta = NextAlarmTracker.getEarlyTriggerTime(alarm) - System.currentTimeMillis();
- return delta > 0 && (mLookaheadThreshold <= 0 || delta < mLookaheadThreshold);
- }
-
- private void notifyCondition(Uri id, AlarmClockInfo alarm, int state, String reason) {
- final String formattedAlarm = alarm == null ? "" : mTracker.formatAlarm(alarm);
- if (DEBUG) Slog.d(TAG, "notifyCondition " + Condition.stateToString(state)
- + " alarm=" + formattedAlarm + " reason=" + reason);
- notifyCondition(new Condition(id,
- mContext.getString(R.string.zen_mode_next_alarm_summary, formattedAlarm),
- mContext.getString(R.string.zen_mode_next_alarm_line_one),
- formattedAlarm, 0, state, Condition.FLAG_RELEVANT_NOW));
- }
-
- private Uri newConditionId(AlarmClockInfo nextAlarm) {
- return new Uri.Builder().scheme(Condition.SCHEME)
- .authority(ZenModeConfig.SYSTEM_AUTHORITY)
- .appendPath(ZenModeConfig.NEXT_ALARM_PATH)
- .appendPath(Integer.toString(mTracker.getCurrentUserId()))
- .appendPath(Long.toString(nextAlarm.getTriggerTime()))
- .build();
- }
-
- private long tryParseNextAlarmCondition(Uri conditionId) {
- return conditionId != null && conditionId.getScheme().equals(Condition.SCHEME)
- && conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY)
- && conditionId.getPathSegments().size() == 3
- && conditionId.getPathSegments().get(0).equals(ZenModeConfig.NEXT_ALARM_PATH)
- && conditionId.getPathSegments().get(1)
- .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);
- 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);
-
- 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);
- }
- }
- 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");
- }
- }
- }
-
- private final NextAlarmTracker.Callback mTrackerCallback = new NextAlarmTracker.Callback() {
- @Override
- public void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
- NextAlarmConditionProvider.this.onEvaluate(nextAlarm, wakeupTime, booted);
- }
- };
-}
diff --git a/services/core/java/com/android/server/notification/NextAlarmTracker.java b/services/core/java/com/android/server/notification/NextAlarmTracker.java
deleted file mode 100644
index 234f545..0000000
--- a/services/core/java/com/android/server/notification/NextAlarmTracker.java
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.notification;
-
-import android.app.ActivityManager;
-import android.app.AlarmManager;
-import android.app.AlarmManager.AlarmClockInfo;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Handler;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.UserHandle;
-import android.text.format.DateFormat;
-import android.util.Log;
-import android.util.Slog;
-import android.util.TimeUtils;
-
-import com.android.server.notification.NotificationManagerService.DumpFilter;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Locale;
-
-/** Helper for tracking updates to the current user's next alarm. */
-public class NextAlarmTracker {
- private static final String TAG = "NextAlarmTracker";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- private static final String ACTION_TRIGGER = TAG + ".trigger";
- private static final String EXTRA_TRIGGER = "trigger";
- private static final int REQUEST_CODE = 100;
-
- private static final long SECONDS = 1000;
- private static final long MINUTES = 60 * SECONDS;
- private static final long NEXT_ALARM_UPDATE_DELAY = 1 * SECONDS; // treat clear+set as update
- private static final long EARLY = 5 * SECONDS; // fire early, ensure alarm stream is unmuted
- private static final long WAIT_AFTER_INIT = 5 * MINUTES;// for initial alarm re-registration
- private static final long WAIT_AFTER_BOOT = 20 * SECONDS; // for initial alarm re-registration
-
- private final Context mContext;
- private final H mHandler = new H();
- private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
-
- private long mInit;
- private boolean mRegistered;
- private AlarmManager mAlarmManager;
- private int mCurrentUserId;
- private long mScheduledAlarmTime;
- private long mBootCompleted;
- private PowerManager.WakeLock mWakeLock;
-
- public NextAlarmTracker(Context context) {
- mContext = context;
- }
-
- public void dump(PrintWriter pw, DumpFilter filter) {
- pw.println(" NextAlarmTracker:");
- pw.print(" len(mCallbacks)="); pw.println(mCallbacks.size());
- pw.print(" mRegistered="); pw.println(mRegistered);
- pw.print(" mInit="); pw.println(mInit);
- pw.print(" mBootCompleted="); pw.println(mBootCompleted);
- pw.print(" mCurrentUserId="); pw.println(mCurrentUserId);
- pw.print(" mScheduledAlarmTime="); pw.println(formatAlarmDebug(mScheduledAlarmTime));
- pw.print(" mWakeLock="); pw.println(mWakeLock);
- }
-
- public void addCallback(Callback callback) {
- mCallbacks.add(callback);
- }
-
- public void removeCallback(Callback callback) {
- mCallbacks.remove(callback);
- }
-
- public int getCurrentUserId() {
- return mCurrentUserId;
- }
-
- public AlarmClockInfo getNextAlarm() {
- return mAlarmManager.getNextAlarmClock(mCurrentUserId);
- }
-
- public void onUserSwitched() {
- reset();
- }
-
- public void init() {
- mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- final PowerManager p = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- mWakeLock = p.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
- mInit = System.currentTimeMillis();
- reset();
- }
-
- public void reset() {
- if (mRegistered) {
- mContext.unregisterReceiver(mReceiver);
- }
- mCurrentUserId = ActivityManager.getCurrentUser();
- final IntentFilter filter = new IntentFilter();
- filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
- filter.addAction(ACTION_TRIGGER);
- filter.addAction(Intent.ACTION_TIME_CHANGED);
- filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
- filter.addAction(Intent.ACTION_BOOT_COMPLETED);
- mContext.registerReceiverAsUser(mReceiver, new UserHandle(mCurrentUserId), filter, null,
- null);
- mRegistered = true;
- evaluate();
- }
-
- public void destroy() {
- if (mRegistered) {
- mContext.unregisterReceiver(mReceiver);
- mRegistered = false;
- }
- }
-
- public void evaluate() {
- mHandler.postEvaluate(0);
- }
-
- private void fireEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
- for (Callback callback : mCallbacks) {
- callback.onEvaluate(nextAlarm, wakeupTime, booted);
- }
- }
-
- private void handleEvaluate() {
- final AlarmClockInfo nextAlarm = mAlarmManager.getNextAlarmClock(mCurrentUserId);
- final long triggerTime = getEarlyTriggerTime(nextAlarm);
- final long now = System.currentTimeMillis();
- final boolean alarmUpcoming = triggerTime > now;
- final boolean booted = isDoneWaitingAfterBoot(now);
- if (DEBUG) Slog.d(TAG, "handleEvaluate nextAlarm=" + formatAlarmDebug(triggerTime)
- + " alarmUpcoming=" + alarmUpcoming
- + " booted=" + booted);
- fireEvaluate(nextAlarm, triggerTime, booted);
- if (!booted) {
- // recheck after boot
- final long recheckTime = (mBootCompleted > 0 ? mBootCompleted : now) + WAIT_AFTER_BOOT;
- rescheduleAlarm(recheckTime);
- return;
- }
- if (alarmUpcoming) {
- // wake up just before the next alarm
- rescheduleAlarm(triggerTime);
- }
- }
-
- public static long getEarlyTriggerTime(AlarmClockInfo alarm) {
- return alarm != null ? (alarm.getTriggerTime() - EARLY) : 0;
- }
-
- private boolean isDoneWaitingAfterBoot(long time) {
- if (mBootCompleted > 0) return (time - mBootCompleted) > WAIT_AFTER_BOOT;
- if (mInit > 0) return (time - mInit) > WAIT_AFTER_INIT;
- return true;
- }
-
- public static String formatDuration(long millis) {
- final StringBuilder sb = new StringBuilder();
- TimeUtils.formatDuration(millis, sb);
- return sb.toString();
- }
-
- public String formatAlarm(AlarmClockInfo alarm) {
- return alarm != null ? formatAlarm(alarm.getTriggerTime()) : null;
- }
-
- private String formatAlarm(long time) {
- return formatAlarm(time, "Hm", "hma");
- }
-
- private String formatAlarm(long time, String skeleton24, String skeleton12) {
- final String skeleton = DateFormat.is24HourFormat(mContext) ? skeleton24 : skeleton12;
- final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
- return DateFormat.format(pattern, time).toString();
- }
-
- public String formatAlarmDebug(AlarmClockInfo alarm) {
- return formatAlarmDebug(alarm != null ? alarm.getTriggerTime() : 0);
- }
-
- public String formatAlarmDebug(long time) {
- if (time <= 0) return Long.toString(time);
- return String.format("%s (%s)", time, formatAlarm(time, "Hms", "hmsa"));
- }
-
- private void rescheduleAlarm(long time) {
- if (DEBUG) Slog.d(TAG, "rescheduleAlarm " + time);
- final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, REQUEST_CODE,
- new Intent(ACTION_TRIGGER)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
- .putExtra(EXTRA_TRIGGER, time),
- PendingIntent.FLAG_UPDATE_CURRENT);
- alarms.cancel(pendingIntent);
- mScheduledAlarmTime = time;
- if (time > 0) {
- if (DEBUG) Slog.d(TAG, String.format("Scheduling alarm for %s (in %s)",
- formatAlarmDebug(time), formatDuration(time - System.currentTimeMillis())));
- alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
- }
- }
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (DEBUG) Slog.d(TAG, "onReceive " + action);
- long delay = 0;
- if (action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) {
- delay = NEXT_ALARM_UPDATE_DELAY;
- if (DEBUG) Slog.d(TAG, String.format(" next alarm for user %s: %s",
- mCurrentUserId,
- formatAlarmDebug(mAlarmManager.getNextAlarmClock(mCurrentUserId))));
- } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
- mBootCompleted = System.currentTimeMillis();
- }
- mHandler.postEvaluate(delay);
- mWakeLock.acquire(delay + 5000); // stay awake during evaluate
- }
- };
-
- private class H extends Handler {
- private static final int MSG_EVALUATE = 1;
-
- public void postEvaluate(long delay) {
- removeMessages(MSG_EVALUATE);
- sendEmptyMessageDelayed(MSG_EVALUATE, delay);
- }
-
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == MSG_EVALUATE) {
- handleEvaluate();
- }
- }
- }
-
- public interface Callback {
- void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted);
- }
-}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c330046..4cf2909 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -879,7 +879,8 @@
mRankingHelper = new RankingHelper(getContext(),
new RankingWorkerHandler(mRankingThread.getLooper()),
extractorNames);
- mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper());
+ mConditionProviders = new ConditionProviders(getContext(), mHandler, mUserProfiles);
+ mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders);
mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
@Override
public void onConfigChanged() {
@@ -900,8 +901,6 @@
importOldBlockDb();
mListeners = new NotificationListeners();
- mConditionProviders = new ConditionProviders(getContext(),
- mHandler, mUserProfiles, mZenModeHelper);
mStatusBar = getLocalService(StatusBarManagerInternal.class);
mStatusBar.setNotificationDelegate(mNotificationDelegate);
@@ -936,7 +935,7 @@
Settings.Global.DEVICE_PROVISIONED, 0)) {
mDisableNotificationEffects = true;
}
- mZenModeHelper.readZenModeFromSetting();
+ mZenModeHelper.initZenMode();
mInterruptionFilter = mZenModeHelper.getZenModeListenerInterruptionFilter();
mUserProfiles.updateCache(getContext());
@@ -1490,23 +1489,28 @@
}
@Override
+ public int getZenMode() {
+ return mZenModeHelper.getZenMode();
+ }
+
+ @Override
public ZenModeConfig getZenModeConfig() {
enforceSystemOrSystemUIOrVolume("INotificationManager.getZenModeConfig");
return mZenModeHelper.getConfig();
}
@Override
- public boolean setZenModeConfig(ZenModeConfig config) {
+ public boolean setZenModeConfig(ZenModeConfig config, String reason) {
checkCallerIsSystem();
- return mZenModeHelper.setConfig(config);
+ return mZenModeHelper.setConfig(config, reason);
}
@Override
- public void setZenMode(int mode) throws RemoteException {
+ public void setZenMode(int mode, Uri conditionId, String reason) throws RemoteException {
enforceSystemOrSystemUIOrVolume("INotificationManager.setZenMode");
final long identity = Binder.clearCallingIdentity();
try {
- mZenModeHelper.setZenMode(mode, "NotificationManager");
+ mZenModeHelper.setManualZenMode(mode, conditionId, reason);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -1528,30 +1532,7 @@
@Override
public void requestZenModeConditions(IConditionListener callback, int relevance) {
enforceSystemOrSystemUIOrVolume("INotificationManager.requestZenModeConditions");
- mConditionProviders.requestZenModeConditions(callback, relevance);
- }
-
- @Override
- public void setZenModeCondition(Condition condition) {
- enforceSystemOrSystemUIOrVolume("INotificationManager.setZenModeCondition");
- final long identity = Binder.clearCallingIdentity();
- try {
- mConditionProviders.setZenModeCondition(condition, "binderCall");
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @Override
- public void setAutomaticZenModeConditions(Uri[] conditionIds) {
- enforceSystemOrSystemUI("INotificationManager.setAutomaticZenModeConditions");
- mConditionProviders.setAutomaticZenModeConditions(conditionIds);
- }
-
- @Override
- public Condition[] getAutomaticZenModeConditions() {
- enforceSystemOrSystemUI("INotificationManager.getAutomaticZenModeConditions");
- return mConditionProviders.getAutomaticZenModeConditions();
+ mZenModeHelper.requestZenModeConditions(callback, relevance);
}
private void enforceSystemOrSystemUIOrVolume(String message) {
@@ -1603,7 +1584,7 @@
@Override
public boolean isSystemConditionProviderEnabled(String path) {
enforceSystemOrSystemUIOrVolume("INotificationManager.isSystemConditionProviderEnabled");
- return mConditionProviders.isSystemConditionProviderEnabled(path);
+ return mConditionProviders.isSystemProviderEnabled(path);
}
};
diff --git a/services/core/java/com/android/server/notification/DowntimeCalendar.java b/services/core/java/com/android/server/notification/ScheduleCalendar.java
similarity index 62%
rename from services/core/java/com/android/server/notification/DowntimeCalendar.java
rename to services/core/java/com/android/server/notification/ScheduleCalendar.java
index d14fd40..cea611d 100644
--- a/services/core/java/com/android/server/notification/DowntimeCalendar.java
+++ b/services/core/java/com/android/server/notification/ScheduleCalendar.java
@@ -16,38 +16,36 @@
package com.android.server.notification;
+import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.util.ArraySet;
+
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 {
-
+public class ScheduleCalendar {
private final ArraySet<Integer> mDays = new ArraySet<Integer>();
private final Calendar mCalendar = Calendar.getInstance();
- private DowntimeInfo mInfo;
+ private ScheduleInfo mSchedule;
@Override
public String toString() {
- return "DowntimeCalendar[mDays=" + mDays + "]";
+ return "ScheduleCalendar[mDays=" + mDays + "]";
}
- public void setDowntimeInfo(DowntimeInfo info) {
- if (Objects.equals(mInfo, info)) return;
- mInfo = info;
+ public void setSchedule(ScheduleInfo schedule) {
+ if (Objects.equals(mSchedule, schedule)) return;
+ mSchedule = schedule;
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);
+ public long nextScheduleStart(long time) {
+ if (mSchedule == null || mDays.size() == 0) return Long.MAX_VALUE;
+ final long start = getTime(time, mSchedule.startHour, mSchedule.startMinute);
for (int i = 0; i < Calendar.SATURDAY; i++) {
final long t = addDays(start, i);
- if (t > time && isInDowntime(t)) {
+ if (t > time && isInSchedule(t)) {
return t;
}
}
@@ -58,7 +56,14 @@
mCalendar.setTimeZone(tz);
}
- public long getNextTime(long now, int hr, int min) {
+ public long getNextChangeTime(long now) {
+ if (mSchedule == null) return 0;
+ final long nextStart = getNextTime(now, mSchedule.startHour, mSchedule.startMinute);
+ final long nextEnd = getNextTime(now, mSchedule.endHour, mSchedule.endMinute);
+ return Math.min(nextStart, nextEnd);
+ }
+
+ private long getNextTime(long now, int hr, int min) {
final long time = getTime(now, hr, min);
return time <= now ? addDays(time, 1) : time;
}
@@ -72,17 +77,17 @@
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);
+ public boolean isInSchedule(long time) {
+ if (mSchedule == null || mDays.size() == 0) return false;
+ final long start = getTime(time, mSchedule.startHour, mSchedule.startMinute);
+ long end = getTime(time, mSchedule.endHour, mSchedule.endMinute);
if (end <= start) {
end = addDays(end, 1);
}
- return isInDowntime(-1, time, start, end) || isInDowntime(0, time, start, end);
+ return isInSchedule(-1, time, start, end) || isInSchedule(0, time, start, end);
}
- private boolean isInDowntime(int daysOffset, long time, long start, long end) {
+ private boolean isInSchedule(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);
@@ -97,10 +102,9 @@
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]);
+ if (mSchedule != null && mSchedule.days != null) {
+ for (int i = 0; i < mSchedule.days.length; i++) {
+ mDays.add(mSchedule.days[i]);
}
}
}
@@ -110,4 +114,4 @@
mCalendar.add(Calendar.DATE, days);
return mCalendar.getTimeInMillis();
}
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
new file mode 100644
index 0000000..c997e45
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.service.notification.Condition;
+import android.service.notification.IConditionProvider;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import com.android.server.notification.NotificationManagerService.DumpFilter;
+
+import java.io.PrintWriter;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * Built-in zen condition provider for daily scheduled time-based conditions.
+ */
+public class ScheduleConditionProvider extends SystemConditionProviderService {
+ private static final String TAG = "ScheduleConditions";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ public static final ComponentName COMPONENT =
+ new ComponentName("android", ScheduleConditionProvider.class.getName());
+ private static final String NOT_SHOWN = "...";
+ private static final String ACTION_EVALUATE = TAG + ".EVALUATE";
+ private static final int REQUEST_CODE_EVALUATE = 1;
+ private static final String EXTRA_TIME = "time";
+
+ private final Context mContext = this;
+ private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>();
+
+ private boolean mConnected;
+ private boolean mRegistered;
+
+ public ScheduleConditionProvider() {
+ if (DEBUG) Slog.d(TAG, "new ScheduleConditionProvider()");
+ }
+
+ @Override
+ public ComponentName getComponent() {
+ return COMPONENT;
+ }
+
+ @Override
+ public boolean isValidConditionid(Uri id) {
+ return ZenModeConfig.isValidScheduleConditionId(id);
+ }
+
+ @Override
+ public void dump(PrintWriter pw, DumpFilter filter) {
+ pw.println(" ScheduleConditionProvider:");
+ pw.print(" mConnected="); pw.println(mConnected);
+ pw.print(" mRegistered="); pw.println(mRegistered);
+ pw.println(" mSubscriptions=");
+ final long now = System.currentTimeMillis();
+ for (Uri conditionId : mSubscriptions) {
+ pw.print(" ");
+ pw.print(meetsSchedule(conditionId, now) ? "* " : " ");
+ pw.println(conditionId);
+ }
+ }
+
+ @Override
+ public void onConnected() {
+ if (DEBUG) Slog.d(TAG, "onConnected");
+ mConnected = true;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (DEBUG) Slog.d(TAG, "onDestroy");
+ mConnected = false;
+ }
+
+ @Override
+ public void onRequestConditions(int relevance) {
+ if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance);
+ // does not advertise conditions
+ }
+
+ @Override
+ public void onSubscribe(Uri conditionId) {
+ if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
+ if (!ZenModeConfig.isValidScheduleConditionId(conditionId)) {
+ notifyCondition(conditionId, Condition.STATE_FALSE, "badCondition");
+ return;
+ }
+ mSubscriptions.add(conditionId);
+ evaluateSubscriptions();
+ }
+
+ @Override
+ public void onUnsubscribe(Uri conditionId) {
+ if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId);
+ mSubscriptions.remove(conditionId);
+ evaluateSubscriptions();
+ }
+
+ @Override
+ public void attachBase(Context base) {
+ attachBaseContext(base);
+ }
+
+ @Override
+ public IConditionProvider asInterface() {
+ return (IConditionProvider) onBind(null);
+ }
+
+ private void evaluateSubscriptions() {
+ setRegistered(!mSubscriptions.isEmpty());
+ final long now = System.currentTimeMillis();
+ long nextAlarmTime = 0;
+ for (Uri conditionId : mSubscriptions) {
+ final ScheduleCalendar cal = toScheduleCalendar(conditionId);
+ if (cal != null && cal.isInSchedule(now)) {
+ notifyCondition(conditionId, Condition.STATE_TRUE, "meetsSchedule");
+ } else {
+ notifyCondition(conditionId, Condition.STATE_FALSE, "!meetsSchedule");
+ }
+ if (cal != null) {
+ final long nextChangeTime = cal.getNextChangeTime(now);
+ if (nextChangeTime > 0 && nextChangeTime > now) {
+ if (nextAlarmTime == 0 || nextChangeTime < nextAlarmTime) {
+ nextAlarmTime = nextChangeTime;
+ }
+ }
+ }
+ }
+ updateAlarm(now, nextAlarmTime);
+ }
+
+ private void updateAlarm(long now, long time) {
+ final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,
+ REQUEST_CODE_EVALUATE,
+ new Intent(ACTION_EVALUATE)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .putExtra(EXTRA_TIME, time),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ alarms.cancel(pendingIntent);
+ if (time > now) {
+ if (DEBUG) Slog.d(TAG, String.format("Scheduling evaluate for %s, in %s, now=%s",
+ ts(time), formatDuration(time - now), ts(now)));
+ alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
+ } else {
+ if (DEBUG) Slog.d(TAG, "Not scheduling evaluate");
+ }
+ }
+
+ private static String ts(long time) {
+ return new Date(time) + " (" + time + ")";
+ }
+
+ private static String formatDuration(long millis) {
+ final StringBuilder sb = new StringBuilder();
+ TimeUtils.formatDuration(millis, sb);
+ return sb.toString();
+ }
+
+ private static boolean meetsSchedule(Uri conditionId, long time) {
+ final ScheduleCalendar cal = toScheduleCalendar(conditionId);
+ return cal != null && cal.isInSchedule(time);
+ }
+
+ private static ScheduleCalendar toScheduleCalendar(Uri conditionId) {
+ final ScheduleInfo schedule = ZenModeConfig.tryParseScheduleConditionId(conditionId);
+ if (schedule == null || schedule.days == null || schedule.days.length == 0) return null;
+ final ScheduleCalendar sc = new ScheduleCalendar();
+ sc.setSchedule(schedule);
+ sc.setTimeZone(TimeZone.getDefault());
+ return sc;
+ }
+
+ private void setRegistered(boolean registered) {
+ if (mRegistered == registered) return;
+ if (DEBUG) Slog.d(TAG, "setRegistered " + registered);
+ mRegistered = registered;
+ if (mRegistered) {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
+ filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+ filter.addAction(ACTION_EVALUATE);
+ registerReceiver(mReceiver, filter);
+ } else {
+ unregisterReceiver(mReceiver);
+ }
+ }
+
+ private void notifyCondition(Uri conditionId, int state, String reason) {
+ if (DEBUG) Slog.d(TAG, "notifyCondition " + Condition.stateToString(state)
+ + " reason=" + reason);
+ notifyCondition(createCondition(conditionId, state));
+ }
+
+ private Condition createCondition(Uri id, int state) {
+ final String summary = NOT_SHOWN;
+ final String line1 = NOT_SHOWN;
+ final String line2 = NOT_SHOWN;
+ return new Condition(id, summary, line1, line2, 0, state, Condition.FLAG_RELEVANT_ALWAYS);
+ }
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction());
+ evaluateSubscriptions();
+ }
+ };
+
+}
diff --git a/services/core/java/com/android/server/notification/SystemConditionProviderService.java b/services/core/java/com/android/server/notification/SystemConditionProviderService.java
new file mode 100644
index 0000000..a217623
--- /dev/null
+++ b/services/core/java/com/android/server/notification/SystemConditionProviderService.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.net.Uri;
+import android.service.notification.ConditionProviderService;
+import android.service.notification.IConditionProvider;
+
+import com.android.server.notification.NotificationManagerService.DumpFilter;
+
+import java.io.PrintWriter;
+
+public abstract class SystemConditionProviderService extends ConditionProviderService {
+
+ abstract public void dump(PrintWriter pw, DumpFilter filter);
+ abstract public void attachBase(Context context);
+ abstract public IConditionProvider asInterface();
+ abstract public ComponentName getComponent();
+ abstract public boolean isValidConditionid(Uri id);
+}
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
new file mode 100644
index 0000000..67a2a54
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -0,0 +1,144 @@
+/**
+ * Copyright (c) 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.service.notification.Condition;
+import android.service.notification.IConditionListener;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+public class ZenModeConditions implements ConditionProviders.Callback {
+ private static final String TAG = ZenModeHelper.TAG;
+ private static final boolean DEBUG = ZenModeHelper.DEBUG;
+
+ private final ZenModeHelper mHelper;
+ private final ConditionProviders mConditionProviders;
+ private final ArrayMap<Uri, ComponentName> mSubscriptions = new ArrayMap<>();
+
+ private CountdownConditionProvider mCountdown;
+ private ScheduleConditionProvider mSchedule;
+
+ public ZenModeConditions(ZenModeHelper helper, ConditionProviders conditionProviders) {
+ mHelper = helper;
+ mConditionProviders = conditionProviders;
+ if (mConditionProviders.isSystemProviderEnabled(ZenModeConfig.COUNTDOWN_PATH)) {
+ mCountdown = new CountdownConditionProvider();
+ mConditionProviders.addSystemProvider(mCountdown);
+ }
+ if (mConditionProviders.isSystemProviderEnabled(ZenModeConfig.SCHEDULE_PATH)) {
+ mSchedule = new ScheduleConditionProvider();
+ mConditionProviders.addSystemProvider(mSchedule);
+ }
+ mConditionProviders.setCallback(this);
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.print("mSubscriptions="); pw.println(mSubscriptions);
+ }
+
+ public void requestConditions(IConditionListener callback, int relevance) {
+ mConditionProviders.requestConditions(callback, relevance);
+ }
+
+ public void evaluateConfig(ZenModeConfig config) {
+ if (config == null) return;
+ if (config.manualRule != null && !config.manualRule.isTrueOrUnknown()) {
+ if (DEBUG) Log.d(TAG, "evaluateConfig: clearing manual rule");
+ config.manualRule = null;
+ }
+ final ArraySet<Uri> current = new ArraySet<>();
+ evaluateRule(config.manualRule, current);
+ for (ZenRule automaticRule : config.automaticRules.values()) {
+ evaluateRule(automaticRule, current);
+ }
+ final int N = mSubscriptions.size();
+ for (int i = N - 1; i >= 0; i--) {
+ final Uri id = mSubscriptions.keyAt(i);
+ final ComponentName component = mSubscriptions.valueAt(i);
+ if (!current.contains(id)) {
+ mConditionProviders.unsubscribeIfNecessary(component, id);
+ mSubscriptions.removeAt(i);
+ }
+ }
+ }
+
+ private void evaluateRule(ZenRule rule, ArraySet<Uri> current) {
+ if (rule == null || rule.conditionId == null) return;
+ final Uri id = rule.conditionId;
+ for (SystemConditionProviderService sp : mConditionProviders.getSystemProviders()) {
+ if (sp.isValidConditionid(id)) {
+ mConditionProviders.ensureRecordExists(sp.getComponent(), id, sp.asInterface());
+ rule.component = sp.getComponent();
+ }
+ }
+ current.add(id);
+ if (mConditionProviders.subscribeIfNecessary(rule.component, rule.conditionId)) {
+ mSubscriptions.put(rule.conditionId, rule.component);
+ }
+ }
+
+ @Override
+ public void onBootComplete() {
+ // noop
+ }
+
+ @Override
+ public void onUserSwitched() {
+ // noop
+ }
+
+ @Override
+ public void onConditionChanged(Uri id, Condition condition) {
+ if (DEBUG) Log.d(TAG, "onConditionChanged " + id + " " + condition);
+ ZenModeConfig config = mHelper.getConfig();
+ if (config == null) return;
+ config = config.copy();
+ boolean updated = updateCondition(id, condition, config.manualRule);
+ for (ZenRule automaticRule : config.automaticRules.values()) {
+ updated |= updateCondition(id, condition, automaticRule);
+ updated |= updateSnoozing(automaticRule);
+ }
+ if (updated) {
+ mHelper.setConfig(config, "conditionChanged");
+ }
+ }
+
+ private boolean updateSnoozing(ZenRule rule) {
+ if (rule != null && rule.snoozing && !rule.isTrueOrUnknown()) {
+ rule.snoozing = false;
+ if (DEBUG) Log.d(TAG, "Snoozing reset for " + rule.conditionId);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean updateCondition(Uri id, Condition condition, ZenRule rule) {
+ if (id == null || rule == null || rule.conditionId == null) return false;
+ if (!rule.conditionId.equals(id)) return false;
+ if (Objects.equals(condition, rule.condition)) return false;
+ rule.condition = condition;
+ return true;
+ }
+}
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
new file mode 100644
index 0000000..32fd01a
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -0,0 +1,202 @@
+/**
+ * Copyright (c) 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import android.app.Notification;
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings.Global;
+import android.provider.Settings.Secure;
+import android.service.notification.ZenModeConfig;
+import android.telecom.TelecomManager;
+import android.util.Slog;
+
+import java.util.Objects;
+
+public class ZenModeFiltering {
+ private static final String TAG = ZenModeHelper.TAG;
+ private static final boolean DEBUG = ZenModeHelper.DEBUG;
+
+ private final Context mContext;
+
+ private ComponentName mDefaultPhoneApp;
+
+ public ZenModeFiltering(Context context) {
+ mContext = context;
+ }
+
+ public ComponentName getDefaultPhoneApp() {
+ return mDefaultPhoneApp;
+ }
+
+ /**
+ * @param extras extras of the notification with EXTRA_PEOPLE populated
+ * @param contactsTimeoutMs timeout in milliseconds to wait for contacts response
+ * @param timeoutAffinity affinity to return when the timeout specified via
+ * <code>contactsTimeoutMs</code> is hit
+ */
+ public static boolean matchesCallFilter(int zen, ZenModeConfig config, UserHandle userHandle,
+ Bundle extras, ValidateNotificationPeople validator, int contactsTimeoutMs,
+ float timeoutAffinity) {
+ if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through
+ if (zen == Global.ZEN_MODE_ALARMS) return false; // not an alarm
+ if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
+ if (!config.allowCalls) return false; // no calls get through
+ if (validator != null) {
+ final float contactAffinity = validator.getContactAffinity(userHandle, extras,
+ contactsTimeoutMs, timeoutAffinity);
+ return audienceMatches(config, contactAffinity);
+ }
+ }
+ return true;
+ }
+
+ public boolean shouldIntercept(int zen, ZenModeConfig config, NotificationRecord record) {
+ if (isSystem(record)) {
+ return false;
+ }
+ switch (zen) {
+ case Global.ZEN_MODE_NO_INTERRUPTIONS:
+ // #notevenalarms
+ ZenLog.traceIntercepted(record, "none");
+ return true;
+ case Global.ZEN_MODE_ALARMS:
+ if (isAlarm(record)) {
+ // Alarms only
+ return false;
+ }
+ ZenLog.traceIntercepted(record, "alarmsOnly");
+ return true;
+ case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+ if (isAlarm(record)) {
+ // Alarms are always priority
+ return false;
+ }
+ // allow user-prioritized packages through in priority mode
+ if (record.getPackagePriority() == Notification.PRIORITY_MAX) {
+ ZenLog.traceNotIntercepted(record, "priorityApp");
+ return false;
+ }
+ if (isCall(record)) {
+ if (!config.allowCalls) {
+ ZenLog.traceIntercepted(record, "!allowCalls");
+ return true;
+ }
+ return shouldInterceptAudience(config, record);
+ }
+ if (isMessage(record)) {
+ if (!config.allowMessages) {
+ ZenLog.traceIntercepted(record, "!allowMessages");
+ return true;
+ }
+ return shouldInterceptAudience(config, record);
+ }
+ if (isEvent(record)) {
+ if (!config.allowEvents) {
+ ZenLog.traceIntercepted(record, "!allowEvents");
+ return true;
+ }
+ return false;
+ }
+ if (isReminder(record)) {
+ if (!config.allowReminders) {
+ ZenLog.traceIntercepted(record, "!allowReminders");
+ return true;
+ }
+ return false;
+ }
+ ZenLog.traceIntercepted(record, "!priority");
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static boolean shouldInterceptAudience(ZenModeConfig config,
+ NotificationRecord record) {
+ if (!audienceMatches(config, record.getContactAffinity())) {
+ ZenLog.traceIntercepted(record, "!audienceMatches");
+ return true;
+ }
+ return false;
+ }
+
+ private static boolean isSystem(NotificationRecord record) {
+ return record.isCategory(Notification.CATEGORY_SYSTEM);
+ }
+
+ private static boolean isAlarm(NotificationRecord record) {
+ return record.isCategory(Notification.CATEGORY_ALARM)
+ || record.isAudioStream(AudioManager.STREAM_ALARM)
+ || record.isAudioAttributesUsage(AudioAttributes.USAGE_ALARM);
+ }
+
+ private static boolean isEvent(NotificationRecord record) {
+ return record.isCategory(Notification.CATEGORY_EVENT);
+ }
+
+ private static boolean isReminder(NotificationRecord record) {
+ return record.isCategory(Notification.CATEGORY_REMINDER);
+ }
+
+ public boolean isCall(NotificationRecord record) {
+ return record != null && (isDefaultPhoneApp(record.sbn.getPackageName())
+ || record.isCategory(Notification.CATEGORY_CALL));
+ }
+
+ private boolean isDefaultPhoneApp(String pkg) {
+ if (mDefaultPhoneApp == null) {
+ final TelecomManager telecomm =
+ (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
+ mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null;
+ if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp);
+ }
+ return pkg != null && mDefaultPhoneApp != null
+ && pkg.equals(mDefaultPhoneApp.getPackageName());
+ }
+
+ @SuppressWarnings("deprecation")
+ private boolean isDefaultMessagingApp(NotificationRecord record) {
+ final int userId = record.getUserId();
+ if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false;
+ final String defaultApp = Secure.getStringForUser(mContext.getContentResolver(),
+ Secure.SMS_DEFAULT_APPLICATION, userId);
+ return Objects.equals(defaultApp, record.sbn.getPackageName());
+ }
+
+ private boolean isMessage(NotificationRecord record) {
+ return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record);
+ }
+
+ private static boolean audienceMatches(ZenModeConfig config, float contactAffinity) {
+ switch (config.allowFrom) {
+ case ZenModeConfig.SOURCE_ANYONE:
+ return true;
+ case ZenModeConfig.SOURCE_CONTACT:
+ return contactAffinity >= ValidateNotificationPeople.VALID_CONTACT;
+ case ZenModeConfig.SOURCE_STAR:
+ return contactAffinity >= ValidateNotificationPeople.STARRED_CONTACT;
+ default:
+ Slog.w(TAG, "Encountered unknown source: " + config.allowFrom);
+ return true;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 1775df2..77f78fe 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -21,14 +21,12 @@
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
import android.app.AppOpsManager;
-import android.app.Notification;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
-import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
import android.media.VolumePolicy;
@@ -39,12 +37,13 @@
import android.os.Message;
import android.os.UserHandle;
import android.provider.Settings.Global;
-import android.provider.Settings.Secure;
+import android.service.notification.IConditionListener;
import android.service.notification.NotificationListenerService;
import android.service.notification.ZenModeConfig;
-import android.telecom.TelecomManager;
+import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.service.notification.ZenModeConfig.ZenRule;
+import android.util.ArraySet;
import android.util.Log;
-import android.util.Slog;
import com.android.internal.R;
import com.android.server.LocalServices;
@@ -58,14 +57,13 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Objects;
/**
* NotificationManagerService helper for functionality related to zen mode.
*/
-public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
- private static final String TAG = "ZenModeHelper";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+public class ZenModeHelper {
+ static final String TAG = "ZenModeHelper";
+ static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final Context mContext;
private final H mHandler;
@@ -73,38 +71,46 @@
private final AppOpsManager mAppOps;
private final ZenModeConfig mDefaultConfig;
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+ private final ZenModeFiltering mFiltering;
+ private final RingerModeDelegate mRingerModeDelegate = new RingerModeDelegate();
+ private final ZenModeConditions mConditions;
- private ComponentName mDefaultPhoneApp;
private int mZenMode;
private ZenModeConfig mConfig;
private AudioManagerInternal mAudioManager;
private int mPreviousRingerMode = -1;
private boolean mEffectsSuppressed;
- public ZenModeHelper(Context context, Looper looper) {
+ public ZenModeHelper(Context context, Looper looper, ConditionProviders conditionProviders) {
mContext = context;
mHandler = new H(looper);
mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mDefaultConfig = readDefaultConfig(context.getResources());
+ appendDefaultScheduleRules(mDefaultConfig);
mConfig = mDefaultConfig;
mSettingsObserver = new SettingsObserver(mHandler);
mSettingsObserver.observe();
+ mFiltering = new ZenModeFiltering(mContext);
+ mConditions = new ZenModeConditions(this, conditionProviders);
}
- public static ZenModeConfig readDefaultConfig(Resources resources) {
- XmlResourceParser parser = null;
- try {
- parser = resources.getXml(R.xml.default_zen_mode_config);
- while (parser.next() != XmlPullParser.END_DOCUMENT) {
- final ZenModeConfig config = ZenModeConfig.readXml(parser);
- if (config != null) return config;
- }
- } catch (Exception e) {
- Slog.w(TAG, "Error reading default zen mode config from resource", e);
- } finally {
- IoUtils.closeQuietly(parser);
- }
- return new ZenModeConfig();
+ @Override
+ public String toString() {
+ return TAG;
+ }
+
+ public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
+ ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
+ return ZenModeFiltering.matchesCallFilter(mZenMode, mConfig, userHandle, extras, validator,
+ contactsTimeoutMs, timeoutAffinity);
+ }
+
+ public boolean isCall(NotificationRecord record) {
+ return mFiltering.isCall(record);
+ }
+
+ public boolean shouldIntercept(NotificationRecord record) {
+ return mFiltering.shouldIntercept(mZenMode, mConfig, record);
}
public void addCallback(Callback callback) {
@@ -115,48 +121,32 @@
mCallbacks.remove(callback);
}
+ public void initZenMode() {
+ if (DEBUG) Log.d(TAG, "initZenMode");
+ evaluateZenMode("init", true /*setRingerMode*/);
+ }
+
public void onSystemReady() {
+ if (DEBUG) Log.d(TAG, "onSystemReady");
mAudioManager = LocalServices.getService(AudioManagerInternal.class);
if (mAudioManager != null) {
- mAudioManager.setRingerModeDelegate(this);
+ mAudioManager.setRingerModeDelegate(mRingerModeDelegate);
}
}
+ public void requestZenModeConditions(IConditionListener callback, int relevance) {
+ mConditions.requestConditions(callback, relevance);
+ }
+
public int getZenModeListenerInterruptionFilter() {
- switch (mZenMode) {
- case Global.ZEN_MODE_OFF:
- return NotificationListenerService.INTERRUPTION_FILTER_ALL;
- case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
- return NotificationListenerService.INTERRUPTION_FILTER_PRIORITY;
- case Global.ZEN_MODE_ALARMS:
- return NotificationListenerService.INTERRUPTION_FILTER_ALARMS;
- case Global.ZEN_MODE_NO_INTERRUPTIONS:
- return NotificationListenerService.INTERRUPTION_FILTER_NONE;
- default:
- return 0;
- }
- }
-
- private static int zenModeFromListenerInterruptionFilter(int listenerInterruptionFilter,
- int defValue) {
- switch (listenerInterruptionFilter) {
- case NotificationListenerService.INTERRUPTION_FILTER_ALL:
- return Global.ZEN_MODE_OFF;
- case NotificationListenerService.INTERRUPTION_FILTER_PRIORITY:
- return Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- case NotificationListenerService.INTERRUPTION_FILTER_ALARMS:
- return Global.ZEN_MODE_ALARMS;
- case NotificationListenerService.INTERRUPTION_FILTER_NONE:
- return Global.ZEN_MODE_NO_INTERRUPTIONS;
- default:
- return defValue;
- }
+ return getZenModeListenerInterruptionFilter(mZenMode);
}
public void requestFromListener(ComponentName name, int interruptionFilter) {
final int newZen = zenModeFromListenerInterruptionFilter(interruptionFilter, -1);
if (newZen != -1) {
- setZenMode(newZen, "listener:" + (name != null ? name.flattenToShortString() : null));
+ setManualZenMode(newZen, null,
+ "listener:" + (name != null ? name.flattenToShortString() : null));
}
}
@@ -166,100 +156,144 @@
applyRestrictions();
}
- public boolean shouldIntercept(NotificationRecord record) {
- if (isSystem(record)) {
- return false;
- }
- switch (mZenMode) {
- case Global.ZEN_MODE_NO_INTERRUPTIONS:
- // #notevenalarms
- ZenLog.traceIntercepted(record, "none");
- return true;
- case Global.ZEN_MODE_ALARMS:
- if (isAlarm(record)) {
- // Alarms only
- return false;
- }
- ZenLog.traceIntercepted(record, "alarmsOnly");
- return true;
- case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
- if (isAlarm(record)) {
- // Alarms are always priority
- return false;
- }
- // allow user-prioritized packages through in priority mode
- if (record.getPackagePriority() == Notification.PRIORITY_MAX) {
- ZenLog.traceNotIntercepted(record, "priorityApp");
- return false;
- }
- if (isCall(record)) {
- if (!mConfig.allowCalls) {
- ZenLog.traceIntercepted(record, "!allowCalls");
- return true;
- }
- return shouldInterceptAudience(record);
- }
- if (isMessage(record)) {
- if (!mConfig.allowMessages) {
- ZenLog.traceIntercepted(record, "!allowMessages");
- return true;
- }
- return shouldInterceptAudience(record);
- }
- if (isEvent(record)) {
- if (!mConfig.allowEvents) {
- ZenLog.traceIntercepted(record, "!allowEvents");
- return true;
- }
- return false;
- }
- if (isReminder(record)) {
- if (!mConfig.allowReminders) {
- ZenLog.traceIntercepted(record, "!allowReminders");
- return true;
- }
- return false;
- }
- ZenLog.traceIntercepted(record, "!priority");
- return true;
- default:
- return false;
- }
- }
-
- private boolean shouldInterceptAudience(NotificationRecord record) {
- if (!audienceMatches(record.getContactAffinity())) {
- ZenLog.traceIntercepted(record, "!audienceMatches");
- return true;
- }
- return false;
- }
-
public int getZenMode() {
return mZenMode;
}
- public void setZenMode(int zenMode, String reason) {
- setZenMode(zenMode, reason, true);
+ public void setManualZenMode(int zenMode, Uri conditionId, String reason) {
+ setManualZenMode(zenMode, conditionId, reason, true /*setRingerMode*/);
}
- private void setZenMode(int zenMode, String reason, boolean setRingerMode) {
- ZenLog.traceSetZenMode(zenMode, reason);
- if (mZenMode == zenMode) return;
- ZenLog.traceUpdateZenMode(mZenMode, zenMode);
- mZenMode = zenMode;
- Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, mZenMode);
+ private void setManualZenMode(int zenMode, Uri conditionId, String reason,
+ boolean setRingerMode) {
+ if (mConfig == null) return;
+ if (!Global.isValidZenMode(zenMode)) return;
+ if (DEBUG) Log.d(TAG, "setManualZenMode " + Global.zenModeToString(zenMode)
+ + " conditionId=" + conditionId + " reason=" + reason
+ + " setRingerMode=" + setRingerMode);
+ final ZenModeConfig newConfig = mConfig.copy();
+ if (zenMode == Global.ZEN_MODE_OFF) {
+ newConfig.manualRule = null;
+ for (ZenRule automaticRule : newConfig.automaticRules.values()) {
+ if (automaticRule.isTrueOrUnknown()) {
+ automaticRule.snoozing = true;
+ }
+ }
+ } else {
+ final ZenRule newRule = new ZenRule();
+ newRule.enabled = true;
+ newRule.zenMode = zenMode;
+ newRule.conditionId = conditionId;
+ newConfig.manualRule = newRule;
+ }
+ setConfig(newConfig, reason, setRingerMode);
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.print("mZenMode=");
+ pw.println(Global.zenModeToString(mZenMode));
+ dump(pw, prefix, "mConfig", mConfig);
+ dump(pw, prefix, "mDefaultConfig", mDefaultConfig);
+ pw.print(prefix); pw.print("mPreviousRingerMode="); pw.println(mPreviousRingerMode);
+ pw.print(prefix); pw.print("DefaultPhoneApp="); pw.println(mFiltering.getDefaultPhoneApp());
+ pw.print(prefix); pw.print("mEffectsSuppressed="); pw.println(mEffectsSuppressed);
+ mConditions.dump(pw, prefix);
+ }
+
+ private static void dump(PrintWriter pw, String prefix, String var, ZenModeConfig config) {
+ pw.print(prefix); pw.print(var); pw.print('=');
+ if (config == null) {
+ pw.println(config);
+ return;
+ }
+ pw.printf("allow(calls=%s,events=%s,from=%s,messages=%s,reminders=%s)\n",
+ config.allowCalls, config.allowEvents, config.allowFrom, config.allowMessages,
+ config.allowReminders);
+ pw.print(prefix); pw.print(" manualRule="); pw.println(config.manualRule);
+ if (config.automaticRules.isEmpty()) return;
+ final int N = config.automaticRules.size();
+ for (int i = 0; i < N; i++) {
+ pw.print(prefix); pw.print(i == 0 ? " automaticRules=" : " ");
+ pw.println(config.automaticRules.valueAt(i));
+ }
+ }
+
+ public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException {
+ final ZenModeConfig config = ZenModeConfig.readXml(parser, mConfigMigration);
+ if (config != null) {
+ if (DEBUG) Log.d(TAG, "readXml");
+ setConfig(config, "readXml");
+ }
+ }
+
+ public void writeXml(XmlSerializer out) throws IOException {
+ mConfig.writeXml(out);
+ }
+
+ public ZenModeConfig getConfig() {
+ return mConfig;
+ }
+
+ public boolean setConfig(ZenModeConfig config, String reason) {
+ return setConfig(config, reason, true /*setRingerMode*/);
+ }
+
+ private boolean setConfig(ZenModeConfig config, String reason, boolean setRingerMode) {
+ if (config == null || !config.isValid()) {
+ Log.w(TAG, "Invalid config in setConfig; " + config);
+ return false;
+ }
+ mConditions.evaluateConfig(config); // may modify config
+ if (config.equals(mConfig)) return true;
+ if (DEBUG) Log.d(TAG, "setConfig reason=" + reason);
+ ZenLog.traceConfig(mConfig, config);
+ mConfig = config;
+ dispatchOnConfigChanged();
+ final String val = Integer.toString(mConfig.hashCode());
+ Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
+ if (!evaluateZenMode(reason, setRingerMode)) {
+ applyRestrictions(); // evaluateZenMode will also apply restrictions if changed
+ }
+ return true;
+ }
+
+ private int getZenModeSetting() {
+ return Global.getInt(mContext.getContentResolver(), Global.ZEN_MODE, Global.ZEN_MODE_OFF);
+ }
+
+ private void setZenModeSetting(int zen) {
+ Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zen);
+ }
+
+ private boolean evaluateZenMode(String reason, boolean setRingerMode) {
+ if (DEBUG) Log.d(TAG, "evaluateZenMode");
+ final ArraySet<ZenRule> automaticRules = new ArraySet<ZenRule>();
+ final int zen = computeZenMode(automaticRules);
+ if (zen == mZenMode) return false;
+ ZenLog.traceSetZenMode(zen, reason);
+ mZenMode = zen;
+ setZenModeSetting(mZenMode);
if (setRingerMode) {
applyZenToRingerMode();
}
applyRestrictions();
mHandler.postDispatchOnZenModeChanged();
+ return true;
}
- public void readZenModeFromSetting() {
- final int newMode = Global.getInt(mContext.getContentResolver(),
- Global.ZEN_MODE, Global.ZEN_MODE_OFF);
- setZenMode(newMode, "setting");
+ private int computeZenMode(ArraySet<ZenRule> automaticRulesOut) {
+ if (mConfig == null) return Global.ZEN_MODE_OFF;
+ if (mConfig.manualRule != null) return mConfig.manualRule.zenMode;
+ int zen = Global.ZEN_MODE_OFF;
+ for (ZenRule automaticRule : mConfig.automaticRules.values()) {
+ if (automaticRule.enabled && !automaticRule.snoozing
+ && automaticRule.isTrueOrUnknown()) {
+ if (zenSeverity(automaticRule.zenMode) > zenSeverity(zen)) {
+ zen = automaticRule.zenMode;
+ }
+ }
+ }
+ return zen;
}
private void applyRestrictions() {
@@ -288,43 +322,6 @@
exceptionPackages);
}
- public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.print("mZenMode=");
- pw.println(Global.zenModeToString(mZenMode));
- pw.print(prefix); pw.print("mConfig="); pw.println(mConfig);
- pw.print(prefix); pw.print("mDefaultConfig="); pw.println(mDefaultConfig);
- pw.print(prefix); pw.print("mPreviousRingerMode="); pw.println(mPreviousRingerMode);
- pw.print(prefix); pw.print("mDefaultPhoneApp="); pw.println(mDefaultPhoneApp);
- pw.print(prefix); pw.print("mEffectsSuppressed="); pw.println(mEffectsSuppressed);
- }
-
- public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException {
- final ZenModeConfig config = ZenModeConfig.readXml(parser);
- if (config != null) {
- setConfig(config);
- }
- }
-
- public void writeXml(XmlSerializer out) throws IOException {
- mConfig.writeXml(out);
- }
-
- public ZenModeConfig getConfig() {
- return mConfig;
- }
-
- public boolean setConfig(ZenModeConfig config) {
- if (config == null || !config.isValid()) return false;
- if (config.equals(mConfig)) return true;
- ZenLog.traceConfig(mConfig, config);
- mConfig = config;
- dispatchOnConfigChanged();
- final String val = Integer.toString(mConfig.hashCode());
- Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
- applyRestrictions();
- return true;
- }
-
private void applyZenToRingerMode() {
if (mAudioManager == null) return;
// force the ringer mode into compliance
@@ -352,81 +349,6 @@
}
}
- @Override // RingerModeDelegate
- public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller,
- int ringerModeExternal, VolumePolicy policy) {
- final boolean isChange = ringerModeOld != ringerModeNew;
-
- int ringerModeExternalOut = ringerModeNew;
-
- int newZen = -1;
- switch (ringerModeNew) {
- case AudioManager.RINGER_MODE_SILENT:
- if (isChange && policy.doNotDisturbWhenSilent) {
- if (mZenMode != Global.ZEN_MODE_NO_INTERRUPTIONS
- && mZenMode != Global.ZEN_MODE_ALARMS) {
- newZen = Global.ZEN_MODE_NO_INTERRUPTIONS;
- }
- }
- break;
- case AudioManager.RINGER_MODE_VIBRATE:
- case AudioManager.RINGER_MODE_NORMAL:
- if (isChange && ringerModeOld == AudioManager.RINGER_MODE_SILENT
- && (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
- || mZenMode == Global.ZEN_MODE_ALARMS)) {
- newZen = Global.ZEN_MODE_OFF;
- } else if (mZenMode != Global.ZEN_MODE_OFF) {
- ringerModeExternalOut = AudioManager.RINGER_MODE_SILENT;
- }
- break;
- }
- if (newZen != -1) {
- setZenMode(newZen, "ringerModeInternal", false /*setRingerMode*/);
- }
-
- if (isChange || newZen != -1 || ringerModeExternal != ringerModeExternalOut) {
- ZenLog.traceSetRingerModeInternal(ringerModeOld, ringerModeNew, caller,
- ringerModeExternal, ringerModeExternalOut);
- }
- return ringerModeExternalOut;
- }
-
- @Override // RingerModeDelegate
- public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
- int ringerModeInternal, VolumePolicy policy) {
- int ringerModeInternalOut = ringerModeNew;
- final boolean isChange = ringerModeOld != ringerModeNew;
- final boolean isVibrate = ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
-
- int newZen = -1;
- switch (ringerModeNew) {
- case AudioManager.RINGER_MODE_SILENT:
- if (isChange) {
- if (mZenMode == Global.ZEN_MODE_OFF) {
- newZen = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- }
- ringerModeInternalOut = isVibrate ? AudioManager.RINGER_MODE_VIBRATE
- : AudioManager.RINGER_MODE_NORMAL;
- } else {
- ringerModeInternalOut = ringerModeInternal;
- }
- break;
- case AudioManager.RINGER_MODE_VIBRATE:
- case AudioManager.RINGER_MODE_NORMAL:
- if (mZenMode != Global.ZEN_MODE_OFF) {
- newZen = Global.ZEN_MODE_OFF;
- }
- break;
- }
- if (newZen != -1) {
- setZenMode(newZen, "ringerModeExternal", false /*setRingerMode*/);
- }
-
- ZenLog.traceSetRingerModeExternal(ringerModeOld, ringerModeNew, caller, ringerModeInternal,
- ringerModeInternalOut);
- return ringerModeInternalOut;
- }
-
private void dispatchOnConfigChanged() {
for (Callback callback : mCallbacks) {
callback.onConfigChanged();
@@ -439,94 +361,210 @@
}
}
- private static boolean isSystem(NotificationRecord record) {
- return record.isCategory(Notification.CATEGORY_SYSTEM);
- }
-
- private static boolean isAlarm(NotificationRecord record) {
- return record.isCategory(Notification.CATEGORY_ALARM)
- || record.isAudioStream(AudioManager.STREAM_ALARM)
- || record.isAudioAttributesUsage(AudioAttributes.USAGE_ALARM);
- }
-
- private static boolean isEvent(NotificationRecord record) {
- return record.isCategory(Notification.CATEGORY_EVENT);
- }
-
- private static boolean isReminder(NotificationRecord record) {
- return record.isCategory(Notification.CATEGORY_REMINDER);
- }
-
- public boolean isCall(NotificationRecord record) {
- return record != null && (isDefaultPhoneApp(record.sbn.getPackageName())
- || record.isCategory(Notification.CATEGORY_CALL));
- }
-
- private boolean isDefaultPhoneApp(String pkg) {
- if (mDefaultPhoneApp == null) {
- final TelecomManager telecomm =
- (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
- mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null;
- if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp);
- }
- return pkg != null && mDefaultPhoneApp != null
- && pkg.equals(mDefaultPhoneApp.getPackageName());
- }
-
- private boolean isDefaultMessagingApp(NotificationRecord record) {
- final int userId = record.getUserId();
- if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false;
- final String defaultApp = Secure.getStringForUser(mContext.getContentResolver(),
- Secure.SMS_DEFAULT_APPLICATION, userId);
- return Objects.equals(defaultApp, record.sbn.getPackageName());
- }
-
- private boolean isMessage(NotificationRecord record) {
- return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record);
- }
-
- /**
- * @param extras extras of the notification with EXTRA_PEOPLE populated
- * @param contactsTimeoutMs timeout in milliseconds to wait for contacts response
- * @param timeoutAffinity affinity to return when the timeout specified via
- * <code>contactsTimeoutMs</code> is hit
- */
- public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
- ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
- final int zen = mZenMode;
- if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through
- if (zen == Global.ZEN_MODE_ALARMS) return false; // not an alarm
- if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
- if (!mConfig.allowCalls) return false; // no calls get through
- if (validator != null) {
- final float contactAffinity = validator.getContactAffinity(userHandle, extras,
- contactsTimeoutMs, timeoutAffinity);
- return audienceMatches(contactAffinity);
- }
- }
- return true;
- }
-
- @Override
- public String toString() {
- return TAG;
- }
-
- private boolean audienceMatches(float contactAffinity) {
- switch (mConfig.allowFrom) {
- case ZenModeConfig.SOURCE_ANYONE:
- return true;
- case ZenModeConfig.SOURCE_CONTACT:
- return contactAffinity >= ValidateNotificationPeople.VALID_CONTACT;
- case ZenModeConfig.SOURCE_STAR:
- return contactAffinity >= ValidateNotificationPeople.STARRED_CONTACT;
+ private static int getZenModeListenerInterruptionFilter(int zen) {
+ switch (zen) {
+ case Global.ZEN_MODE_OFF:
+ return NotificationListenerService.INTERRUPTION_FILTER_ALL;
+ case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+ return NotificationListenerService.INTERRUPTION_FILTER_PRIORITY;
+ case Global.ZEN_MODE_ALARMS:
+ return NotificationListenerService.INTERRUPTION_FILTER_ALARMS;
+ case Global.ZEN_MODE_NO_INTERRUPTIONS:
+ return NotificationListenerService.INTERRUPTION_FILTER_NONE;
default:
- Slog.w(TAG, "Encountered unknown source: " + mConfig.allowFrom);
- return true;
+ return 0;
}
}
- private class SettingsObserver extends ContentObserver {
+ private static int zenModeFromListenerInterruptionFilter(int listenerInterruptionFilter,
+ int defValue) {
+ switch (listenerInterruptionFilter) {
+ case NotificationListenerService.INTERRUPTION_FILTER_ALL:
+ return Global.ZEN_MODE_OFF;
+ case NotificationListenerService.INTERRUPTION_FILTER_PRIORITY:
+ return Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ case NotificationListenerService.INTERRUPTION_FILTER_ALARMS:
+ return Global.ZEN_MODE_ALARMS;
+ case NotificationListenerService.INTERRUPTION_FILTER_NONE:
+ return Global.ZEN_MODE_NO_INTERRUPTIONS;
+ default:
+ return defValue;
+ }
+ }
+
+ private ZenModeConfig readDefaultConfig(Resources resources) {
+ XmlResourceParser parser = null;
+ try {
+ parser = resources.getXml(R.xml.default_zen_mode_config);
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ final ZenModeConfig config = ZenModeConfig.readXml(parser, mConfigMigration);
+ if (config != null) return config;
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Error reading default zen mode config from resource", e);
+ } finally {
+ IoUtils.closeQuietly(parser);
+ }
+ return new ZenModeConfig();
+ }
+
+ private void appendDefaultScheduleRules(ZenModeConfig config) {
+ if (config == null) return;
+
+ final ScheduleInfo weeknights = new ScheduleInfo();
+ weeknights.days = ZenModeConfig.WEEKNIGHT_DAYS;
+ weeknights.startHour = 22;
+ weeknights.endHour = 7;
+ final ZenRule rule1 = new ZenRule();
+ rule1.enabled = false;
+ rule1.name = mContext.getResources()
+ .getString(R.string.zen_mode_default_weeknights_name);
+ rule1.conditionId = ZenModeConfig.toScheduleConditionId(weeknights);
+ rule1.zenMode = Global.ZEN_MODE_ALARMS;
+ config.automaticRules.put(config.newRuleId(), rule1);
+
+ final ScheduleInfo weekends = new ScheduleInfo();
+ weekends.days = ZenModeConfig.WEEKEND_DAYS;
+ weekends.startHour = 23;
+ weekends.startMinute = 30;
+ weekends.endHour = 10;
+ final ZenRule rule2 = new ZenRule();
+ rule2.enabled = false;
+ rule2.name = mContext.getResources()
+ .getString(R.string.zen_mode_default_weekends_name);
+ rule2.conditionId = ZenModeConfig.toScheduleConditionId(weekends);
+ rule2.zenMode = Global.ZEN_MODE_ALARMS;
+ config.automaticRules.put(config.newRuleId(), rule2);
+ }
+
+ private static int zenSeverity(int zen) {
+ switch (zen) {
+ case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: return 1;
+ case Global.ZEN_MODE_ALARMS: return 2;
+ case Global.ZEN_MODE_NO_INTERRUPTIONS: return 3;
+ default: return 0;
+ }
+ }
+
+ private final ZenModeConfig.Migration mConfigMigration = new ZenModeConfig.Migration() {
+ @Override
+ public ZenModeConfig migrate(ZenModeConfig.XmlV1 v1) {
+ if (v1 == null) return null;
+ final ZenModeConfig rt = new ZenModeConfig();
+ rt.allowCalls = v1.allowCalls;
+ rt.allowEvents = v1.allowEvents;
+ rt.allowFrom = v1.allowFrom;
+ rt.allowMessages = v1.allowMessages;
+ rt.allowReminders = v1.allowReminders;
+ // don't migrate current exit condition
+ final int[] days = ZenModeConfig.XmlV1.tryParseDays(v1.sleepMode);
+ if (days != null && days.length > 0) {
+ Log.i(TAG, "Migrating existing V1 downtime to single schedule");
+ final ScheduleInfo schedule = new ScheduleInfo();
+ schedule.days = days;
+ schedule.startHour = v1.sleepStartHour;
+ schedule.startMinute = v1.sleepStartMinute;
+ schedule.endHour = v1.sleepEndHour;
+ schedule.endMinute = v1.sleepEndMinute;
+ final ZenRule rule = new ZenRule();
+ rule.enabled = true;
+ rule.name = mContext.getResources()
+ .getString(R.string.zen_mode_downtime_feature_name);
+ rule.conditionId = ZenModeConfig.toScheduleConditionId(schedule);
+ rule.zenMode = v1.sleepNone ? Global.ZEN_MODE_NO_INTERRUPTIONS
+ : Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ rt.automaticRules.put(rt.newRuleId(), rule);
+ } else {
+ Log.i(TAG, "No existing V1 downtime found, generating default schedules");
+ appendDefaultScheduleRules(rt);
+ }
+ return rt;
+ }
+ };
+
+ private final class RingerModeDelegate implements AudioManagerInternal.RingerModeDelegate {
+ @Override
+ public String toString() {
+ return TAG;
+ }
+
+ @Override
+ public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller,
+ int ringerModeExternal, VolumePolicy policy) {
+ final boolean isChange = ringerModeOld != ringerModeNew;
+
+ int ringerModeExternalOut = ringerModeNew;
+
+ int newZen = -1;
+ switch (ringerModeNew) {
+ case AudioManager.RINGER_MODE_SILENT:
+ if (isChange && policy.doNotDisturbWhenSilent) {
+ if (mZenMode != Global.ZEN_MODE_NO_INTERRUPTIONS
+ && mZenMode != Global.ZEN_MODE_ALARMS) {
+ newZen = Global.ZEN_MODE_NO_INTERRUPTIONS;
+ }
+ }
+ break;
+ case AudioManager.RINGER_MODE_VIBRATE:
+ case AudioManager.RINGER_MODE_NORMAL:
+ if (isChange && ringerModeOld == AudioManager.RINGER_MODE_SILENT
+ && (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
+ || mZenMode == Global.ZEN_MODE_ALARMS)) {
+ newZen = Global.ZEN_MODE_OFF;
+ } else if (mZenMode != Global.ZEN_MODE_OFF) {
+ ringerModeExternalOut = AudioManager.RINGER_MODE_SILENT;
+ }
+ break;
+ }
+ if (newZen != -1) {
+ setManualZenMode(newZen, null, "ringerModeInternal", false /*setRingerMode*/);
+ }
+
+ if (isChange || newZen != -1 || ringerModeExternal != ringerModeExternalOut) {
+ ZenLog.traceSetRingerModeInternal(ringerModeOld, ringerModeNew, caller,
+ ringerModeExternal, ringerModeExternalOut);
+ }
+ return ringerModeExternalOut;
+ }
+
+ @Override
+ public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
+ int ringerModeInternal, VolumePolicy policy) {
+ int ringerModeInternalOut = ringerModeNew;
+ final boolean isChange = ringerModeOld != ringerModeNew;
+ final boolean isVibrate = ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
+
+ int newZen = -1;
+ switch (ringerModeNew) {
+ case AudioManager.RINGER_MODE_SILENT:
+ if (isChange) {
+ if (mZenMode == Global.ZEN_MODE_OFF) {
+ newZen = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ }
+ ringerModeInternalOut = isVibrate ? AudioManager.RINGER_MODE_VIBRATE
+ : AudioManager.RINGER_MODE_NORMAL;
+ } else {
+ ringerModeInternalOut = ringerModeInternal;
+ }
+ break;
+ case AudioManager.RINGER_MODE_VIBRATE:
+ case AudioManager.RINGER_MODE_NORMAL:
+ if (mZenMode != Global.ZEN_MODE_OFF) {
+ newZen = Global.ZEN_MODE_OFF;
+ }
+ break;
+ }
+ if (newZen != -1) {
+ setManualZenMode(newZen, null, "ringerModeExternal", false /*setRingerMode*/);
+ }
+
+ ZenLog.traceSetRingerModeExternal(ringerModeOld, ringerModeNew, caller,
+ ringerModeInternal, ringerModeInternalOut);
+ return ringerModeInternalOut;
+ }
+ }
+
+ private final class SettingsObserver extends ContentObserver {
private final Uri ZEN_MODE = Global.getUriFor(Global.ZEN_MODE);
public SettingsObserver(Handler handler) {
@@ -546,12 +584,15 @@
public void update(Uri uri) {
if (ZEN_MODE.equals(uri)) {
- readZenModeFromSetting();
+ if (mZenMode != getZenModeSetting()) {
+ if (DEBUG) Log.d(TAG, "Fixing zen mode setting");
+ setZenModeSetting(mZenMode);
+ }
}
}
}
- private class H extends Handler {
+ private final class H extends Handler {
private static final int MSG_DISPATCH = 1;
private H(Looper looper) {
@@ -577,4 +618,5 @@
void onConfigChanged() {}
void onZenModeChanged() {}
}
+
}