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