• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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 com.android.server.healthconnect.notifications;
18 
19 import android.annotation.Nullable;
20 import android.app.Notification;
21 import android.app.NotificationChannel;
22 import android.app.NotificationChannelGroup;
23 import android.app.NotificationManager;
24 import android.content.Context;
25 import android.os.Binder;
26 import android.os.UserHandle;
27 import android.util.Log;
28 import android.util.Slog;
29 
30 import java.util.Objects;
31 
32 /**
33  * Abstraction for the Health Connect NotificationSender
34  *
35  * @hide
36  */
37 public final class HealthConnectNotificationSender {
38 
39     private static final String TAG = "HealthConnectNotificationSender";
40 
41     private final Context mContext;
42     private final HealthConnectNotificationFactory mNotificationFactory;
43     private final int mFixedNotificationId;
44     private final String mNotificationTag;
45     private final String mChannelId;
46     private final String mChannelGroupId;
47     private final String mChannelNameResource;
48     private final String mChannelGroupNameResource;
49     private final boolean mIsEnabled;
50 
HealthConnectNotificationSender(Builder builder)51     public HealthConnectNotificationSender(Builder builder) {
52         if (builder.mContext == null
53                 || builder.mNotificationFactory == null
54                 || builder.mNotificationTag == null
55                 || builder.mChannelId == null
56                 || builder.mChannelGroupId == null
57                 || builder.mChannelNameResource == null
58                 || builder.mChannelGroupNameResource == null) {
59             throw new IllegalArgumentException("Values cannot be null");
60         }
61         this.mContext = builder.mContext;
62         this.mNotificationFactory = builder.mNotificationFactory;
63         this.mFixedNotificationId = builder.mFixedNotificationId;
64         this.mNotificationTag = builder.mNotificationTag;
65         this.mChannelId = builder.mChannelId;
66         this.mChannelGroupId = builder.mChannelGroupId;
67         this.mChannelNameResource = builder.mChannelNameResource;
68         this.mChannelGroupNameResource = builder.mChannelGroupNameResource;
69         this.mIsEnabled = builder.mIsEnabled;
70     }
71 
72     public static final class Builder {
73 
74         @Nullable private Context mContext;
75         @Nullable private HealthConnectNotificationFactory mNotificationFactory;
76         private int mFixedNotificationId;
77         @Nullable private String mNotificationTag;
78         @Nullable private String mChannelId;
79         @Nullable private String mChannelGroupId;
80         @Nullable private String mChannelNameResource;
81         @Nullable private String mChannelGroupNameResource;
82         private boolean mIsEnabled;
83 
84         /** provide notification sender with context */
setContext(Context context)85         public Builder setContext(Context context) {
86             this.mContext = context;
87             return this;
88         }
89 
90         /**
91          * set the current status of the notification sender e.g. may want to only enable
92          * notifications if a specific Flag is enabled
93          */
setIsEnabled(boolean isEnabled)94         public Builder setIsEnabled(boolean isEnabled) {
95             this.mIsEnabled = isEnabled;
96             return this;
97         }
98 
99         /** provide notification sender with notification factory */
setNotificationFactory( HealthConnectNotificationFactory notificationFactory)100         public Builder setNotificationFactory(
101                 HealthConnectNotificationFactory notificationFactory) {
102             this.mNotificationFactory = notificationFactory;
103             return this;
104         }
105 
106         /** set notification ID */
setFixedNotificationId(int fixedNotificationId)107         public Builder setFixedNotificationId(int fixedNotificationId) {
108             this.mFixedNotificationId = fixedNotificationId;
109             return this;
110         }
111 
112         /** set the identifying tag for notifications */
setNotificationTag(String notificationTag)113         public Builder setNotificationTag(String notificationTag) {
114             this.mNotificationTag = notificationTag;
115             return this;
116         }
117 
118         /** set the notification channel ID */
setChannelId(String channelId)119         public Builder setChannelId(String channelId) {
120             this.mChannelId = channelId;
121             return this;
122         }
123 
124         /** set the notification channel group ID */
setChannelGroupId(String channelGroupId)125         public Builder setChannelGroupId(String channelGroupId) {
126             this.mChannelGroupId = channelGroupId;
127             return this;
128         }
129 
130         /** set the name of the notification channel */
setChannelNameResource(String channelNameResource)131         public Builder setChannelNameResource(String channelNameResource) {
132             this.mChannelNameResource = channelNameResource;
133             return this;
134         }
135 
136         /** set the name of the notification channel group */
setChannelGroupNameResource(String channelGroupNameResource)137         public Builder setChannelGroupNameResource(String channelGroupNameResource) {
138             this.mChannelGroupNameResource = channelGroupNameResource;
139             return this;
140         }
141 
142         /** build the notification sender */
build()143         public HealthConnectNotificationSender build() {
144             if (this.mChannelGroupId == null
145                     || this.mChannelId == null
146                     || this.mNotificationTag == null
147                     || this.mNotificationFactory == null
148                     || this.mContext == null
149                     || this.mChannelNameResource == null
150                     || this.mChannelGroupNameResource == null) {
151                 throw new IllegalArgumentException("Cannot have null parameter.");
152             }
153             return new HealthConnectNotificationSender(this);
154         }
155     }
156 
157     /** Creates a notification determined by the passed-in type and displays it to the user. */
sendNotificationAsUser( @ealthConnectNotificationType int notificationType, UserHandle userHandle)158     public void sendNotificationAsUser(
159             @HealthConnectNotificationType int notificationType, UserHandle userHandle) {
160 
161         Slog.i(TAG, "Sending notification as user.");
162 
163         if (!mIsEnabled) {
164             Slog.i(TAG, "Notifications have been disabled.");
165             return;
166         }
167 
168         createNotificationChannel(userHandle);
169         Notification notification = mNotificationFactory.createNotification(notificationType);
170         if (notification == null) return;
171         NotificationManager notificationManager = getNotificationManagerForUser(userHandle);
172         notifyFromSystem(notificationManager, notification);
173     }
174 
175     /** Cancels all Health Connect notifications on this channel. */
clearNotificationsAsUser(UserHandle userHandle)176     public void clearNotificationsAsUser(UserHandle userHandle) {
177         if (!mIsEnabled) return;
178         NotificationManager notificationManager = getNotificationManagerForUser(userHandle);
179         cancelFromSystem(notificationManager);
180     }
181 
182     /** Returns a {@link NotificationManager} which will send notifications to the given user. */
getNotificationManagerForUser(UserHandle userHandle)183     private NotificationManager getNotificationManagerForUser(UserHandle userHandle) {
184         Context contextAsUser = mContext.createContextAsUser(userHandle, 0);
185         return Objects.requireNonNull(contextAsUser.getSystemService(NotificationManager.class));
186     }
187 
notifyFromSystem( NotificationManager notificationManager, Notification notification)188     private void notifyFromSystem(
189             NotificationManager notificationManager, Notification notification) {
190         final long callingId = Binder.clearCallingIdentity();
191         try {
192             notificationManager.notify(mNotificationTag, mFixedNotificationId, notification);
193         } catch (Throwable e) {
194             Log.w(TAG, "Unable to send system notification", e);
195         } finally {
196             Binder.restoreCallingIdentity(callingId);
197         }
198     }
199 
cancelFromSystem(NotificationManager notificationManager)200     private void cancelFromSystem(NotificationManager notificationManager) {
201         final long callingId = Binder.clearCallingIdentity();
202         try {
203             notificationManager.cancel(mNotificationTag, mFixedNotificationId);
204         } catch (Throwable e) {
205             Log.w(TAG, "Unable to cancel system notification", e);
206         } finally {
207             Binder.restoreCallingIdentity(callingId);
208         }
209     }
210 
createNotificationChannel(UserHandle userHandle)211     private void createNotificationChannel(UserHandle userHandle) {
212         CharSequence channelGroupName =
213                 mNotificationFactory.getStringResource(mChannelGroupNameResource);
214         CharSequence channelName = mNotificationFactory.getStringResource(mChannelNameResource);
215 
216         NotificationChannelGroup group =
217                 new NotificationChannelGroup(mChannelGroupId, channelGroupName);
218 
219         int importance = NotificationManager.IMPORTANCE_HIGH;
220         NotificationChannel notificationChannel =
221                 new NotificationChannel(mChannelId, channelName, importance);
222         notificationChannel.setGroup(mChannelGroupId);
223         notificationChannel.setBlockable(false);
224 
225         final long callingId = Binder.clearCallingIdentity();
226 
227         NotificationManager notificationManager = getNotificationManagerForUser(userHandle);
228 
229         try {
230             notificationManager.createNotificationChannelGroup(group);
231             notificationManager.createNotificationChannel(notificationChannel);
232         } catch (Throwable e) {
233             Log.w(TAG, "Unable to create notification channel", e);
234         } finally {
235             Binder.restoreCallingIdentity(callingId);
236         }
237     }
238 
239     /** @hide */
240     public @interface HealthConnectNotificationType {}
241 }
242