1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.app; 17 18 import android.annotation.SystemApi; 19 import android.annotation.TestApi; 20 import android.content.Intent; 21 import android.os.Parcel; 22 import android.os.Parcelable; 23 import android.text.TextUtils; 24 import android.util.proto.ProtoOutputStream; 25 26 import org.json.JSONException; 27 import org.json.JSONObject; 28 import org.xmlpull.v1.XmlPullParser; 29 import org.xmlpull.v1.XmlSerializer; 30 31 import java.io.IOException; 32 import java.util.ArrayList; 33 import java.util.List; 34 35 /** 36 * A grouping of related notification channels. e.g., channels that all belong to a single account. 37 */ 38 public final class NotificationChannelGroup implements Parcelable { 39 40 /** 41 * The maximum length for text fields in a NotificationChannelGroup. Fields will be truncated at 42 * this limit. 43 */ 44 private static final int MAX_TEXT_LENGTH = 1000; 45 46 private static final String TAG_GROUP = "channelGroup"; 47 private static final String ATT_NAME = "name"; 48 private static final String ATT_DESC = "desc"; 49 private static final String ATT_ID = "id"; 50 private static final String ATT_BLOCKED = "blocked"; 51 52 private final String mId; 53 private CharSequence mName; 54 private String mDescription; 55 private boolean mBlocked; 56 private List<NotificationChannel> mChannels = new ArrayList<>(); 57 58 /** 59 * Creates a notification channel group. 60 * 61 * @param id The id of the group. Must be unique per package. the value may be truncated if 62 * it is too long. 63 * @param name The user visible name of the group. You can rename this group when the system 64 * locale changes by listening for the {@link Intent#ACTION_LOCALE_CHANGED} 65 * broadcast. <p>The recommended maximum length is 40 characters; the value may be 66 * truncated if it is too long. 67 */ NotificationChannelGroup(String id, CharSequence name)68 public NotificationChannelGroup(String id, CharSequence name) { 69 this.mId = getTrimmedString(id); 70 this.mName = name != null ? getTrimmedString(name.toString()) : null; 71 } 72 73 /** 74 * @hide 75 */ NotificationChannelGroup(Parcel in)76 protected NotificationChannelGroup(Parcel in) { 77 if (in.readByte() != 0) { 78 mId = in.readString(); 79 } else { 80 mId = null; 81 } 82 mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 83 if (in.readByte() != 0) { 84 mDescription = in.readString(); 85 } else { 86 mDescription = null; 87 } 88 in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader()); 89 mBlocked = in.readBoolean(); 90 } 91 getTrimmedString(String input)92 private String getTrimmedString(String input) { 93 if (input != null && input.length() > MAX_TEXT_LENGTH) { 94 return input.substring(0, MAX_TEXT_LENGTH); 95 } 96 return input; 97 } 98 99 @Override writeToParcel(Parcel dest, int flags)100 public void writeToParcel(Parcel dest, int flags) { 101 if (mId != null) { 102 dest.writeByte((byte) 1); 103 dest.writeString(mId); 104 } else { 105 dest.writeByte((byte) 0); 106 } 107 TextUtils.writeToParcel(mName, dest, flags); 108 if (mDescription != null) { 109 dest.writeByte((byte) 1); 110 dest.writeString(mDescription); 111 } else { 112 dest.writeByte((byte) 0); 113 } 114 dest.writeParcelableList(mChannels, flags); 115 dest.writeBoolean(mBlocked); 116 } 117 118 /** 119 * Returns the id of this group. 120 */ getId()121 public String getId() { 122 return mId; 123 } 124 125 /** 126 * Returns the user visible name of this group. 127 */ getName()128 public CharSequence getName() { 129 return mName; 130 } 131 132 /** 133 * Returns the user visible description of this group. 134 */ getDescription()135 public String getDescription() { 136 return mDescription; 137 } 138 139 /** 140 * Returns the list of channels that belong to this group 141 */ getChannels()142 public List<NotificationChannel> getChannels() { 143 return mChannels; 144 } 145 146 /** 147 * Returns whether or not notifications posted to {@link NotificationChannel channels} belonging 148 * to this group are blocked. This value is independent of 149 * {@link NotificationManager#areNotificationsEnabled()} and 150 * {@link NotificationChannel#getImportance()}. 151 */ isBlocked()152 public boolean isBlocked() { 153 return mBlocked; 154 } 155 156 /** 157 * Sets the user visible description of this group. 158 * 159 * <p>The recommended maximum length is 300 characters; the value may be truncated if it is too 160 * long. 161 */ setDescription(String description)162 public void setDescription(String description) { 163 mDescription = getTrimmedString(description); 164 } 165 166 /** 167 * @hide 168 */ 169 @TestApi setBlocked(boolean blocked)170 public void setBlocked(boolean blocked) { 171 mBlocked = blocked; 172 } 173 174 /** 175 * @hide 176 */ addChannel(NotificationChannel channel)177 public void addChannel(NotificationChannel channel) { 178 mChannels.add(channel); 179 } 180 181 /** 182 * @hide 183 */ setChannels(List<NotificationChannel> channels)184 public void setChannels(List<NotificationChannel> channels) { 185 mChannels = channels; 186 } 187 188 /** 189 * @hide 190 */ populateFromXml(XmlPullParser parser)191 public void populateFromXml(XmlPullParser parser) { 192 // Name, id, and importance are set in the constructor. 193 setDescription(parser.getAttributeValue(null, ATT_DESC)); 194 setBlocked(safeBool(parser, ATT_BLOCKED, false)); 195 } 196 safeBool(XmlPullParser parser, String att, boolean defValue)197 private static boolean safeBool(XmlPullParser parser, String att, boolean defValue) { 198 final String value = parser.getAttributeValue(null, att); 199 if (TextUtils.isEmpty(value)) return defValue; 200 return Boolean.parseBoolean(value); 201 } 202 203 /** 204 * @hide 205 */ writeXml(XmlSerializer out)206 public void writeXml(XmlSerializer out) throws IOException { 207 out.startTag(null, TAG_GROUP); 208 209 out.attribute(null, ATT_ID, getId()); 210 if (getName() != null) { 211 out.attribute(null, ATT_NAME, getName().toString()); 212 } 213 if (getDescription() != null) { 214 out.attribute(null, ATT_DESC, getDescription().toString()); 215 } 216 out.attribute(null, ATT_BLOCKED, Boolean.toString(isBlocked())); 217 218 out.endTag(null, TAG_GROUP); 219 } 220 221 /** 222 * @hide 223 */ 224 @SystemApi toJson()225 public JSONObject toJson() throws JSONException { 226 JSONObject record = new JSONObject(); 227 record.put(ATT_ID, getId()); 228 record.put(ATT_NAME, getName()); 229 record.put(ATT_DESC, getDescription()); 230 record.put(ATT_BLOCKED, isBlocked()); 231 return record; 232 } 233 234 public static final Creator<NotificationChannelGroup> CREATOR = 235 new Creator<NotificationChannelGroup>() { 236 @Override 237 public NotificationChannelGroup createFromParcel(Parcel in) { 238 return new NotificationChannelGroup(in); 239 } 240 241 @Override 242 public NotificationChannelGroup[] newArray(int size) { 243 return new NotificationChannelGroup[size]; 244 } 245 }; 246 247 @Override describeContents()248 public int describeContents() { 249 return 0; 250 } 251 252 @Override equals(Object o)253 public boolean equals(Object o) { 254 if (this == o) return true; 255 if (o == null || getClass() != o.getClass()) return false; 256 257 NotificationChannelGroup that = (NotificationChannelGroup) o; 258 259 if (isBlocked() != that.isBlocked()) return false; 260 if (getId() != null ? !getId().equals(that.getId()) : that.getId() != null) return false; 261 if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) { 262 return false; 263 } 264 if (getDescription() != null ? !getDescription().equals(that.getDescription()) 265 : that.getDescription() != null) { 266 return false; 267 } 268 return getChannels() != null ? getChannels().equals(that.getChannels()) 269 : that.getChannels() == null; 270 } 271 272 @Override hashCode()273 public int hashCode() { 274 int result = getId() != null ? getId().hashCode() : 0; 275 result = 31 * result + (getName() != null ? getName().hashCode() : 0); 276 result = 31 * result + (getDescription() != null ? getDescription().hashCode() : 0); 277 result = 31 * result + (isBlocked() ? 1 : 0); 278 result = 31 * result + (getChannels() != null ? getChannels().hashCode() : 0); 279 return result; 280 } 281 282 @Override clone()283 public NotificationChannelGroup clone() { 284 NotificationChannelGroup cloned = new NotificationChannelGroup(getId(), getName()); 285 cloned.setDescription(getDescription()); 286 cloned.setBlocked(isBlocked()); 287 cloned.setChannels(getChannels()); 288 return cloned; 289 } 290 291 @Override toString()292 public String toString() { 293 return "NotificationChannelGroup{" 294 + "mId='" + mId + '\'' 295 + ", mName=" + mName 296 + ", mDescription=" + (!TextUtils.isEmpty(mDescription) ? "hasDescription " : "") 297 + ", mBlocked=" + mBlocked 298 + ", mChannels=" + mChannels 299 + '}'; 300 } 301 302 /** @hide */ writeToProto(ProtoOutputStream proto, long fieldId)303 public void writeToProto(ProtoOutputStream proto, long fieldId) { 304 final long token = proto.start(fieldId); 305 306 proto.write(NotificationChannelGroupProto.ID, mId); 307 proto.write(NotificationChannelGroupProto.NAME, mName.toString()); 308 proto.write(NotificationChannelGroupProto.DESCRIPTION, mDescription); 309 proto.write(NotificationChannelGroupProto.IS_BLOCKED, mBlocked); 310 for (NotificationChannel channel : mChannels) { 311 channel.writeToProto(proto, NotificationChannelGroupProto.CHANNELS); 312 } 313 314 proto.end(token); 315 } 316 } 317