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