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