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.server.display.notifications; 18 19 import static android.app.Notification.COLOR_DEFAULT; 20 21 import static com.android.internal.notification.SystemNotificationChannels.ALERTS; 22 23 import android.annotation.Nullable; 24 import android.annotation.SuppressLint; 25 import android.app.Notification; 26 import android.app.NotificationManager; 27 import android.content.Context; 28 import android.content.res.Resources; 29 import android.os.UserHandle; 30 import android.util.Slog; 31 32 import com.android.internal.R; 33 import com.android.internal.annotations.VisibleForTesting; 34 import com.android.server.display.ExternalDisplayStatsService; 35 import com.android.server.display.feature.DisplayManagerFlags; 36 37 /** 38 * Manages notifications for {@link com.android.server.display.DisplayManagerService}. 39 */ 40 public class DisplayNotificationManager implements ConnectedDisplayUsbErrorsDetector.Listener { 41 /** Dependency injection interface for {@link DisplayNotificationManager} */ 42 public interface Injector { 43 /** Get {@link NotificationManager} service or null if not available. */ 44 @Nullable getNotificationManager()45 NotificationManager getNotificationManager(); 46 47 /** Get {@link ConnectedDisplayUsbErrorsDetector} or null if not available. */ 48 @Nullable getUsbErrorsDetector()49 ConnectedDisplayUsbErrorsDetector getUsbErrorsDetector(); 50 51 /** Get {@link com.android.server.display.ExternalDisplayStatsService} or null */ 52 @Nullable getExternalDisplayStatsService()53 ExternalDisplayStatsService getExternalDisplayStatsService(); 54 } 55 56 private static final String TAG = "DisplayNotificationManager"; 57 private static final String NOTIFICATION_GROUP_NAME = TAG; 58 private static final String DISPLAY_NOTIFICATION_TAG = TAG; 59 private static final int DISPLAY_NOTIFICATION_ID = 1; 60 private static final long NOTIFICATION_TIMEOUT_MILLISEC = 30000L; 61 62 private final Injector mInjector; 63 private final Context mContext; 64 private final boolean mConnectedDisplayErrorHandlingEnabled; 65 private NotificationManager mNotificationManager; 66 private ConnectedDisplayUsbErrorsDetector mConnectedDisplayUsbErrorsDetector; 67 68 private final ExternalDisplayStatsService mExternalDisplayStatsService; 69 DisplayNotificationManager(final DisplayManagerFlags flags, final Context context, final ExternalDisplayStatsService statsService)70 public DisplayNotificationManager(final DisplayManagerFlags flags, final Context context, 71 final ExternalDisplayStatsService statsService) { 72 this(flags, context, new Injector() { 73 @Nullable 74 @Override 75 public NotificationManager getNotificationManager() { 76 return context.getSystemService(NotificationManager.class); 77 } 78 79 @Nullable 80 @Override 81 public ConnectedDisplayUsbErrorsDetector getUsbErrorsDetector() { 82 return new ConnectedDisplayUsbErrorsDetector(flags, context); 83 } 84 85 @Nullable 86 @Override 87 public ExternalDisplayStatsService getExternalDisplayStatsService() { 88 return statsService; 89 } 90 }); 91 } 92 93 @VisibleForTesting DisplayNotificationManager(final DisplayManagerFlags flags, final Context context, final Injector injector)94 DisplayNotificationManager(final DisplayManagerFlags flags, final Context context, 95 final Injector injector) { 96 mConnectedDisplayErrorHandlingEnabled = flags.isConnectedDisplayErrorHandlingEnabled(); 97 mContext = context; 98 mInjector = injector; 99 mExternalDisplayStatsService = injector.getExternalDisplayStatsService(); 100 } 101 102 /** 103 * Initialize services, which may be not yet published during boot. 104 * see {@link android.os.ServiceManager.ServiceNotFoundException}. 105 */ onBootCompleted()106 public void onBootCompleted() { 107 mNotificationManager = mInjector.getNotificationManager(); 108 if (mNotificationManager == null) { 109 Slog.e(TAG, "onBootCompleted: NotificationManager is null"); 110 return; 111 } 112 113 mConnectedDisplayUsbErrorsDetector = mInjector.getUsbErrorsDetector(); 114 if (mConnectedDisplayUsbErrorsDetector != null) { 115 mConnectedDisplayUsbErrorsDetector.registerListener(this); 116 } 117 } 118 119 /** 120 * Display error notification upon DisplayPort link training failure. 121 */ 122 @Override onDisplayPortLinkTrainingFailure()123 public void onDisplayPortLinkTrainingFailure() { 124 if (!mConnectedDisplayErrorHandlingEnabled) { 125 Slog.d(TAG, "onDisplayPortLinkTrainingFailure:" 126 + " mConnectedDisplayErrorHandlingEnabled is false"); 127 return; 128 } 129 130 mExternalDisplayStatsService.onDisplayPortLinkTrainingFailure(); 131 132 sendErrorNotification(createErrorNotification( 133 R.string.connected_display_unavailable_notification_title, 134 R.string.connected_display_unavailable_notification_content, 135 R.drawable.usb_cable_unknown_issue)); 136 } 137 138 /** 139 * Display error notification upon cable not capable of DisplayPort connected to a device 140 * capable of DisplayPort. 141 */ 142 @Override onCableNotCapableDisplayPort()143 public void onCableNotCapableDisplayPort() { 144 if (!mConnectedDisplayErrorHandlingEnabled) { 145 Slog.d(TAG, "onCableNotCapableDisplayPort:" 146 + " mConnectedDisplayErrorHandlingEnabled is false"); 147 return; 148 } 149 150 mExternalDisplayStatsService.onCableNotCapableDisplayPort(); 151 152 sendErrorNotification(createErrorNotification( 153 R.string.connected_display_unavailable_notification_title, 154 R.string.connected_display_unavailable_notification_content, 155 R.drawable.usb_cable_unknown_issue)); 156 } 157 158 /** 159 * Send notification about hotplug connection error. 160 */ onHotplugConnectionError()161 public void onHotplugConnectionError() { 162 if (!mConnectedDisplayErrorHandlingEnabled) { 163 Slog.d(TAG, "onHotplugConnectionError:" 164 + " mConnectedDisplayErrorHandlingEnabled is false"); 165 return; 166 } 167 168 mExternalDisplayStatsService.onHotplugConnectionError(); 169 170 sendErrorNotification(createErrorNotification( 171 R.string.connected_display_unavailable_notification_title, 172 R.string.connected_display_unavailable_notification_content, 173 R.drawable.usb_cable_unknown_issue)); 174 } 175 176 /** 177 * Send notification about high temperature preventing usage of the external display. 178 */ onHighTemperatureExternalDisplayNotAllowed()179 public void onHighTemperatureExternalDisplayNotAllowed() { 180 if (!mConnectedDisplayErrorHandlingEnabled) { 181 Slog.d(TAG, "onHighTemperatureExternalDisplayNotAllowed:" 182 + " mConnectedDisplayErrorHandlingEnabled is false"); 183 return; 184 } 185 186 sendErrorNotification(createErrorNotification( 187 R.string.connected_display_unavailable_notification_title, 188 R.string.connected_display_thermally_unavailable_notification_content, 189 R.drawable.ic_thermostat_notification)); 190 } 191 192 /** 193 * Cancel sent notifications. 194 */ cancelNotifications()195 public void cancelNotifications() { 196 if (mNotificationManager == null) { 197 Slog.e(TAG, "Can't cancelNotifications: NotificationManager is null"); 198 return; 199 } 200 201 mNotificationManager.cancelAsUser(DISPLAY_NOTIFICATION_TAG, DISPLAY_NOTIFICATION_ID, 202 UserHandle.CURRENT); 203 } 204 205 /** 206 * Send generic error notification. 207 */ 208 @SuppressLint("AndroidFrameworkRequiresPermission") sendErrorNotification(final Notification notification)209 private void sendErrorNotification(final Notification notification) { 210 if (mNotificationManager == null) { 211 Slog.e(TAG, "Can't sendErrorNotification: NotificationManager is null"); 212 return; 213 } 214 215 mNotificationManager.notifyAsUser(DISPLAY_NOTIFICATION_TAG, DISPLAY_NOTIFICATION_ID, 216 notification, UserHandle.CURRENT); 217 } 218 219 /** 220 * @return a newly built notification about an issue with connected display. 221 */ createErrorNotification(final int titleId, final int messageId, final int icon)222 private Notification createErrorNotification(final int titleId, final int messageId, 223 final int icon) { 224 final Resources resources = mContext.getResources(); 225 final CharSequence title = resources.getText(titleId); 226 final CharSequence message = resources.getText(messageId); 227 228 int color = COLOR_DEFAULT; 229 try (var attrs = mContext.obtainStyledAttributes(new int[]{R.attr.colorError})) { 230 color = attrs.getColor(0, color); 231 } catch (Resources.NotFoundException e) { 232 Slog.e(TAG, "colorError attribute is not found: " + e.getMessage()); 233 } 234 235 return new Notification.Builder(mContext, ALERTS) 236 .setGroup(NOTIFICATION_GROUP_NAME) 237 .setSmallIcon(icon) 238 .setWhen(0) 239 .setTimeoutAfter(NOTIFICATION_TIMEOUT_MILLISEC) 240 .setOngoing(false) 241 .setTicker(title) 242 .setColor(color) 243 .setContentTitle(title) 244 .setContentText(message) 245 .setVisibility(Notification.VISIBILITY_PUBLIC) 246 .setCategory(Notification.CATEGORY_ERROR) 247 .build(); 248 } 249 } 250