Merge "Add notification channel groups."

This commit is contained in:
TreeHugger Robot
2017-01-26 04:49:29 +00:00
committed by Android (Google) Code Review
13 changed files with 582 additions and 40 deletions

View File

@@ -5420,6 +5420,7 @@ package android.app {
method public boolean canShowBadge();
method public int describeContents();
method public void enableVibration(boolean);
method public java.lang.String getGroup();
method public java.lang.String getId();
method public int getImportance();
method public int getLockscreenVisibility();
@@ -5427,6 +5428,7 @@ package android.app {
method public android.net.Uri getSound();
method public long[] getVibrationPattern();
method public void setBypassDnd(boolean);
method public void setGroup(java.lang.String);
method public void setImportance(int);
method public void setLights(boolean);
method public void setLockscreenVisibility(int);
@@ -5440,6 +5442,17 @@ package android.app {
field public static final java.lang.String DEFAULT_CHANNEL_ID = "miscellaneous";
}
public final class NotificationChannelGroup implements android.os.Parcelable {
ctor public NotificationChannelGroup(java.lang.String, java.lang.CharSequence);
ctor protected NotificationChannelGroup(android.os.Parcel);
method public int describeContents();
method public java.util.List<android.app.NotificationChannel> getChannels();
method public java.lang.String getId();
method public java.lang.CharSequence getName();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR;
}
public class NotificationManager {
method public java.lang.String addAutomaticZenRule(android.app.AutomaticZenRule);
method public boolean areNotificationsEnabled();
@@ -5447,6 +5460,8 @@ package android.app {
method public void cancel(java.lang.String, int);
method public void cancelAll();
method public void createNotificationChannel(android.app.NotificationChannel);
method public void createNotificationChannelGroup(android.app.NotificationChannelGroup);
method public void createNotificationChannelGroups(java.util.List<android.app.NotificationChannelGroup>);
method public void createNotificationChannels(java.util.List<android.app.NotificationChannel>);
method public void deleteNotificationChannel(java.lang.String);
method public android.service.notification.StatusBarNotification[] getActiveNotifications();

View File

@@ -5596,6 +5596,7 @@ package android.app {
method public boolean canShowBadge();
method public int describeContents();
method public void enableVibration(boolean);
method public java.lang.String getGroup();
method public java.lang.String getId();
method public int getImportance();
method public int getLockscreenVisibility();
@@ -5608,6 +5609,7 @@ package android.app {
method public void populateFromXml(org.xmlpull.v1.XmlPullParser);
method public void setBypassDnd(boolean);
method public void setDeleted(boolean);
method public void setGroup(java.lang.String);
method public void setImportance(int);
method public void setLights(boolean);
method public void setLockscreenVisibility(int);
@@ -5632,6 +5634,20 @@ package android.app {
field public static final int USER_LOCKED_VISIBILITY = 2; // 0x2
}
public final class NotificationChannelGroup implements android.os.Parcelable {
ctor public NotificationChannelGroup(java.lang.String, java.lang.CharSequence);
ctor protected NotificationChannelGroup(android.os.Parcel);
method public void addChannel(android.app.NotificationChannel);
method public int describeContents();
method public java.util.List<android.app.NotificationChannel> getChannels();
method public java.lang.String getId();
method public java.lang.CharSequence getName();
method public org.json.JSONObject toJson() throws org.json.JSONException;
method public void writeToParcel(android.os.Parcel, int);
method public void writeXml(org.xmlpull.v1.XmlSerializer) throws java.io.IOException;
field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR;
}
public class NotificationManager {
method public java.lang.String addAutomaticZenRule(android.app.AutomaticZenRule);
method public boolean areNotificationsEnabled();
@@ -5639,6 +5655,8 @@ package android.app {
method public void cancel(java.lang.String, int);
method public void cancelAll();
method public void createNotificationChannel(android.app.NotificationChannel);
method public void createNotificationChannelGroup(android.app.NotificationChannelGroup);
method public void createNotificationChannelGroups(java.util.List<android.app.NotificationChannelGroup>);
method public void createNotificationChannels(java.util.List<android.app.NotificationChannel>);
method public void deleteNotificationChannel(java.lang.String);
method public android.service.notification.StatusBarNotification[] getActiveNotifications();

View File

@@ -5430,6 +5430,7 @@ package android.app {
method public boolean canShowBadge();
method public int describeContents();
method public void enableVibration(boolean);
method public java.lang.String getGroup();
method public java.lang.String getId();
method public int getImportance();
method public int getLockscreenVisibility();
@@ -5437,6 +5438,7 @@ package android.app {
method public android.net.Uri getSound();
method public long[] getVibrationPattern();
method public void setBypassDnd(boolean);
method public void setGroup(java.lang.String);
method public void setImportance(int);
method public void setLights(boolean);
method public void setLockscreenVisibility(int);
@@ -5450,6 +5452,17 @@ package android.app {
field public static final java.lang.String DEFAULT_CHANNEL_ID = "miscellaneous";
}
public final class NotificationChannelGroup implements android.os.Parcelable {
ctor public NotificationChannelGroup(java.lang.String, java.lang.CharSequence);
ctor protected NotificationChannelGroup(android.os.Parcel);
method public int describeContents();
method public java.util.List<android.app.NotificationChannel> getChannels();
method public java.lang.String getId();
method public java.lang.CharSequence getName();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR;
}
public class NotificationManager {
method public java.lang.String addAutomaticZenRule(android.app.AutomaticZenRule);
method public boolean areNotificationsEnabled();
@@ -5457,6 +5470,8 @@ package android.app {
method public void cancel(java.lang.String, int);
method public void cancelAll();
method public void createNotificationChannel(android.app.NotificationChannel);
method public void createNotificationChannelGroup(android.app.NotificationChannelGroup);
method public void createNotificationChannelGroups(java.util.List<android.app.NotificationChannelGroup>);
method public void createNotificationChannels(java.util.List<android.app.NotificationChannel>);
method public void deleteNotificationChannel(java.lang.String);
method public android.service.notification.StatusBarNotification[] getActiveNotifications();

View File

@@ -54,7 +54,9 @@ interface INotificationManager
boolean areNotificationsEnabled(String pkg);
int getPackageImportance(String pkg);
void createNotificationChannelGroups(String pkg, in ParceledListSlice channelGroupList);
void createNotificationChannels(String pkg, in ParceledListSlice channelsList);
ParceledListSlice getNotificationChannelGroupsForPackage(String pkg, int uid, boolean includeDeleted);
void updateNotificationChannelForPackage(String pkg, int uid, in NotificationChannel channel);
NotificationChannel getNotificationChannel(String pkg, String channelId);
NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, boolean includeDeleted);

View File

@@ -21,6 +21,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
import android.annotation.SystemApi;
import android.app.NotificationManager;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
@@ -49,6 +50,8 @@ public final class NotificationChannel implements Parcelable {
private static final String ATT_VISIBILITY = "visibility";
private static final String ATT_IMPORTANCE = "importance";
private static final String ATT_LIGHTS = "lights";
//TODO: add support for light colors
private static final String ATT_LIGHT_COLOR = "light_color";
private static final String ATT_VIBRATION = "vibration";
private static final String ATT_VIBRATION_ENABLED = "vibration_enabled";
private static final String ATT_SOUND = "sound";
@@ -56,6 +59,7 @@ public final class NotificationChannel implements Parcelable {
private static final String ATT_AUDIO_ATTRIBUTES = "audio_attributes";
private static final String ATT_SHOW_BADGE = "show_badge";
private static final String ATT_USER_LOCKED = "locked";
private static final String ATT_GROUP = "group";
private static final String DELIMITER = ",";
/**
@@ -136,6 +140,7 @@ public final class NotificationChannel implements Parcelable {
private boolean mVibrationEnabled;
private boolean mShowBadge = DEFAULT_SHOW_BADGE;
private boolean mDeleted = DEFAULT_DELETED;
private String mGroup;
/**
* Creates a notification channel.
@@ -173,6 +178,11 @@ public final class NotificationChannel implements Parcelable {
mVibrationEnabled = in.readByte() != 0;
mShowBadge = in.readByte() != 0;
mDeleted = in.readByte() != 0;
if (in.readByte() != 0) {
mGroup = in.readString();
} else {
mGroup = null;
}
}
@Override
@@ -199,6 +209,12 @@ public final class NotificationChannel implements Parcelable {
dest.writeByte(mVibrationEnabled ? (byte) 1 : (byte) 0);
dest.writeByte(mShowBadge ? (byte) 1 : (byte) 0);
dest.writeByte(mDeleted ? (byte) 1 : (byte) 0);
if (mGroup != null) {
dest.writeByte((byte) 1);
dest.writeString(mGroup);
} else {
dest.writeByte((byte) 0);
}
}
/**
@@ -254,6 +270,18 @@ public final class NotificationChannel implements Parcelable {
// Modifiable by apps on channel creation.
/**
* Sets what group this channel belongs to.
*
* Group information is only used for presentation, not for behavior.
*
* @param groupId the id of a group created by
* {@link NotificationManager#createNotificationChannelGroup(NotificationChannelGroup)}.
*/
public void setGroup(String groupId) {
this.mGroup = groupId;
}
/**
* Sets whether notifications posted to this channel can appear as application icon badges
* in a Launcher.
@@ -376,6 +404,15 @@ public final class NotificationChannel implements Parcelable {
return mShowBadge;
}
/**
* Returns what group this channel belongs to.
*
* This is used only for visually grouping channels in the UI.
*/
public String getGroup() {
return mGroup;
}
/**
* @hide
*/
@@ -407,6 +444,7 @@ public final class NotificationChannel implements Parcelable {
setVibrationPattern(safeLongArray(parser, ATT_VIBRATION, null));
setShowBadge(safeBool(parser, ATT_SHOW_BADGE, false));
setDeleted(safeBool(parser, ATT_DELETED, false));
setGroup(parser.getAttributeValue(null, ATT_GROUP));
lockFields(safeInt(parser, ATT_USER_LOCKED, 0));
}
@@ -451,6 +489,9 @@ public final class NotificationChannel implements Parcelable {
if (isDeleted()) {
out.attribute(null, ATT_DELETED, Boolean.toString(isDeleted()));
}
if (getGroup() != null) {
out.attribute(null, ATT_GROUP, getGroup());
}
out.endTag(null, TAG_CHANNEL);
}
@@ -482,6 +523,7 @@ public final class NotificationChannel implements Parcelable {
record.put(ATT_VIBRATION, longArrayToString(getVibrationPattern()));
record.put(ATT_SHOW_BADGE, Boolean.toString(canShowBadge()));
record.put(ATT_DELETED, Boolean.toString(isDeleted()));
record.put(ATT_GROUP, getGroup());
return record;
}
@@ -527,10 +569,12 @@ public final class NotificationChannel implements Parcelable {
private static String longArrayToString(long[] values) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < values.length - 1; i++) {
sb.append(values[i]).append(DELIMITER);
if (values != null) {
for (int i = 0; i < values.length - 1; i++) {
sb.append(values[i]).append(DELIMITER);
}
sb.append(values[values.length - 1]);
}
sb.append(values[values.length - 1]);
return sb.toString();
}
@@ -558,35 +602,41 @@ public final class NotificationChannel implements Parcelable {
NotificationChannel that = (NotificationChannel) o;
if (mImportance != that.mImportance) return false;
if (getImportance() != that.getImportance()) return false;
if (mBypassDnd != that.mBypassDnd) return false;
if (mLockscreenVisibility != that.mLockscreenVisibility) return false;
if (getLockscreenVisibility() != that.getLockscreenVisibility()) return false;
if (mLights != that.mLights) return false;
if (mUserLockedFields != that.mUserLockedFields) return false;
if (getUserLockedFields() != that.getUserLockedFields()) return false;
if (mVibrationEnabled != that.mVibrationEnabled) return false;
if (mShowBadge != that.mShowBadge) return false;
if (mDeleted != that.mDeleted) return false;
if (mId != null ? !mId.equals(that.mId) : that.mId != null) return false;
if (mName != null ? !mName.equals(that.mName) : that.mName != null) return false;
if (mSound != null ? !mSound.equals(that.mSound) : that.mSound != null) return false;
return Arrays.equals(mVibration, that.mVibration);
if (isDeleted() != that.isDeleted()) return false;
if (getId() != null ? !getId().equals(that.getId()) : that.getId() != null) return false;
if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
return false;
}
if (getSound() != null ? !getSound().equals(that.getSound()) : that.getSound() != null) {
return false;
}
if (!Arrays.equals(mVibration, that.mVibration)) return false;
return getGroup() != null ? getGroup().equals(that.getGroup()) : that.getGroup() == null;
}
@Override
public int hashCode() {
int result = mId != null ? mId.hashCode() : 0;
result = 31 * result + (mName != null ? mName.hashCode() : 0);
result = 31 * result + mImportance;
int result = getId() != null ? getId().hashCode() : 0;
result = 31 * result + (getName() != null ? getName().hashCode() : 0);
result = 31 * result + getImportance();
result = 31 * result + (mBypassDnd ? 1 : 0);
result = 31 * result + mLockscreenVisibility;
result = 31 * result + (mSound != null ? mSound.hashCode() : 0);
result = 31 * result + getLockscreenVisibility();
result = 31 * result + (getSound() != null ? getSound().hashCode() : 0);
result = 31 * result + (mLights ? 1 : 0);
result = 31 * result + Arrays.hashCode(mVibration);
result = 31 * result + mUserLockedFields;
result = 31 * result + getUserLockedFields();
result = 31 * result + (mVibrationEnabled ? 1 : 0);
result = 31 * result + (mShowBadge ? 1 : 0);
result = 31 * result + (mDeleted ? 1 : 0);
result = 31 * result + (isDeleted() ? 1 : 0);
result = 31 * result + (getGroup() != null ? getGroup().hashCode() : 0);
return result;
}
@@ -605,6 +655,7 @@ public final class NotificationChannel implements Parcelable {
", mVibrationEnabled=" + mVibrationEnabled +
", mShowBadge=" + mShowBadge +
", mDeleted=" + mDeleted +
", mGroup='" + mGroup + '\'' +
'}';
}
}

View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) 2017, 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.app;
parcelable NotificationChannelGroup;

View File

@@ -0,0 +1,187 @@
/*
* Copyright (C) 2017 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.app;
import android.annotation.SystemApi;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.service.notification.NotificationListenerService;
import android.text.TextUtils;
import android.util.Slog;
import org.json.JSONException;
import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* A grouping of related notification channels. e.g., channels that all belong to a single account.
*/
public final class NotificationChannelGroup implements Parcelable {
private static final String TAG_GROUP = "channelGroup";
private static final String ATT_NAME = "name";
private static final String ATT_ID = "id";
private final String mId;
private CharSequence mName;
private List<NotificationChannel> mChannels = new ArrayList<>();
/**
* Creates a notification channel.
*
* @param id The id of the group. Must be unique per package.
* @param name The user visible name of the group.
*/
public NotificationChannelGroup(String id, CharSequence name) {
this.mId = id;
this.mName = name;
}
protected NotificationChannelGroup(Parcel in) {
if (in.readByte() != 0) {
mId = in.readString();
} else {
mId = null;
}
mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
in.readList(mChannels, NotificationChannel.class.getClassLoader());
}
@Override
public void writeToParcel(Parcel dest, int flags) {
if (mId != null) {
dest.writeByte((byte) 1);
dest.writeString(mId);
} else {
dest.writeByte((byte) 0);
}
TextUtils.writeToParcel(mName, dest, flags);
dest.writeParcelableList(mChannels, flags);
}
/**
* Returns the id of this channel.
*/
public String getId() {
return mId;
}
/**
* Returns the user visible name of this channel.
*/
public CharSequence getName() {
return mName;
}
/*
* Returns the list of channels that belong to this group
*
* @hide
*/
@SystemApi
public List<NotificationChannel> getChannels() {
return mChannels;
}
/**
* @hide
*/
@SystemApi
public void addChannel(NotificationChannel channel) {
mChannels.add(channel);
}
/**
* @hide
*/
@SystemApi
public void writeXml(XmlSerializer out) throws IOException {
out.startTag(null, TAG_GROUP);
out.attribute(null, ATT_ID, getId());
out.attribute(null, ATT_NAME, getName().toString());
out.endTag(null, TAG_GROUP);
}
/**
* @hide
*/
@SystemApi
public JSONObject toJson() throws JSONException {
JSONObject record = new JSONObject();
record.put(ATT_ID, getId());
record.put(ATT_NAME, getName());
return record;
}
public static final Creator<NotificationChannelGroup> CREATOR =
new Creator<NotificationChannelGroup>() {
@Override
public NotificationChannelGroup createFromParcel(Parcel in) {
return new NotificationChannelGroup(in);
}
@Override
public NotificationChannelGroup[] newArray(int size) {
return new NotificationChannelGroup[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NotificationChannelGroup that = (NotificationChannelGroup) o;
if (getId() != null ? !getId().equals(that.getId()) : that.getId() != null) return false;
if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
return false;
}
return getChannels() != null ? getChannels().equals(that.getChannels())
: that.getChannels() == null;
}
@Override
public int hashCode() {
int result = getId() != null ? getId().hashCode() : 0;
result = 31 * result + (getName() != null ? getName().hashCode() : 0);
result = 31 * result + (getChannels() != null ? getChannels().hashCode() : 0);
return result;
}
@Override
public String toString() {
return "NotificationChannelGroup{" +
"mId='" + mId + '\'' +
", mName=" + mName +
'}';
}
}

View File

@@ -382,9 +382,47 @@ public class NotificationManager
}
}
/**
* Creates a group container for {@link NotificationChannel} objects.
*
* This is a no-op for groups that already exist.
* <p>
* Group information is only used for presentation, not for behavior. Groups are optional
* for channels, and you can have a mix of channels that belong to groups and channels
* that do not.
* </p>
* <p>
* For example, if your application supports multiple accounts, and those accounts will
* have similar channels, you can create a group for each account with account specific
* labels instead of appending account information to each channel's label.
* </p>
*
* @param group The group to create
*/
public void createNotificationChannelGroup(@NonNull NotificationChannelGroup group) {
createNotificationChannelGroups(Arrays.asList(group));
}
/**
* Creates multiple notification channel groups.
*
* @param groups The list of groups to create
*/
public void createNotificationChannelGroups(@NonNull List<NotificationChannelGroup> groups) {
INotificationManager service = getService();
try {
service.createNotificationChannelGroups(mContext.getPackageName(),
new ParceledListSlice(groups));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Creates a notification channel that notifications can be posted to.
*
* This is a no-op for channels that already exist.
*
* @param channel the channel to create. Note that the created channel may differ from this
* value. If the channel already exists, it will not be modified.
*/

View File

@@ -55,6 +55,7 @@ import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
import android.app.NotificationChannelGroup;
import android.app.backup.BackupManager;
import android.app.IActivityManager;
import android.app.INotificationManager;
@@ -1600,6 +1601,21 @@ public class NotificationManagerService extends SystemService {
savePolicyFile();
}
@Override
public void createNotificationChannelGroups(String pkg,
ParceledListSlice channelGroupList) throws RemoteException {
checkCallerIsSystemOrSameApp(pkg);
List<NotificationChannelGroup> groups = channelGroupList.getList();
final int groupSize = groups.size();
for (int i = 0; i < groupSize; i++) {
final NotificationChannelGroup group = groups.get(i);
Preconditions.checkNotNull(group, "group in list is null");
mRankingHelper.createNotificationChannelGroup(pkg, Binder.getCallingUid(), group,
true /* fromTargetApp */);
}
savePolicyFile();
}
@Override
public void createNotificationChannels(String pkg,
ParceledListSlice channelsList) throws RemoteException {
@@ -1657,6 +1673,13 @@ public class NotificationManagerService extends SystemService {
return mRankingHelper.getNotificationChannels(pkg, uid, includeDeleted);
}
@Override
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroupsForPackage(
String pkg, int uid, boolean includeDeleted) {
checkCallerIsSystem();
return mRankingHelper.getNotificationChannelGroups(pkg, uid, includeDeleted);
}
@Override
public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg) {
checkCallerIsSystemOrSameApp(pkg);

View File

@@ -16,6 +16,7 @@
package com.android.server.notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.content.pm.ParceledListSlice;
public interface RankingConfig {
@@ -25,6 +26,10 @@ public interface RankingConfig {
void setShowBadge(String packageName, int uid, boolean showBadge);
boolean canShowBadge(String packageName, int uid);
void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
boolean fromTargetApp);
ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
int uid, boolean includeDeleted);
void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
boolean fromTargetApp);
void updateNotificationChannel(String pkg, int uid, NotificationChannel channel);

View File

@@ -23,6 +23,7 @@ import com.android.internal.util.Preconditions;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -59,6 +60,7 @@ public class RankingHelper implements RankingConfig {
private static final String TAG_RANKING = "ranking";
private static final String TAG_PACKAGE = "package";
private static final String TAG_CHANNEL = "channel";
private static final String TAG_GROUP = "channelGroup";
private static final String ATT_VERSION = "version";
private static final String ATT_NAME = "name";
@@ -174,7 +176,6 @@ public class RankingHelper implements RankingConfig {
safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
safeBool(parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE));
// Channels
final int innerDepth = parser.getDepth();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG
@@ -184,6 +185,17 @@ public class RankingHelper implements RankingConfig {
}
String tagName = parser.getName();
// Channel groups
if (TAG_GROUP.equals(tagName)) {
String id = parser.getAttributeValue(null, ATT_ID);
CharSequence groupName = parser.getAttributeValue(null, ATT_NAME);
if (!TextUtils.isEmpty(id)) {
final NotificationChannelGroup group =
new NotificationChannelGroup(id, groupName);
r.groups.put(id, group);
}
}
// Channels
if (TAG_CHANNEL.equals(tagName)) {
String id = parser.getAttributeValue(null, ATT_ID);
CharSequence channelName = parser.getAttributeValue(null, ATT_NAME);
@@ -302,7 +314,8 @@ public class RankingHelper implements RankingConfig {
}
final boolean hasNonDefaultSettings = r.importance != DEFAULT_IMPORTANCE
|| r.priority != DEFAULT_PRIORITY || r.visibility != DEFAULT_VISIBILITY
|| r.showBadge != DEFAULT_SHOW_BADGE || r.channels.size() > 0;
|| r.showBadge != DEFAULT_SHOW_BADGE || r.channels.size() > 0
|| r.groups.size() > 0;
if (hasNonDefaultSettings) {
out.startTag(null, TAG_PACKAGE);
out.attribute(null, ATT_NAME, r.pkg);
@@ -321,6 +334,10 @@ public class RankingHelper implements RankingConfig {
out.attribute(null, ATT_UID, Integer.toString(r.uid));
}
for (NotificationChannelGroup group : r.groups.values()) {
group.writeXml(out);
}
for (NotificationChannel channel : r.channels.values()) {
channel.writeXml(out);
}
@@ -440,6 +457,21 @@ public class RankingHelper implements RankingConfig {
updateConfig();
}
@Override
public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
boolean fromTargetApp) {
Preconditions.checkNotNull(pkg);
Preconditions.checkNotNull(group);
Preconditions.checkNotNull(group.getId());
Preconditions.checkNotNull(group.getName());
Record r = getOrCreateRecord(pkg, uid);
if (r == null) {
throw new IllegalArgumentException("Invalid package");
}
r.groups.put(group.getId(), group);
updateConfig();
}
@Override
public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
boolean fromTargetApp) {
@@ -454,6 +486,10 @@ public class RankingHelper implements RankingConfig {
if (IMPORTANCE_NONE == r.importance) {
throw new IllegalArgumentException("Package blocked");
}
if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
}
NotificationChannel existing = r.channels.get(channel.getId());
// Keep existing settings
if (existing != null) {
@@ -549,8 +585,9 @@ public class RankingHelper implements RankingConfig {
channel.setShowBadge(updatedChannel.canShowBadge());
}
if (updatedChannel.isDeleted()) {
updatedChannel.setDeleted(true);
channel.setDeleted(true);
}
// Assistant cannot change the group
r.channels.put(channel.getId(), channel);
updateConfig();
@@ -631,6 +668,36 @@ public class RankingHelper implements RankingConfig {
}
}
@Override
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
int uid, boolean includeDeleted) {
Preconditions.checkNotNull(pkg);
List<NotificationChannelGroup> groups = new ArrayList<>();
Record r = getRecord(pkg, uid);
if (r == null) {
return ParceledListSlice.emptyList();
}
NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
int N = r.channels.size();
for (int i = 0; i < N; i++) {
final NotificationChannel nc = r.channels.valueAt(i);
if (includeDeleted || !nc.isDeleted()) {
if (nc.getGroup() != null) {
// lazily populate channel list
NotificationChannelGroup ncg = r.groups.get(nc.getGroup());
ncg.addChannel(nc);
} else {
nonGrouped.addChannel(nc);
}
}
}
groups.addAll(r.groups.values());
if (nonGrouped.getChannels().size() > 0) {
groups.add(nonGrouped);
}
return new ParceledListSlice<>(groups);
}
@Override
public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
boolean includeDeleted) {
@@ -723,6 +790,12 @@ public class RankingHelper implements RankingConfig {
pw.print(" ");
pw.println(channel);
}
for (NotificationChannelGroup group : r.groups.values()) {
pw.print(prefix);
pw.print(" ");
pw.print(" ");
pw.println(group);
}
}
}
}
@@ -758,6 +831,9 @@ public class RankingHelper implements RankingConfig {
for (NotificationChannel channel : r.channels.values()) {
record.put("channel", channel.toJson());
}
for (NotificationChannelGroup group : r.groups.values()) {
record.put("group", group.toJson());
}
} catch (JSONException e) {
// pass
}
@@ -871,5 +947,6 @@ public class RankingHelper implements RankingConfig {
boolean showBadge = DEFAULT_SHOW_BADGE;
ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
ArrayMap<String, NotificationChannelGroup> groups = new ArrayMap<>();
}
}

View File

@@ -63,8 +63,7 @@ import org.junit.runner.RunWith;
import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
@SmallTest
@RunWith(AndroidJUnit4.class)
// THESE TESTS ARE DISABLED FOR NOW BECAUSE THEY DO NOT FINISH 1/2 THE TIME.
public class NotificationManagerServiceTest {
private final String pkg = "com.android.server.notification";
private final int uid = Binder.getCallingUid();
@@ -137,7 +136,6 @@ public class NotificationManagerServiceTest {
return new NotificationRecord(mContext, sbn, channel);
}
@Test
@UiThreadTest
public void testCreateNotificationChannels_SingleChannel() throws Exception {
final NotificationChannel channel =
@@ -149,7 +147,6 @@ public class NotificationManagerServiceTest {
assertTrue(createdChannel != null);
}
@Test
@UiThreadTest
public void testCreateNotificationChannels_NullChannelThrowsException() throws Exception {
try {
@@ -161,7 +158,6 @@ public class NotificationManagerServiceTest {
}
}
@Test
@UiThreadTest
public void testCreateNotificationChannels_TwoChannels() throws Exception {
final NotificationChannel channel1 =
@@ -174,7 +170,6 @@ public class NotificationManagerServiceTest {
assertTrue(mBinderService.getNotificationChannel("test_pkg", "id2") != null);
}
@Test
@UiThreadTest
public void testCreateNotificationChannels_SecondCreateDoesNotChangeImportance()
throws Exception {
@@ -193,7 +188,6 @@ public class NotificationManagerServiceTest {
assertEquals(NotificationManager.IMPORTANCE_DEFAULT, createdChannel.getImportance());
}
@Test
@UiThreadTest
public void testCreateNotificationChannels_IdenticalChannelsInListIgnoresSecond()
throws Exception {
@@ -208,7 +202,6 @@ public class NotificationManagerServiceTest {
assertEquals(NotificationManager.IMPORTANCE_DEFAULT, createdChannel.getImportance());
}
@Test
@UiThreadTest
public void testBlockedNotifications_suspended() throws Exception {
NotificationUsageStats usageStats = mock(NotificationUsageStats.class);
@@ -224,7 +217,6 @@ public class NotificationManagerServiceTest {
verify(usageStats, times(1)).registerSuspendedByAdmin(eq(r));
}
@Test
@UiThreadTest
public void testBlockedNotifications_blockedChannel() throws Exception {
NotificationUsageStats usageStats = mock(NotificationUsageStats.class);
@@ -241,7 +233,6 @@ public class NotificationManagerServiceTest {
verify(usageStats, times(1)).registerBlocked(eq(r));
}
@Test
@UiThreadTest
public void testBlockedNotifications_blockedApp() throws Exception {
NotificationUsageStats usageStats = mock(NotificationUsageStats.class);
@@ -258,7 +249,6 @@ public class NotificationManagerServiceTest {
verify(usageStats, times(1)).registerBlocked(eq(r));
}
@Test
@UiThreadTest
public void testEnqueueNotificationWithTag_PopulatesGetActiveNotifications() throws Exception {
mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
@@ -269,7 +259,6 @@ public class NotificationManagerServiceTest {
assertEquals(1, notifs.length);
}
@Test
@UiThreadTest
public void testCancelNotificationImmediatelyAfterEnqueue() throws Exception {
mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
@@ -281,7 +270,6 @@ public class NotificationManagerServiceTest {
assertEquals(0, notifs.length);
}
@Test
@UiThreadTest
public void testCancelNotificationsFromListenerImmediatelyAfterEnqueue() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
@@ -294,7 +282,6 @@ public class NotificationManagerServiceTest {
assertEquals(0, notifs.length);
}
@Test
@UiThreadTest
public void testCancelAllNotificationsImmediatelyAfterEnqueue() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
@@ -307,7 +294,6 @@ public class NotificationManagerServiceTest {
assertEquals(0, notifs.length);
}
@Test
@UiThreadTest
public void testCancelAllNotifications_IgnoreForegroundService() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
@@ -321,7 +307,6 @@ public class NotificationManagerServiceTest {
assertEquals(1, notifs.length);
}
@Test
@UiThreadTest
public void testCancelAllNotifications_IgnoreOtherPackages() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
@@ -335,7 +320,6 @@ public class NotificationManagerServiceTest {
assertEquals(1, notifs.length);
}
@Test
@UiThreadTest
public void testCancelAllNotifications_NullPkgRemovesAll() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
@@ -348,7 +332,6 @@ public class NotificationManagerServiceTest {
assertEquals(0, notifs.length);
}
@Test
@UiThreadTest
public void testCancelAllNotifications_NullPkgIgnoresUserAllNotifications() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;

View File

@@ -30,12 +30,12 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
import android.app.Notification;
import android.app.NotificationChannelGroup;
import android.content.Context;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.net.Uri;
import android.os.Build;
import android.os.UserHandle;
@@ -186,6 +186,7 @@ public class RankingHelperTest {
assertEquals(expected.getSound(), actual.getSound());
assertEquals(expected.canBypassDnd(), actual.canBypassDnd());
assertTrue(Arrays.equals(expected.getVibrationPattern(), actual.getVibrationPattern()));
assertEquals(expected.getGroup(), actual.getGroup());
}
@Test
@@ -240,6 +241,7 @@ public class RankingHelperTest {
@Test
public void testChannelXml() throws Exception {
NotificationChannelGroup ncg = new NotificationChannelGroup("1", "2");
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
NotificationChannel channel2 =
@@ -249,8 +251,10 @@ public class RankingHelperTest {
channel2.setBypassDnd(true);
channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
channel2.enableVibration(true);
channel2.setGroup(ncg.getId());
channel2.setVibrationPattern(new long[] {100, 67, 145, 156});
mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
mHelper.createNotificationChannel(pkg, uid, channel1, true);
mHelper.createNotificationChannel(pkg, uid, channel2, false);
@@ -274,6 +278,10 @@ public class RankingHelperTest {
mHelper.getNotificationChannel(pkg, uid, channel2.getId(), false));
assertNotNull(mHelper.getNotificationChannel(
pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false));
assertEquals(ncg.getId(),
mHelper.getNotificationChannelGroups(pkg, uid, false).getList().get(0).getId());
assertEquals(channel2.getGroup(), mHelper.getNotificationChannelGroups(
pkg, uid, false).getList().get(0).getChannels().get(0).getGroup());
}
@Test
@@ -765,10 +773,111 @@ public class RankingHelperTest {
assertEquals(2, mHelper.getNotificationChannels(pkg, uid, false).getList().size());
}
@Test
public void testOnPackageChanged_packageRemoval_importance() throws Exception {
mHelper.setImportance(pkg, uid, NotificationManager.IMPORTANCE_HIGH);
mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(pkg, uid));
}
@Test
public void testOnPackageChanged_packageRemoval_groups() throws Exception {
NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
assertEquals(0, mHelper.getNotificationChannelGroups(pkg, uid, true).getList().size());
}
@Test
public void testRecordDefaults() throws Exception {
assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(pkg, uid));
assertEquals(true, mHelper.canShowBadge(pkg, uid));
assertEquals(1, mHelper.getNotificationChannels(pkg, uid, false).getList().size());
}
@Test
public void testCreateGroup() throws Exception {
NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
assertEquals(ncg, mHelper.getNotificationChannelGroups(pkg, uid, false).getList().get(0));
}
@Test
public void testCannotCreateChannel_badGroup() throws Exception {
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
channel1.setGroup("garbage");
try {
mHelper.createNotificationChannel(pkg, uid, channel1, true);
fail("Created a channel with a bad group");
} catch (IllegalArgumentException e) {}
}
@Test
public void testCannotCreateChannel_goodGroup() throws Exception {
NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
channel1.setGroup(ncg.getId());
mHelper.createNotificationChannel(pkg, uid, channel1, true);
assertEquals(ncg.getId(),
mHelper.getNotificationChannel(pkg, uid, channel1.getId(), false).getGroup());
}
@Test
public void testGetChannelGroups() throws Exception {
NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
channel1.setGroup(ncg.getId());
mHelper.createNotificationChannel(pkg, uid, channel1, true);
NotificationChannel channel1a =
new NotificationChannel("id1a", "name1", NotificationManager.IMPORTANCE_HIGH);
channel1a.setGroup(ncg.getId());
mHelper.createNotificationChannel(pkg, uid, channel1a, true);
NotificationChannel channel2 =
new NotificationChannel("id2", "name1", NotificationManager.IMPORTANCE_HIGH);
channel2.setGroup(ncg2.getId());
mHelper.createNotificationChannel(pkg, uid, channel2, true);
NotificationChannel channel3 =
new NotificationChannel("id3", "name1", NotificationManager.IMPORTANCE_HIGH);
mHelper.createNotificationChannel(pkg, uid, channel3, true);
List<NotificationChannelGroup> actual =
mHelper.getNotificationChannelGroups(pkg, uid, true).getList();
assertEquals(3, actual.size());
for (NotificationChannelGroup group: actual) {
if (group.getId() == null) {
assertEquals(2, group.getChannels().size()); // misc channel too
assertTrue(channel3.getId().equals(group.getChannels().get(0).getId())
|| channel3.getId().equals(group.getChannels().get(1).getId()));
} else if (group.getId().equals(ncg.getId())) {
assertEquals(2, group.getChannels().size());
if (group.getChannels().get(0).getId().equals(channel1.getId())) {
assertTrue(group.getChannels().get(1).getId().equals(channel1a.getId()));
} else if (group.getChannels().get(0).getId().equals(channel1a.getId())) {
assertTrue(group.getChannels().get(1).getId().equals(channel1.getId()));
} else {
fail("expected channel not found");
}
} else if (group.getId().equals(ncg2.getId())) {
assertEquals(1, group.getChannels().size());
assertEquals(channel2.getId(), group.getChannels().get(0).getId());
}
}
}
}