Merge "Do not disturb: persist user config."

This commit is contained in:
John Spurlock
2014-04-22 15:02:36 +00:00
committed by Android (Google) Code Review
11 changed files with 714 additions and 122 deletions

View File

@@ -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);
}

View File

@@ -6119,6 +6119,13 @@ public final class Settings {
return "ZEN_MODE_ON";
}
/**
* 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.
*

View File

@@ -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;

View File

@@ -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];
}
};
}

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);
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;
}
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
@@ -1744,6 +1766,18 @@ public class NotificationManagerService extends SystemService {
return NotificationManagerService.this.getActiveNotificationKeysFromListener(token);
}
@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)
@@ -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;
}
}

View File

@@ -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();
}
}