1 /*
2  * Copyright 2020 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 
17 package androidx.core.app;
18 
19 import android.app.NotificationChannel;
20 import android.app.NotificationChannelGroup;
21 import android.content.Intent;
22 import android.os.Build;
23 
24 import androidx.annotation.RequiresApi;
25 import androidx.core.util.Preconditions;
26 
27 import org.jspecify.annotations.NonNull;
28 import org.jspecify.annotations.Nullable;
29 
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.List;
33 
34 /**
35  * A grouping of related notification channels. e.g., channels that all belong to a single account.
36  *
37  * Setters return {@code this} to allow chaining.
38  *
39  * This class doesn't do anything on older SDKs which don't support Notification Channels.
40  */
41 public class NotificationChannelGroupCompat {
42     // These fields are settable through the builder
43     final String mId;
44     CharSequence mName;
45     String mDescription;
46 
47     // These fields are read-only
48     private boolean mBlocked;
49     private List<NotificationChannelCompat> mChannels = Collections.emptyList();
50 
51     /**
52      * Builder class for {@link NotificationChannelGroupCompat} objects.
53      */
54     public static class Builder {
55         final NotificationChannelGroupCompat mGroup;
56 
57         /**
58          * Creates a notification channel group.
59          *
60          * @param id The id of the group. Must be unique per package.
61          *           The value may be truncated if it is too long.
62          */
Builder(@onNull String id)63         public Builder(@NonNull String id) {
64             mGroup = new NotificationChannelGroupCompat(id);
65         }
66 
67         /**
68          * Sets the user visible name of this group.
69          *
70          * You can rename this group when the system locale changes by listening for the
71          * {@link Intent#ACTION_LOCALE_CHANGED} broadcast.
72          *
73          * <p>The recommended maximum length is 40 characters; the value may be truncated if it
74          * is too long.
75          */
setName(@ullable CharSequence name)76         public @NonNull Builder setName(@Nullable CharSequence name) {
77             mGroup.mName = name;
78             return this;
79         }
80 
81         /**
82          * Sets the user visible description of this group.
83          *
84          * <p>The recommended maximum length is 300 characters; the value may be truncated if it
85          * is too
86          * long.
87          */
setDescription(@ullable String description)88         public @NonNull Builder setDescription(@Nullable String description) {
89             mGroup.mDescription = description;
90             return this;
91         }
92 
93         /**
94          * Creates a {@link NotificationChannelGroupCompat} instance.
95          */
build()96         public @NonNull NotificationChannelGroupCompat build() {
97             return mGroup;
98         }
99     }
100 
NotificationChannelGroupCompat(@onNull String id)101     NotificationChannelGroupCompat(@NonNull String id) {
102         mId = Preconditions.checkNotNull(id);
103     }
104 
105     @RequiresApi(28)
NotificationChannelGroupCompat(@onNull NotificationChannelGroup group)106     NotificationChannelGroupCompat(@NonNull NotificationChannelGroup group) {
107         this(group, Collections.<NotificationChannel>emptyList());
108     }
109 
110     @RequiresApi(26)
NotificationChannelGroupCompat(@onNull NotificationChannelGroup group, @NonNull List<NotificationChannel> allChannels)111     NotificationChannelGroupCompat(@NonNull NotificationChannelGroup group,
112             @NonNull List<NotificationChannel> allChannels) {
113         this(Api26Impl.getId(group));
114         // Populate all builder-editable fields
115         mName = Api26Impl.getName(group);
116         if (Build.VERSION.SDK_INT >= 28) {
117             mDescription = Api28Impl.getDescription(group);
118         }
119         // Populate all read-only fields
120         if (Build.VERSION.SDK_INT >= 28) {
121             mBlocked = Api28Impl.isBlocked(group);
122             mChannels = getChannelsCompat(Api26Impl.getChannels(group));
123         } else {
124             // On API 26 and 27, the NotificationChannelGroup.getChannels() method was broken,
125             // so we collect this information from the full list of channels at construction.
126             mChannels = getChannelsCompat(allChannels);
127         }
128     }
129 
130     @RequiresApi(26)
getChannelsCompat(List<NotificationChannel> channels)131     private List<NotificationChannelCompat> getChannelsCompat(List<NotificationChannel> channels) {
132         List<NotificationChannelCompat> channelsCompat = new ArrayList<>();
133         for (NotificationChannel channel : channels) {
134             if (mId.equals(Api26Impl.getGroup(channel))) {
135                 channelsCompat.add(new NotificationChannelCompat(channel));
136             }
137         }
138         return channelsCompat;
139     }
140 
141     /**
142      * Gets the platform notification channel group object.
143      *
144      * Returns {@code null} on older SDKs which don't support Notification Channels.
145      */
getNotificationChannelGroup()146     NotificationChannelGroup getNotificationChannelGroup() {
147         if (Build.VERSION.SDK_INT < 26) {
148             return null;
149         }
150         NotificationChannelGroup group = Api26Impl.createNotificationChannelGroup(mId, mName);
151         if (Build.VERSION.SDK_INT >= 28) {
152             Api28Impl.setDescription(group, mDescription);
153         }
154         return group;
155     }
156 
157     /**
158      * Creates a {@link Builder} instance with all the writeable property values of this instance.
159      */
toBuilder()160     public @NonNull Builder toBuilder() {
161         return new Builder(mId)
162                 .setName(mName)
163                 .setDescription(mDescription);
164     }
165 
166     /**
167      * Gets the id of the group.
168      */
getId()169     public @NonNull String getId() {
170         return mId;
171     }
172 
173     /**
174      * Gets the user visible name of the group.
175      */
getName()176     public @Nullable CharSequence getName() {
177         return mName;
178     }
179 
180     /**
181      * Gets the user visible description of the group.
182      */
getDescription()183     public @Nullable String getDescription() {
184         return mDescription;
185     }
186 
187     /**
188      * Returns whether or not notifications posted to {@link NotificationChannelCompat} belonging
189      * to this group are blocked. This value is independent of
190      * {@link NotificationManagerCompat#areNotificationsEnabled()} and
191      * {@link NotificationChannelCompat#getImportance()}.
192      *
193      * <p>This value is always {@code false} before {@link Build.VERSION_CODES#P}
194      *
195      * <p>This is a read-only property which is only valid on instances fetched from the
196      * {@link NotificationManagerCompat}.
197      */
isBlocked()198     public boolean isBlocked() {
199         return mBlocked;
200     }
201 
202     /**
203      * Returns the list of channels that belong to this group.
204      *
205      * <p>This is a read-only property which is only valid on instances fetched from the
206      * {@link NotificationManagerCompat}.
207      */
getChannels()208     public @NonNull List<NotificationChannelCompat> getChannels() {
209         return mChannels;
210     }
211 
212     /**
213      * A class for wrapping calls to {@link NotificationChannelGroupCompat} methods which
214      * were added in API 26; these calls must be wrapped to avoid performance issues.
215      * See the UnsafeNewApiCall lint rule for more details.
216      */
217     @RequiresApi(26)
218     static class Api26Impl {
Api26Impl()219         private Api26Impl() { }
220 
createNotificationChannelGroup(String id, CharSequence name)221         static NotificationChannelGroup createNotificationChannelGroup(String id,
222                 CharSequence name) {
223             return new NotificationChannelGroup(id, name);
224         }
225 
getId(NotificationChannelGroup notificationChannelGroup)226         static String getId(NotificationChannelGroup notificationChannelGroup) {
227             return notificationChannelGroup.getId();
228         }
229 
getName(NotificationChannelGroup notificationChannelGroup)230         static CharSequence getName(NotificationChannelGroup notificationChannelGroup) {
231             return notificationChannelGroup.getName();
232         }
233 
getChannels( NotificationChannelGroup notificationChannelGroup)234         static List<NotificationChannel> getChannels(
235                 NotificationChannelGroup notificationChannelGroup) {
236             return notificationChannelGroup.getChannels();
237         }
238 
getGroup(NotificationChannel notificationChannel)239         static String getGroup(NotificationChannel notificationChannel) {
240             return notificationChannel.getGroup();
241         }
242     }
243 
244     /**
245      * A class for wrapping calls to {@link NotificationChannelGroupCompat} methods which
246      * were added in API 28; these calls must be wrapped to avoid performance issues.
247      * See the UnsafeNewApiCall lint rule for more details.
248      */
249     @RequiresApi(28)
250     static class Api28Impl {
Api28Impl()251         private Api28Impl() { }
252 
isBlocked(NotificationChannelGroup notificationChannelGroup)253         static boolean isBlocked(NotificationChannelGroup notificationChannelGroup) {
254             return notificationChannelGroup.isBlocked();
255         }
256 
getDescription(NotificationChannelGroup notificationChannelGroup)257         static String getDescription(NotificationChannelGroup notificationChannelGroup) {
258             return notificationChannelGroup.getDescription();
259         }
260 
setDescription(NotificationChannelGroup notificationChannelGroup, String description)261         static void setDescription(NotificationChannelGroup notificationChannelGroup,
262                 String description) {
263             notificationChannelGroup.setDescription(description);
264         }
265     }
266 }
267