summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/INotificationManager.aidl4
-rw-r--r--core/java/android/provider/Settings.java7
-rw-r--r--core/java/android/service/notification/ZenModeConfig.aidl20
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java234
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/res/res/xml/default_zen_mode_config.xml24
-rw-r--r--packages/SystemUI/res/values/strings.xml3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeView.java4
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java227
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java312
11 files changed, 715 insertions, 123 deletions
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index bb6eeda2dd3a..8681f5c85b12 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -23,6 +23,7 @@ import android.app.Notification;
import android.content.ComponentName;
import android.content.Intent;
import android.service.notification.INotificationListener;
+import android.service.notification.ZenModeConfig;
/** {@hide} */
interface INotificationManager
@@ -49,4 +50,7 @@ interface INotificationManager
StatusBarNotification[] getActiveNotificationsFromListener(in INotificationListener token, in String[] keys);
String[] getActiveNotificationKeysFromListener(in INotificationListener token);
+
+ ZenModeConfig getZenModeConfig();
+ boolean setZenModeConfig(in ZenModeConfig config);
} \ No newline at end of file
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1e202ca722d4..2ce6210cb6b4 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6120,6 +6120,13 @@ public final class Settings {
}
/**
+ * Opaque value, changes when persisted zen mode configuration changes.
+ *
+ * @hide
+ */
+ public static final String ZEN_MODE_CONFIG_ETAG = "zen_mode_config_etag";
+
+ /**
* Defines global heads up toggle. One of HEADS_UP_OFF, HEADS_UP_ON.
*
* @hide
diff --git a/core/java/android/service/notification/ZenModeConfig.aidl b/core/java/android/service/notification/ZenModeConfig.aidl
new file mode 100644
index 000000000000..c73b75ef821f
--- /dev/null
+++ b/core/java/android/service/notification/ZenModeConfig.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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 android.service.notification;
+
+parcelable ZenModeConfig;
+
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
new file mode 100644
index 000000000000..925ddcfc79df
--- /dev/null
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -0,0 +1,234 @@
+/**
+ * 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 android.service.notification;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * Persisted configuration for zen mode.
+ *
+ * @hide
+ */
+public class ZenModeConfig implements Parcelable {
+
+ public static final String SLEEP_MODE_NIGHTS = "nights";
+ public static final String SLEEP_MODE_WEEKNIGHTS = "weeknights";
+
+ private static final int XML_VERSION = 1;
+ private static final String ZEN_TAG = "zen";
+ private static final String ZEN_ATT_VERSION = "version";
+ private static final String ALLOW_TAG = "allow";
+ private static final String ALLOW_ATT_CALLS = "calls";
+ private static final String ALLOW_ATT_MESSAGES = "messages";
+ private static final String SLEEP_TAG = "sleep";
+ private static final String SLEEP_ATT_MODE = "mode";
+
+ private static final String SLEEP_ATT_START_HR = "startHour";
+ private static final String SLEEP_ATT_START_MIN = "startMin";
+ private static final String SLEEP_ATT_END_HR = "endHour";
+ private static final String SLEEP_ATT_END_MIN = "endMin";
+
+ public boolean allowCalls;
+ public boolean allowMessages;
+
+ public String sleepMode;
+ public int sleepStartHour;
+ public int sleepStartMinute;
+ public int sleepEndHour;
+ public int sleepEndMinute;
+
+ public ZenModeConfig() { }
+
+ public ZenModeConfig(Parcel source) {
+ allowCalls = source.readInt() == 1;
+ allowMessages = source.readInt() == 1;
+ if (source.readInt() == 1) {
+ sleepMode = source.readString();
+ }
+ sleepStartHour = source.readInt();
+ sleepStartMinute = source.readInt();
+ sleepEndHour = source.readInt();
+ sleepEndMinute = source.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(allowCalls ? 1 : 0);
+ dest.writeInt(allowMessages ? 1 : 0);
+ if (sleepMode != null) {
+ dest.writeInt(1);
+ dest.writeString(sleepMode);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(sleepStartHour);
+ dest.writeInt(sleepStartMinute);
+ dest.writeInt(sleepEndHour);
+ dest.writeInt(sleepEndMinute);
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
+ .append("allowCalls=").append(allowCalls)
+ .append(",allowMessages=").append(allowMessages)
+ .append(",sleepMode=").append(sleepMode)
+ .append(",sleepStart=").append(sleepStartHour).append('.').append(sleepStartMinute)
+ .append(",sleepEnd=").append(sleepEndHour).append('.').append(sleepEndMinute)
+ .append(']').toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ZenModeConfig)) return false;
+ if (o == this) return true;
+ final ZenModeConfig other = (ZenModeConfig) o;
+ return other.allowCalls == allowCalls
+ && other.allowMessages == allowMessages
+ && Objects.equals(other.sleepMode, sleepMode)
+ && other.sleepStartHour == sleepStartHour
+ && other.sleepStartMinute == sleepStartMinute
+ && other.sleepEndHour == sleepEndHour
+ && other.sleepEndMinute == sleepEndMinute;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(allowCalls, allowMessages, sleepMode, sleepStartHour,
+ sleepStartMinute, sleepEndHour, sleepEndMinute);
+ }
+
+ public boolean isValid() {
+ return isValidHour(sleepStartHour) && isValidMinute(sleepStartMinute)
+ && isValidHour(sleepEndHour) && isValidMinute(sleepEndMinute)
+ && (sleepMode == null || sleepMode.equals(SLEEP_MODE_NIGHTS)
+ || sleepMode.equals(SLEEP_MODE_WEEKNIGHTS));
+ }
+
+ public static ZenModeConfig readXml(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int type = parser.getEventType();
+ if (type != XmlPullParser.START_TAG) return null;
+ String tag = parser.getName();
+ if (!ZEN_TAG.equals(tag)) return null;
+ final ZenModeConfig rt = new ZenModeConfig();
+ final int version = Integer.parseInt(parser.getAttributeValue(null, ZEN_ATT_VERSION));
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ tag = parser.getName();
+ if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) return rt;
+ if (type == XmlPullParser.START_TAG) {
+ if (ALLOW_TAG.equals(tag)) {
+ rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false);
+ rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false);
+ } else if (SLEEP_TAG.equals(tag)) {
+ final String mode = parser.getAttributeValue(null, SLEEP_ATT_MODE);
+ rt.sleepMode = (SLEEP_MODE_NIGHTS.equals(mode)
+ || SLEEP_MODE_WEEKNIGHTS.equals(mode)) ? mode : null;
+ final int startHour = safeInt(parser, SLEEP_ATT_START_HR, 0);
+ final int startMinute = safeInt(parser, SLEEP_ATT_START_MIN, 0);
+ final int endHour = safeInt(parser, SLEEP_ATT_END_HR, 0);
+ final int endMinute = safeInt(parser, SLEEP_ATT_END_MIN, 0);
+ rt.sleepStartHour = isValidHour(startHour) ? startHour : 0;
+ rt.sleepStartMinute = isValidMinute(startMinute) ? startMinute : 0;
+ rt.sleepEndHour = isValidHour(endHour) ? endHour : 0;
+ rt.sleepEndMinute = isValidMinute(endMinute) ? endMinute : 0;
+ }
+ }
+ }
+ return rt;
+ }
+
+ public void writeXml(XmlSerializer out) throws IOException {
+ out.startTag(null, ZEN_TAG);
+ out.attribute(null, ZEN_ATT_VERSION, Integer.toString(XML_VERSION));
+
+ out.startTag(null, ALLOW_TAG);
+ out.attribute(null, ALLOW_ATT_CALLS, Boolean.toString(allowCalls));
+ out.attribute(null, ALLOW_ATT_MESSAGES, Boolean.toString(allowMessages));
+ out.endTag(null, ALLOW_TAG);
+
+ out.startTag(null, SLEEP_TAG);
+ if (sleepMode != null) {
+ out.attribute(null, SLEEP_ATT_MODE, sleepMode);
+ }
+ out.attribute(null, SLEEP_ATT_START_HR, Integer.toString(sleepStartHour));
+ out.attribute(null, SLEEP_ATT_START_MIN, Integer.toString(sleepStartMinute));
+ out.attribute(null, SLEEP_ATT_END_HR, Integer.toString(sleepEndHour));
+ out.attribute(null, SLEEP_ATT_END_MIN, Integer.toString(sleepEndMinute));
+ out.endTag(null, SLEEP_TAG);
+
+ out.endTag(null, ZEN_TAG);
+ }
+
+ public static boolean isValidHour(int val) {
+ return val >= 0 && val < 24;
+ }
+
+ public static boolean isValidMinute(int val) {
+ return val >= 0 && val < 60;
+ }
+
+ private static boolean safeBoolean(XmlPullParser parser, String att, boolean defValue) {
+ final String val = parser.getAttributeValue(null, att);
+ if (TextUtils.isEmpty(val)) return defValue;
+ return Boolean.valueOf(val);
+ }
+
+ private static int safeInt(XmlPullParser parser, String att, int defValue) {
+ final String val = parser.getAttributeValue(null, att);
+ if (TextUtils.isEmpty(val)) return defValue;
+ return Integer.valueOf(val);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public ZenModeConfig copy() {
+ final Parcel parcel = Parcel.obtain();
+ try {
+ writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ return new ZenModeConfig(parcel);
+ } finally {
+ parcel.recycle();
+ }
+ }
+
+ public static final Parcelable.Creator<ZenModeConfig> CREATOR
+ = new Parcelable.Creator<ZenModeConfig>() {
+ @Override
+ public ZenModeConfig createFromParcel(Parcel source) {
+ return new ZenModeConfig(source);
+ }
+
+ @Override
+ public ZenModeConfig[] newArray(int size) {
+ return new ZenModeConfig[size];
+ }
+ };
+}
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 26efe36a5a7a..f91612829f4d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1230,6 +1230,7 @@
<java-symbol type="xml" name="sms_short_codes" />
<java-symbol type="xml" name="audio_assets" />
<java-symbol type="xml" name="global_keys" />
+ <java-symbol type="xml" name="default_zen_mode_config" />
<java-symbol type="raw" name="accessibility_gestures" />
<java-symbol type="raw" name="incognito_mode_start_page" />
diff --git a/core/res/res/xml/default_zen_mode_config.xml b/core/res/res/xml/default_zen_mode_config.xml
new file mode 100644
index 000000000000..1bdc1ec6b735
--- /dev/null
+++ b/core/res/res/xml/default_zen_mode_config.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<!-- Default configuration for zen mode. See android.service.notification.ZenModeConfig. -->
+<zen version="1">
+ <allow calls="false" messages="false" />
+ <sleep startHour="22" startMin="0" endHour="7" endMin="0" />
+</zen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 72d14fefce1f..f3c956ce70e5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -539,6 +539,9 @@
<!-- Zen mode: Summary notification content text. [CHAR LIMIT=NONE] -->
<string name="zen_mode_notification_text">Touch to show</string>
+ <!-- Zen mode: Short title. [CHAR LIMIT=40] -->
+ <string name="zen_mode_title">Do not disturb</string>
+
<!-- Text for overflow card on Keyguard when there is not enough space for all notifications on Keyguard. [CHAR LIMIT=12] -->
<plurals name="keyguard_more_overflow_text">
<item quantity="other">%d more</item>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
index 9b2504629720..e1ef83a9e754 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
@@ -608,7 +608,7 @@ class QuickSettingsModel implements BluetoothStateChangeCallback,
Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
mZenModeState.enabled = mode != Settings.Global.ZEN_MODE_OFF;
mZenModeState.zenMode = mode;
- mZenModeState.label = ZenModeView.MODE_LABEL;
+ mZenModeState.label = mContext.getString(R.string.zen_mode_title);
mZenModeState.iconId = R.drawable.stat_sys_zen_limited;
mZenModeCallback.refreshView(mZenModeTile, mZenModeState);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeView.java
index 49cf78b9d824..c1662bac3667 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeView.java
@@ -42,13 +42,13 @@ import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
+import com.android.systemui.R;
import com.android.systemui.statusbar.phone.ZenModeView.Adapter.ExitCondition;
public class ZenModeView extends RelativeLayout {
private static final String TAG = ZenModeView.class.getSimpleName();
private static final boolean DEBUG = false;
- public static final String MODE_LABEL = "Limited interruptions";
public static final int BACKGROUND = 0xff282828;
private static final Typeface CONDENSED =
@@ -91,7 +91,7 @@ public class ZenModeView extends RelativeLayout {
LayoutParams lp = null;
mModeText = new TextView(mContext);
- mModeText.setText(MODE_LABEL);
+ mModeText.setText(R.string.zen_mode_title);
mModeText.setId(android.R.id.title);
mModeText.setTextColor(GRAY);
mModeText.setTypeface(CONDENSED);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 5b597a3cf329..c8bdb4c3a2c9 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -52,6 +52,7 @@ import android.media.IRingtonePlayer;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
+import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -64,6 +65,7 @@ import android.provider.Settings;
import android.service.notification.INotificationListener;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
+import android.service.notification.ZenModeConfig;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -78,6 +80,7 @@ import android.widget.Toast;
import com.android.internal.R;
import com.android.internal.notification.NotificationScorer;
+import com.android.internal.util.FastXmlSerializer;
import com.android.server.EventLogTags;
import com.android.server.notification.NotificationUsageStats.SingleNotificationStats;
import com.android.server.statusbar.StatusBarManagerInternal;
@@ -87,11 +90,13 @@ import com.android.server.lights.LightsManager;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Array;
@@ -115,6 +120,7 @@ public class NotificationManagerService extends SystemService {
// message codes
static final int MESSAGE_TIMEOUT = 2;
+ static final int MESSAGE_SAVE_POLICY_FILE = 3;
static final int LONG_DELAY = 3500; // 3.5 seconds
static final int SHORT_DELAY = 2000; // 2 seconds
@@ -209,15 +215,6 @@ public class NotificationManagerService extends SystemService {
private final NotificationUsageStats mUsageStats = new NotificationUsageStats();
- private int mZenMode;
- // temporary, until we update apps to provide metadata
- private static final Set<String> CALL_PACKAGES = new HashSet<String>(Arrays.asList(
- "com.google.android.dialer",
- "com.android.phone"
- ));
- private static final Set<String> ALARM_PACKAGES = new HashSet<String>(Arrays.asList(
- "com.google.android.deskclock"
- ));
private static final String EXTRA_INTERCEPT = "android.intercept";
// Profiles of the current user.
@@ -421,53 +418,82 @@ public class NotificationManagerService extends SystemService {
Archive mArchive = new Archive();
- private void loadBlockDb() {
- synchronized(mBlockedPackages) {
- if (mPolicyFile == null) {
- File dir = new File("/data/system");
- mPolicyFile = new AtomicFile(new File(dir, "notification_policy.xml"));
+ private void loadPolicyFile() {
+ synchronized(mPolicyFile) {
+ mBlockedPackages.clear();
- mBlockedPackages.clear();
-
- FileInputStream infile = null;
- try {
- infile = mPolicyFile.openRead();
- final XmlPullParser parser = Xml.newPullParser();
- parser.setInput(infile, null);
-
- int type;
- String tag;
- int version = DB_VERSION;
- while ((type = parser.next()) != END_DOCUMENT) {
- tag = parser.getName();
- if (type == START_TAG) {
- if (TAG_BODY.equals(tag)) {
- version = Integer.parseInt(
- parser.getAttributeValue(null, ATTR_VERSION));
- } else if (TAG_BLOCKED_PKGS.equals(tag)) {
- while ((type = parser.next()) != END_DOCUMENT) {
- tag = parser.getName();
- if (TAG_PACKAGE.equals(tag)) {
- mBlockedPackages.add(
- parser.getAttributeValue(null, ATTR_NAME));
- } else if (TAG_BLOCKED_PKGS.equals(tag) && type == END_TAG) {
- break;
- }
+ FileInputStream infile = null;
+ try {
+ infile = mPolicyFile.openRead();
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(infile, null);
+
+ int type;
+ String tag;
+ int version = DB_VERSION;
+ while ((type = parser.next()) != END_DOCUMENT) {
+ tag = parser.getName();
+ if (type == START_TAG) {
+ if (TAG_BODY.equals(tag)) {
+ version = Integer.parseInt(
+ parser.getAttributeValue(null, ATTR_VERSION));
+ } else if (TAG_BLOCKED_PKGS.equals(tag)) {
+ while ((type = parser.next()) != END_DOCUMENT) {
+ tag = parser.getName();
+ if (TAG_PACKAGE.equals(tag)) {
+ mBlockedPackages.add(
+ parser.getAttributeValue(null, ATTR_NAME));
+ } else if (TAG_BLOCKED_PKGS.equals(tag) && type == END_TAG) {
+ break;
}
}
}
}
- } catch (FileNotFoundException e) {
- // No data yet
- } catch (IOException e) {
- Log.wtf(TAG, "Unable to read blocked notifications database", e);
- } catch (NumberFormatException e) {
- Log.wtf(TAG, "Unable to parse blocked notifications database", e);
- } catch (XmlPullParserException e) {
- Log.wtf(TAG, "Unable to parse blocked notifications database", e);
- } finally {
- IoUtils.closeQuietly(infile);
+ mZenModeHelper.readXml(parser);
}
+ } catch (FileNotFoundException e) {
+ // No data yet
+ } catch (IOException e) {
+ Log.wtf(TAG, "Unable to read notification policy", e);
+ } catch (NumberFormatException e) {
+ Log.wtf(TAG, "Unable to parse notification policy", e);
+ } catch (XmlPullParserException e) {
+ Log.wtf(TAG, "Unable to parse notification policy", e);
+ } finally {
+ IoUtils.closeQuietly(infile);
+ }
+ }
+ }
+
+ public void savePolicyFile() {
+ mHandler.removeMessages(MESSAGE_SAVE_POLICY_FILE);
+ mHandler.sendEmptyMessage(MESSAGE_SAVE_POLICY_FILE);
+ }
+
+ private void handleSavePolicyFile() {
+ Slog.d(TAG, "handleSavePolicyFile");
+ synchronized (mPolicyFile) {
+ final FileOutputStream stream;
+ try {
+ stream = mPolicyFile.startWrite();
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to save policy file", e);
+ return;
+ }
+
+ try {
+ final XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(stream, "utf-8");
+ out.startDocument(null, true);
+ out.startTag(null, TAG_BODY);
+ out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
+ mZenModeHelper.writeXml(out);
+ out.endTag(null, TAG_BODY);
+ out.endDocument();
+ mPolicyFile.finishWrite(stream);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to save policy file, restoring backup", e);
+ mPolicyFile.failWrite(stream);
}
}
}
@@ -1066,10 +1092,7 @@ public class NotificationManagerService extends SystemService {
@Override
public boolean allowDisable(int what, IBinder token, String pkg) {
- if (isCall(pkg, null)) {
- return mZenMode == Settings.Global.ZEN_MODE_OFF;
- }
- return true;
+ return mZenModeHelper.allowDisable(what, token, pkg);
}
@Override
@@ -1194,9 +1217,6 @@ public class NotificationManagerService extends SystemService {
private final Uri ENABLED_NOTIFICATION_LISTENERS_URI
= Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
- private final Uri ZEN_MODE
- = Settings.Global.getUriFor(Settings.Global.ZEN_MODE);
-
SettingsObserver(Handler handler) {
super(handler);
}
@@ -1207,8 +1227,6 @@ public class NotificationManagerService extends SystemService {
false, this, UserHandle.USER_ALL);
resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI,
false, this, UserHandle.USER_ALL);
- resolver.registerContentObserver(ZEN_MODE,
- false, this);
update(null);
}
@@ -1229,13 +1247,11 @@ public class NotificationManagerService extends SystemService {
if (uri == null || ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri)) {
rebindListenerServices();
}
- if (ZEN_MODE.equals(uri)) {
- updateZenMode();
- }
}
}
private SettingsObserver mSettingsObserver;
+ private ZenModeHelper mZenModeHelper;
static long[] getLongArray(Resources r, int resid, int maxlen, long[] def) {
int[] ar = r.getIntArray(resid);
@@ -1261,6 +1277,15 @@ public class NotificationManagerService extends SystemService {
mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
mHandler = new WorkerHandler();
+ mZenModeHelper = new ZenModeHelper(getContext(), mHandler);
+ mZenModeHelper.setCallback(new ZenModeHelper.Callback() {
+ @Override
+ public void onConfigChanged() {
+ savePolicyFile();
+ }
+ });
+ final File systemDir = new File(Environment.getDataDirectory(), "system");
+ mPolicyFile = new AtomicFile(new File(systemDir, "notification_policy.xml"));
importOldBlockDb();
@@ -1297,7 +1322,7 @@ public class NotificationManagerService extends SystemService {
Settings.Global.DEVICE_PROVISIONED, 0)) {
mDisableNotificationAlerts = true;
}
- updateZenMode();
+ mZenModeHelper.updateZenMode();
updateCurrentProfilesCache(getContext());
@@ -1350,7 +1375,7 @@ public class NotificationManagerService extends SystemService {
* Read the old XML-based app block database and import those blockages into the AppOps system.
*/
private void importOldBlockDb() {
- loadBlockDb();
+ loadPolicyFile();
PackageManager pm = getContext().getPackageManager();
for (String pkg : mBlockedPackages) {
@@ -1363,9 +1388,6 @@ public class NotificationManagerService extends SystemService {
}
}
mBlockedPackages.clear();
- if (mPolicyFile != null) {
- mPolicyFile.delete();
- }
}
@Override
@@ -1745,6 +1767,18 @@ public class NotificationManagerService extends SystemService {
}
@Override
+ public ZenModeConfig getZenModeConfig() {
+ checkCallerIsSystem();
+ return mZenModeHelper.getConfig();
+ }
+
+ @Override
+ public boolean setZenModeConfig(ZenModeConfig config) {
+ checkCallerIsSystem();
+ return mZenModeHelper.setConfig(config);
+ }
+
+ @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
@@ -1825,7 +1859,6 @@ public class NotificationManagerService extends SystemService {
pw.println(" mSoundNotification=" + mSoundNotification);
pw.println(" mVibrateNotification=" + mVibrateNotification);
pw.println(" mDisableNotificationAlerts=" + mDisableNotificationAlerts);
- pw.println(" mZenMode=" + Settings.Global.zenModeToString(mZenMode));
pw.println(" mSystemReady=" + mSystemReady);
pw.println(" mArchive=" + mArchive.toString());
Iterator<StatusBarNotification> iter = mArchive.descendingIterator();
@@ -1841,6 +1874,8 @@ public class NotificationManagerService extends SystemService {
pw.println("\n Usage Stats:");
mUsageStats.dump(pw, " ");
+ pw.println("\n Zen Mode:");
+ mZenModeHelper.dump(pw, " ");
}
}
@@ -1973,7 +2008,7 @@ public class NotificationManagerService extends SystemService {
}
// Is this notification intercepted by zen mode?
- final boolean intercept = shouldIntercept(pkg, notification);
+ final boolean intercept = mZenModeHelper.shouldIntercept(pkg, notification);
notification.extras.putBoolean(EXTRA_INTERCEPT, intercept);
// Should this notification make noise, vibe, or use the LED?
@@ -2358,6 +2393,9 @@ public class NotificationManagerService extends SystemService {
case MESSAGE_TIMEOUT:
handleTimeout((ToastRecord)msg.obj);
break;
+ case MESSAGE_SAVE_POLICY_FILE:
+ handleSavePolicyFile();
+ break;
}
}
}
@@ -2722,42 +2760,6 @@ public class NotificationManagerService extends SystemService {
}
}
- private void updateZenMode() {
- final int mode = Settings.Global.getInt(getContext().getContentResolver(),
- Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
- if (mode != mZenMode) {
- Slog.d(TAG, String.format("updateZenMode: %s -> %s",
- Settings.Global.zenModeToString(mZenMode),
- Settings.Global.zenModeToString(mode)));
- }
- mZenMode = mode;
-
- final String[] exceptionPackages = null; // none (for now)
-
- // call restrictions
- final boolean muteCalls = mZenMode != Settings.Global.ZEN_MODE_OFF;
- mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, AudioManager.STREAM_RING,
- muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
- exceptionPackages);
- mAppOps.setRestriction(AppOpsManager.OP_PLAY_AUDIO, AudioManager.STREAM_RING,
- muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
- exceptionPackages);
-
- // alarm restrictions
- final boolean muteAlarms = false; // TODO until we save user config
- mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, AudioManager.STREAM_ALARM,
- muteAlarms ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
- exceptionPackages);
- mAppOps.setRestriction(AppOpsManager.OP_PLAY_AUDIO, AudioManager.STREAM_ALARM,
- muteAlarms ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
- exceptionPackages);
-
- // restrict vibrations with no hints
- mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, AudioManager.USE_DEFAULT_STREAM_TYPE,
- (muteAlarms || muteCalls) ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
- exceptionPackages);
- }
-
private void updateCurrentProfilesCache(Context context) {
UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
if (userManager != null) {
@@ -2788,19 +2790,4 @@ public class NotificationManagerService extends SystemService {
return mCurrentProfiles.get(userId) != null;
}
}
-
- private boolean isCall(String pkg, Notification n) {
- return CALL_PACKAGES.contains(pkg);
- }
-
- private boolean isAlarm(String pkg, Notification n) {
- return ALARM_PACKAGES.contains(pkg);
- }
-
- private boolean shouldIntercept(String pkg, Notification n) {
- if (mZenMode != Settings.Global.ZEN_MODE_OFF) {
- return !isAlarm(pkg, n);
- }
- return false;
- }
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
new file mode 100644
index 000000000000..80f5b5c1d541
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -0,0 +1,312 @@
+/**
+ * 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.AppOpsManager;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.database.ContentObserver;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.provider.Settings.Global;
+import android.service.notification.ZenModeConfig;
+import android.util.Slog;
+
+import com.android.internal.R;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * NotificationManagerService helper for functionality related to zen mode.
+ */
+public class ZenModeHelper {
+ private static final String TAG = "ZenModeHelper";
+
+ private static final String ACTION_ENTER_ZEN = "enter_zen";
+ private static final int REQUEST_CODE_ENTER = 100;
+ private static final String ACTION_EXIT_ZEN = "exit_zen";
+ private static final int REQUEST_CODE_EXIT = 101;
+ private static final String EXTRA_TIME = "time";
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final SettingsObserver mSettingsObserver;
+ private final AppOpsManager mAppOps;
+ private final ZenModeConfig mDefaultConfig;
+
+ private Callback mCallback;
+ private int mZenMode;
+ private ZenModeConfig mConfig;
+
+ // temporary, until we update apps to provide metadata
+ private static final Set<String> CALL_PACKAGES = new HashSet<String>(Arrays.asList(
+ "com.google.android.dialer",
+ "com.android.phone"
+ ));
+ private static final Set<String> MESSAGE_PACKAGES = new HashSet<String>(Arrays.asList(
+ "com.google.android.talk",
+ "com.android.mms"
+ ));
+
+ public ZenModeHelper(Context context, Handler handler) {
+ mContext = context;
+ mHandler = handler;
+ mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+ mDefaultConfig = readDefaultConfig(context.getResources());
+ mConfig = mDefaultConfig;
+ mSettingsObserver = new SettingsObserver(mHandler);
+ mSettingsObserver.observe();
+
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_ENTER_ZEN);
+ filter.addAction(ACTION_EXIT_ZEN);
+ mContext.registerReceiver(new ZenBroadcastReceiver(), filter);
+ }
+
+ 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();
+ }
+
+ public void setCallback(Callback callback) {
+ mCallback = callback;
+ }
+
+ public boolean shouldIntercept(String pkg, Notification n) {
+ if (mZenMode != Global.ZEN_MODE_OFF) {
+ if (isCall(pkg, n)) {
+ return !mConfig.allowCalls;
+ }
+ if (isMessage(pkg, n)) {
+ return !mConfig.allowMessages;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public void updateZenMode() {
+ final int mode = Global.getInt(mContext.getContentResolver(),
+ Global.ZEN_MODE, Global.ZEN_MODE_OFF);
+ if (mode != mZenMode) {
+ Slog.d(TAG, String.format("updateZenMode: %s -> %s",
+ Global.zenModeToString(mZenMode),
+ Global.zenModeToString(mode)));
+ }
+ mZenMode = mode;
+ final boolean zen = mZenMode != Global.ZEN_MODE_OFF;
+ final String[] exceptionPackages = null; // none (for now)
+
+ // call restrictions
+ final boolean muteCalls = zen && !mConfig.allowCalls;
+ mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, AudioManager.STREAM_RING,
+ muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
+ exceptionPackages);
+ mAppOps.setRestriction(AppOpsManager.OP_PLAY_AUDIO, AudioManager.STREAM_RING,
+ muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
+ exceptionPackages);
+
+ // restrict vibrations with no hints
+ mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, AudioManager.USE_DEFAULT_STREAM_TYPE,
+ zen ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
+ exceptionPackages);
+ }
+
+ public boolean allowDisable(int what, IBinder token, String pkg) {
+ if (isCall(pkg, null)) {
+ return mZenMode == Global.ZEN_MODE_OFF || mConfig.allowCalls;
+ }
+ return true;
+ }
+
+ 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);
+ }
+
+ 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;
+ mConfig = config;
+ Slog.d(TAG, "mConfig=" + mConfig);
+ if (mCallback != null) mCallback.onConfigChanged();
+ final String val = Integer.toString(mConfig.hashCode());
+ Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
+ updateAlarms();
+ updateZenMode();
+ return true;
+ }
+
+ private boolean isCall(String pkg, Notification n) {
+ return CALL_PACKAGES.contains(pkg);
+ }
+
+ private boolean isMessage(String pkg, Notification n) {
+ return MESSAGE_PACKAGES.contains(pkg);
+ }
+
+ private void updateAlarms() {
+ updateAlarm(ACTION_ENTER_ZEN, REQUEST_CODE_ENTER,
+ mConfig.sleepStartHour, mConfig.sleepStartMinute);
+ updateAlarm(ACTION_EXIT_ZEN, REQUEST_CODE_EXIT,
+ mConfig.sleepEndHour, mConfig.sleepEndMinute);
+ }
+
+ private void updateAlarm(String action, int requestCode, int hr, int min) {
+ final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ final long now = System.currentTimeMillis();
+ final Calendar c = Calendar.getInstance();
+ c.setTimeInMillis(now);
+ c.set(Calendar.HOUR_OF_DAY, hr);
+ c.set(Calendar.MINUTE, min);
+ c.set(Calendar.SECOND, 0);
+ c.set(Calendar.MILLISECOND, 0);
+ if (c.getTimeInMillis() <= now) {
+ c.add(Calendar.DATE, 1);
+ }
+ final long time = c.getTimeInMillis();
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, requestCode,
+ new Intent(action).putExtra(EXTRA_TIME, time), PendingIntent.FLAG_UPDATE_CURRENT);
+ alarms.cancel(pendingIntent);
+ if (mConfig.sleepMode != null) {
+ Slog.d(TAG, String.format("Scheduling %s for %s, %s in the future, now=%s",
+ action, ts(time), time - now, ts(now)));
+ alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
+ }
+ }
+
+ private static String ts(long time) {
+ return new Date(time) + " (" + time + ")";
+ }
+
+ public static boolean isWeekend(long time, int offsetDays) {
+ final Calendar c = Calendar.getInstance();
+ c.setTimeInMillis(time);
+ if (offsetDays != 0) {
+ c.add(Calendar.DATE, offsetDays);
+ }
+ final int day = c.get(Calendar.DAY_OF_WEEK);
+ return day == Calendar.SATURDAY || day == Calendar.SUNDAY;
+ }
+
+ private class SettingsObserver extends ContentObserver {
+ private final Uri ZEN_MODE = Global.getUriFor(Global.ZEN_MODE);
+
+ public SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ public void observe() {
+ final ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(ZEN_MODE, false /*notifyForDescendents*/, this);
+ update(null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ update(uri);
+ }
+
+ public void update(Uri uri) {
+ if (ZEN_MODE.equals(uri)) {
+ updateZenMode();
+ }
+ }
+ }
+
+ private class ZenBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ACTION_ENTER_ZEN.equals(intent.getAction())) {
+ setZenMode(intent, 1, Global.ZEN_MODE_ON);
+ } else if (ACTION_EXIT_ZEN.equals(intent.getAction())) {
+ setZenMode(intent, 0, Global.ZEN_MODE_OFF);
+ }
+ }
+
+ private void setZenMode(Intent intent, int wkendOffsetDays, int zenModeValue) {
+ final long schTime = intent.getLongExtra(EXTRA_TIME, 0);
+ final long now = System.currentTimeMillis();
+ Slog.d(TAG, String.format("%s scheduled for %s, fired at %s, delta=%s",
+ intent.getAction(), ts(schTime), ts(now), now - schTime));
+
+ final boolean skip = ZenModeConfig.SLEEP_MODE_WEEKNIGHTS.equals(mConfig.sleepMode) &&
+ isWeekend(schTime, wkendOffsetDays);
+
+ if (skip) {
+ Slog.d(TAG, "Skipping zen mode update for the weekend");
+ } else {
+ Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zenModeValue);
+ }
+ updateAlarms();
+ }
+ }
+
+ public interface Callback {
+ void onConfigChanged();
+ }
+}