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