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