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.Nullable; 19 import android.annotation.SystemApi; 20 import android.annotation.TestApi; 21 import android.compat.annotation.UnsupportedAppUsage; 22 import android.content.Intent; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 import android.text.TextUtils; 26 import android.util.TypedXmlPullParser; 27 import android.util.TypedXmlSerializer; 28 import android.util.proto.ProtoOutputStream; 29 30 import org.json.JSONException; 31 import org.json.JSONObject; 32 33 import java.io.IOException; 34 import java.util.ArrayList; 35 import java.util.List; 36 import java.util.Objects; 37 38 /** 39 * A grouping of related notification channels. e.g., channels that all belong to a single account. 40 */ 41 public final class NotificationChannelGroup implements Parcelable { 42 43 /** 44 * The maximum length for text fields in a NotificationChannelGroup. Fields will be truncated at 45 * this limit. 46 */ 47 private static final int MAX_TEXT_LENGTH = 1000; 48 49 private static final String TAG_GROUP = "channelGroup"; 50 private static final String ATT_NAME = "name"; 51 private static final String ATT_DESC = "desc"; 52 private static final String ATT_ID = "id"; 53 private static final String ATT_BLOCKED = "blocked"; 54 private static final String ATT_USER_LOCKED = "locked"; 55 56 /** 57 * @hide 58 */ 59 public static final int USER_LOCKED_BLOCKED_STATE = 0x00000001; 60 61 /** 62 * @see #getId() 63 */ 64 @UnsupportedAppUsage 65 private final String mId; 66 private CharSequence mName; 67 private String mDescription; 68 private boolean mBlocked; 69 private List<NotificationChannel> mChannels = new ArrayList<>(); 70 // Bitwise representation of fields that have been changed by the user 71 private int mUserLockedFields; 72 73 /** 74 * Creates a notification channel group. 75 * 76 * @param id The id of the group. Must be unique per package. the value may be truncated if 77 * it is too long. 78 * @param name The user visible name of the group. You can rename this group when the system 79 * locale changes by listening for the {@link Intent#ACTION_LOCALE_CHANGED} 80 * broadcast. <p>The recommended maximum length is 40 characters; the value may be 81 * truncated if it is too long. 82 */ NotificationChannelGroup(String id, CharSequence name)83 public NotificationChannelGroup(String id, CharSequence name) { 84 this.mId = getTrimmedString(id); 85 this.mName = name != null ? getTrimmedString(name.toString()) : null; 86 } 87 88 /** 89 * @hide 90 */ NotificationChannelGroup(Parcel in)91 protected NotificationChannelGroup(Parcel in) { 92 if (in.readByte() != 0) { 93 mId = in.readString(); 94 } else { 95 mId = null; 96 } 97 mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 98 if (in.readByte() != 0) { 99 mDescription = in.readString(); 100 } else { 101 mDescription = null; 102 } 103 in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader()); 104 mBlocked = in.readBoolean(); 105 mUserLockedFields = in.readInt(); 106 } 107 getTrimmedString(String input)108 private String getTrimmedString(String input) { 109 if (input != null && input.length() > MAX_TEXT_LENGTH) { 110 return input.substring(0, MAX_TEXT_LENGTH); 111 } 112 return input; 113 } 114 115 @Override writeToParcel(Parcel dest, int flags)116 public void writeToParcel(Parcel dest, int flags) { 117 if (mId != null) { 118 dest.writeByte((byte) 1); 119 dest.writeString(mId); 120 } else { 121 dest.writeByte((byte) 0); 122 } 123 TextUtils.writeToParcel(mName, dest, flags); 124 if (mDescription != null) { 125 dest.writeByte((byte) 1); 126 dest.writeString(mDescription); 127 } else { 128 dest.writeByte((byte) 0); 129 } 130 dest.writeParcelableList(mChannels, flags); 131 dest.writeBoolean(mBlocked); 132 dest.writeInt(mUserLockedFields); 133 } 134 135 /** 136 * Returns the id of this group. 137 */ getId()138 public String getId() { 139 return mId; 140 } 141 142 /** 143 * Returns the user visible name of this group. 144 */ getName()145 public CharSequence getName() { 146 return mName; 147 } 148 149 /** 150 * Returns the user visible description of this group. 151 */ getDescription()152 public String getDescription() { 153 return mDescription; 154 } 155 156 /** 157 * Returns the list of channels that belong to this group 158 */ getChannels()159 public List<NotificationChannel> getChannels() { 160 return mChannels; 161 } 162 163 /** 164 * Returns whether or not notifications posted to {@link NotificationChannel channels} belonging 165 * to this group are blocked. This value is independent of 166 * {@link NotificationManager#areNotificationsEnabled()} and 167 * {@link NotificationChannel#getImportance()}. 168 */ isBlocked()169 public boolean isBlocked() { 170 return mBlocked; 171 } 172 173 /** 174 * Sets the user visible description of this group. 175 * 176 * <p>The recommended maximum length is 300 characters; the value may be truncated if it is too 177 * long. 178 */ setDescription(String description)179 public void setDescription(String description) { 180 mDescription = getTrimmedString(description); 181 } 182 183 /** 184 * @hide 185 */ 186 @TestApi setBlocked(boolean blocked)187 public void setBlocked(boolean blocked) { 188 mBlocked = blocked; 189 } 190 191 /** 192 * @hide 193 */ addChannel(NotificationChannel channel)194 public void addChannel(NotificationChannel channel) { 195 mChannels.add(channel); 196 } 197 198 /** 199 * @hide 200 */ setChannels(List<NotificationChannel> channels)201 public void setChannels(List<NotificationChannel> channels) { 202 mChannels = channels; 203 } 204 205 /** 206 * @hide 207 */ 208 @TestApi lockFields(int field)209 public void lockFields(int field) { 210 mUserLockedFields |= field; 211 } 212 213 /** 214 * @hide 215 */ unlockFields(int field)216 public void unlockFields(int field) { 217 mUserLockedFields &= ~field; 218 } 219 220 /** 221 * @hide 222 */ 223 @TestApi getUserLockedFields()224 public int getUserLockedFields() { 225 return mUserLockedFields; 226 } 227 228 /** 229 * @hide 230 */ populateFromXml(TypedXmlPullParser parser)231 public void populateFromXml(TypedXmlPullParser parser) { 232 // Name, id, and importance are set in the constructor. 233 setDescription(parser.getAttributeValue(null, ATT_DESC)); 234 setBlocked(parser.getAttributeBoolean(null, ATT_BLOCKED, false)); 235 } 236 237 /** 238 * @hide 239 */ writeXml(TypedXmlSerializer out)240 public void writeXml(TypedXmlSerializer out) throws IOException { 241 out.startTag(null, TAG_GROUP); 242 243 out.attribute(null, ATT_ID, getId()); 244 if (getName() != null) { 245 out.attribute(null, ATT_NAME, getName().toString()); 246 } 247 if (getDescription() != null) { 248 out.attribute(null, ATT_DESC, getDescription().toString()); 249 } 250 out.attributeBoolean(null, ATT_BLOCKED, isBlocked()); 251 out.attributeInt(null, ATT_USER_LOCKED, mUserLockedFields); 252 253 out.endTag(null, TAG_GROUP); 254 } 255 256 /** 257 * @hide 258 */ 259 @SystemApi toJson()260 public JSONObject toJson() throws JSONException { 261 JSONObject record = new JSONObject(); 262 record.put(ATT_ID, getId()); 263 record.put(ATT_NAME, getName()); 264 record.put(ATT_DESC, getDescription()); 265 record.put(ATT_BLOCKED, isBlocked()); 266 record.put(ATT_USER_LOCKED, mUserLockedFields); 267 return record; 268 } 269 270 public static final @android.annotation.NonNull Creator<NotificationChannelGroup> CREATOR = 271 new Creator<NotificationChannelGroup>() { 272 @Override 273 public NotificationChannelGroup createFromParcel(Parcel in) { 274 return new NotificationChannelGroup(in); 275 } 276 277 @Override 278 public NotificationChannelGroup[] newArray(int size) { 279 return new NotificationChannelGroup[size]; 280 } 281 }; 282 283 @Override describeContents()284 public int describeContents() { 285 return 0; 286 } 287 288 @Override equals(@ullable Object o)289 public boolean equals(@Nullable Object o) { 290 if (this == o) return true; 291 if (o == null || getClass() != o.getClass()) return false; 292 NotificationChannelGroup that = (NotificationChannelGroup) o; 293 return isBlocked() == that.isBlocked() && 294 mUserLockedFields == that.mUserLockedFields && 295 Objects.equals(getId(), that.getId()) && 296 Objects.equals(getName(), that.getName()) && 297 Objects.equals(getDescription(), that.getDescription()) && 298 Objects.equals(getChannels(), that.getChannels()); 299 } 300 301 @Override hashCode()302 public int hashCode() { 303 return Objects.hash(getId(), getName(), getDescription(), isBlocked(), getChannels(), 304 mUserLockedFields); 305 } 306 307 @Override clone()308 public NotificationChannelGroup clone() { 309 NotificationChannelGroup cloned = new NotificationChannelGroup(getId(), getName()); 310 cloned.setDescription(getDescription()); 311 cloned.setBlocked(isBlocked()); 312 cloned.setChannels(getChannels()); 313 cloned.lockFields(mUserLockedFields); 314 return cloned; 315 } 316 317 @Override toString()318 public String toString() { 319 return "NotificationChannelGroup{" 320 + "mId='" + mId + '\'' 321 + ", mName=" + mName 322 + ", mDescription=" + (!TextUtils.isEmpty(mDescription) ? "hasDescription " : "") 323 + ", mBlocked=" + mBlocked 324 + ", mChannels=" + mChannels 325 + ", mUserLockedFields=" + mUserLockedFields 326 + '}'; 327 } 328 329 /** @hide */ dumpDebug(ProtoOutputStream proto, long fieldId)330 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 331 final long token = proto.start(fieldId); 332 333 proto.write(NotificationChannelGroupProto.ID, mId); 334 proto.write(NotificationChannelGroupProto.NAME, mName.toString()); 335 proto.write(NotificationChannelGroupProto.DESCRIPTION, mDescription); 336 proto.write(NotificationChannelGroupProto.IS_BLOCKED, mBlocked); 337 for (NotificationChannel channel : mChannels) { 338 channel.dumpDebug(proto, NotificationChannelGroupProto.CHANNELS); 339 } 340 proto.end(token); 341 } 342 } 343