• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.safetycenter.notifications;
18 
19 import static android.os.Build.VERSION_CODES.TIRAMISU;
20 
21 import static java.util.Objects.requireNonNull;
22 
23 import android.annotation.Nullable;
24 import android.app.NotificationChannel;
25 import android.app.NotificationChannelGroup;
26 import android.app.NotificationManager;
27 import android.content.Context;
28 import android.os.Binder;
29 import android.os.UserHandle;
30 import android.safetycenter.SafetySourceData;
31 import android.safetycenter.SafetySourceIssue;
32 import android.util.Log;
33 
34 import androidx.annotation.RequiresApi;
35 
36 import com.android.permission.util.UserUtils;
37 import com.android.safetycenter.resources.SafetyCenterResourcesContext;
38 
39 import java.util.List;
40 
41 /**
42  * Class responsible for creating and updating Safety Center's notification channels.
43  *
44  * @hide
45  */
46 @RequiresApi(TIRAMISU)
47 public final class SafetyCenterNotificationChannels {
48 
49     private static final String TAG = "SafetyCenterNC";
50 
51     private static final String CHANNEL_GROUP_ID = "safety_center_channels";
52     private static final String CHANNEL_ID_INFORMATION = "safety_center_information";
53     private static final String CHANNEL_ID_RECOMMENDATION = "safety_center_recommendation";
54     private static final String CHANNEL_ID_CRITICAL_WARNING = "safety_center_critical_warning";
55 
56     private final SafetyCenterResourcesContext mResourcesContext;
57 
SafetyCenterNotificationChannels( SafetyCenterResourcesContext safetyCenterResourceContext)58     public SafetyCenterNotificationChannels(
59             SafetyCenterResourcesContext safetyCenterResourceContext) {
60         mResourcesContext = safetyCenterResourceContext;
61     }
62 
63     /** Returns a {@link NotificationManager} which will send notifications to the given user. */
64     @Nullable
getNotificationManagerForUser( Context baseContext, UserHandle userHandle)65     static NotificationManager getNotificationManagerForUser(
66             Context baseContext, UserHandle userHandle) {
67         Context contextAsUser = getContextAsUser(baseContext, userHandle);
68         NotificationManager notificationManager =
69                 (contextAsUser != null)
70                         ? contextAsUser.getSystemService(NotificationManager.class)
71                         : null;
72         if (notificationManager == null) {
73             Log.w(TAG, "Could not retrieve NotificationManager for user " + userHandle);
74         }
75         return notificationManager;
76     }
77 
78     @Nullable
getContextAsUser(Context baseContext, UserHandle userHandle)79     static Context getContextAsUser(Context baseContext, UserHandle userHandle) {
80         // This call requires the INTERACT_ACROSS_USERS permission.
81         final long callingId = Binder.clearCallingIdentity();
82         try {
83             return baseContext.createContextAsUser(userHandle, 0);
84         } catch (RuntimeException e) {
85             Log.w(TAG, "Could not create Context as user " + userHandle, e);
86             return null;
87         } finally {
88             Binder.restoreCallingIdentity(callingId);
89         }
90     }
91 
92     /**
93      * Returns the ID of the appropriate {@link NotificationChannel} for a notification about the
94      * given {@code issue} after ensuring that channel has been created.
95      */
96     @Nullable
getCreatedChannelId(NotificationManager notificationManager, SafetySourceIssue issue)97     String getCreatedChannelId(NotificationManager notificationManager, SafetySourceIssue issue) {
98         try {
99             createAllChannelsWithoutCallingIdentity(notificationManager);
100         } catch (RuntimeException e) {
101             Log.w(TAG, "Unable to create notification channels", e);
102             return null;
103         }
104         return getChannelIdForIssue(issue);
105     }
106 
107     /**
108      * Creates all Safety Center {@link NotificationChannel}s instances and their group, for all
109      * current users, dropping any calling identity so those channels can be unblockable. Throws a
110      * {@link RuntimeException} if any channel is malformed and could not be created.
111      */
createAllChannelsForAllUsers(Context context)112     public void createAllChannelsForAllUsers(Context context) {
113         List<UserHandle> users = UserUtils.getUserHandles(context);
114         for (int i = 0; i < users.size(); i++) {
115             createAllChannelsForUser(context, users.get(i));
116         }
117     }
118 
119     /**
120      * Creates all Safety Center {@link NotificationChannel}s instances and their group for the
121      * given {@link UserHandle}, dropping any calling identity so those channels can be unblockable.
122      * Throws a {@link RuntimeException} if any channel is malformed and could not be created.
123      */
createAllChannelsForUser(Context context, UserHandle user)124     public void createAllChannelsForUser(Context context, UserHandle user) {
125         try {
126             NotificationManager notificationManager =
127                     requireNonNull(getNotificationManagerForUser(context, user));
128             createAllChannelsWithoutCallingIdentity(notificationManager);
129         } catch (RuntimeException e) {
130             Log.w(TAG, "Error creating notification channels for user " + user.getIdentifier(), e);
131         }
132     }
133 
134     @Nullable
getChannelIdForIssue(SafetySourceIssue issue)135     private String getChannelIdForIssue(SafetySourceIssue issue) {
136         switch (issue.getSeverityLevel()) {
137             case SafetySourceData.SEVERITY_LEVEL_INFORMATION:
138                 return CHANNEL_ID_INFORMATION;
139             case SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION:
140                 return CHANNEL_ID_RECOMMENDATION;
141             case SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING:
142                 return CHANNEL_ID_CRITICAL_WARNING;
143             default:
144                 Log.w(TAG, "No applicable notification channel for issue " + issue);
145                 return null;
146         }
147     }
148 
149     /**
150      * Creates all Safety Center {@link NotificationChannel}s instances and their group using the
151      * given {@link NotificationManager}, dropping any calling identity so those channels can be
152      * unblockable. Throws a {@link RuntimeException} if any channel is malformed and could not be
153      * created.
154      */
createAllChannelsWithoutCallingIdentity(NotificationManager notificationManager)155     private void createAllChannelsWithoutCallingIdentity(NotificationManager notificationManager) {
156         // Clearing calling identity to be able to make unblockable system notification channels
157         final long callingId = Binder.clearCallingIdentity();
158         try {
159             notificationManager.createNotificationChannelGroup(getChannelGroupDefinition());
160             notificationManager.createNotificationChannel(getInformationChannelDefinition());
161             notificationManager.createNotificationChannel(getRecommendationChannelDefinition());
162             notificationManager.createNotificationChannel(getCriticalWarningChannelDefinition());
163         } finally {
164             Binder.restoreCallingIdentity(callingId);
165         }
166     }
167 
getChannelGroupDefinition()168     private NotificationChannelGroup getChannelGroupDefinition() {
169         return new NotificationChannelGroup(
170                 CHANNEL_GROUP_ID, getString("notification_channel_group_name"));
171     }
172 
getInformationChannelDefinition()173     private NotificationChannel getInformationChannelDefinition() {
174         NotificationChannel channel =
175                 new NotificationChannel(
176                         CHANNEL_ID_INFORMATION,
177                         getString("notification_channel_name_information"),
178                         NotificationManager.IMPORTANCE_LOW);
179         channel.setGroup(CHANNEL_GROUP_ID);
180         channel.setBlockable(true);
181         return channel;
182     }
183 
getRecommendationChannelDefinition()184     private NotificationChannel getRecommendationChannelDefinition() {
185         NotificationChannel channel =
186                 new NotificationChannel(
187                         CHANNEL_ID_RECOMMENDATION,
188                         getString("notification_channel_name_recommendation"),
189                         NotificationManager.IMPORTANCE_DEFAULT);
190         channel.setGroup(CHANNEL_GROUP_ID);
191         channel.setBlockable(false);
192         return channel;
193     }
194 
getCriticalWarningChannelDefinition()195     private NotificationChannel getCriticalWarningChannelDefinition() {
196         NotificationChannel channel =
197                 new NotificationChannel(
198                         CHANNEL_ID_CRITICAL_WARNING,
199                         getString("notification_channel_name_critical_warning"),
200                         NotificationManager.IMPORTANCE_HIGH);
201         channel.setGroup(CHANNEL_GROUP_ID);
202         channel.setBlockable(false);
203         return channel;
204     }
205 
getString(String name)206     private String getString(String name) {
207         return mResourcesContext.getStringByName(name);
208     }
209 }
210