• 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.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