summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Oleg Blinnikov <olb@google.com> 2023-10-02 12:25:46 +0000
committer Oleg Blinnikov <olb@google.com> 2023-10-12 17:15:10 +0000
commit111eefb06167f50d438e1f293decd6a64062d8cc (patch)
tree229b2c4479c0dbb79f26d47e4322162ec68d92cf
parentbb754aee251c12d184c05b11678dc31a0332ad3a (diff)
Connected display usb errors notification
Show notification if connected display link training fails, or usb cable is not displayport capable but connected device is capable of displayport. Change-Id: Ia10ca6fdc6dce1d1feedc69a4f3304ce0c1c3e63 Bug: 294345033 Bug: 283461472 Test: atest ConnectedDisplayUsbErrorsDetectorTest
-rw-r--r--core/res/res/values/strings.xml5
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerShellCommand.java9
-rw-r--r--services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java132
-rw-r--r--services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java59
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java130
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java25
7 files changed, 358 insertions, 4 deletions
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 41356bd49514..a2a4e34f3527 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6298,6 +6298,11 @@ ul.</string>
<!-- Content of connected display unavailable notification. [CHAR LIMIT=NONE] -->
<string name="connected_display_unavailable_notification_content">Use a different cable and try again</string>
+ <!-- Title of cable don't support displays notifications. [CHAR LIMIT=NONE] -->
+ <string name="connected_display_cable_dont_support_displays_notification_title">Cable may not support displays</string>
+ <!-- Content of cable don't support displays notification. [CHAR LIMIT=NONE] -->
+ <string name="connected_display_cable_dont_support_displays_notification_content">Your USB-C cable may not connect to displays properly</string>
+
<!-- Name of concurrent display notifications. [CHAR LIMIT=NONE] -->
<string name="concurrent_display_notification_name">Dual screen</string>
<!-- Title of concurrent display active notification. [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 39a4dd2aed8d..3b00682bd6b3 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5068,6 +5068,8 @@
<java-symbol type="array" name="device_state_notification_power_save_contents"/>
<java-symbol type="string" name="connected_display_unavailable_notification_title"/>
<java-symbol type="string" name="connected_display_unavailable_notification_content"/>
+ <java-symbol type="string" name="connected_display_cable_dont_support_displays_notification_title"/>
+ <java-symbol type="string" name="connected_display_cable_dont_support_displays_notification_content"/>
<java-symbol type="string" name="concurrent_display_notification_name"/>
<java-symbol type="string" name="concurrent_display_notification_active_title"/>
<java-symbol type="string" name="concurrent_display_notification_active_content"/>
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index 5e6ff4683850..d97c8e71c73c 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -30,7 +30,8 @@ import java.util.Arrays;
class DisplayManagerShellCommand extends ShellCommand {
private static final String TAG = "DisplayManagerShellCommand";
- private static final String NOTIFICATION_TYPES = "on-hotplug-error";
+ private static final String NOTIFICATION_TYPES =
+ "on-hotplug-error, on-link-training-failure, on-cable-dp-incapable";
private final DisplayManagerService mService;
private final DisplayManagerFlags mFlags;
@@ -193,6 +194,12 @@ class DisplayManagerShellCommand extends ShellCommand {
case "on-hotplug-error":
mService.getDisplayNotificationManager().onHotplugConnectionError();
break;
+ case "on-link-training-failure":
+ mService.getDisplayNotificationManager().onDisplayPortLinkTrainingFailure();
+ break;
+ case "on-cable-dp-incapable":
+ mService.getDisplayNotificationManager().onCableNotCapableDisplayPort();
+ break;
default:
getErrPrintWriter().println(
"Error: unexpected notification type=" + notificationType + ", use one of: "
diff --git a/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java b/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java
new file mode 100644
index 000000000000..f683e8104889
--- /dev/null
+++ b/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.notifications;
+
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED;
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE;
+import static android.hardware.usb.DisplayPortAltModeInfo.LINK_TRAINING_STATUS_FAILURE;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.hardware.usb.DisplayPortAltModeInfo;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbManager.DisplayPortAltModeInfoListener;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.feature.DisplayManagerFlags;
+
+/**
+ * Detects usb issues related to an external display connected.
+ */
+public class ConnectedDisplayUsbErrorsDetector implements DisplayPortAltModeInfoListener {
+ private static final String TAG = "ConnectedDisplayUsbErrorsDetector";
+
+ /**
+ * Dependency injection for {@link ConnectedDisplayUsbErrorsDetector}.
+ */
+ public interface Injector {
+
+ /**
+ * @return {@link UsbManager} service.
+ */
+ UsbManager getUsbManager();
+ }
+
+ /**
+ * USB errors listener
+ */
+ public interface Listener {
+
+ /**
+ * Link training failure callback.
+ */
+ void onDisplayPortLinkTrainingFailure();
+
+ /**
+ * DisplayPort capable device plugged-in, but cable is not supporting DisplayPort.
+ */
+ void onCableNotCapableDisplayPort();
+ }
+
+ private Listener mListener;
+ private final Injector mInjector;
+ private final Context mContext;
+ private final boolean mIsConnectedDisplayErrorHandlingEnabled;
+
+ ConnectedDisplayUsbErrorsDetector(@NonNull final DisplayManagerFlags flags,
+ @NonNull final Context context) {
+ this(flags, context, () -> context.getSystemService(UsbManager.class));
+ }
+
+ @VisibleForTesting
+ ConnectedDisplayUsbErrorsDetector(@NonNull final DisplayManagerFlags flags,
+ @NonNull final Context context, @NonNull final Injector injector) {
+ mContext = context;
+ mInjector = injector;
+ mIsConnectedDisplayErrorHandlingEnabled =
+ flags.isConnectedDisplayErrorHandlingEnabled();
+ }
+
+ /** Register listener for usb error events. */
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ void registerListener(final Listener listener) {
+ if (!mIsConnectedDisplayErrorHandlingEnabled) {
+ return;
+ }
+
+ final var usbManager = mInjector.getUsbManager();
+ if (usbManager == null) {
+ Slog.e(TAG, "UsbManager is null");
+ return;
+ }
+
+ mListener = listener;
+
+ try {
+ usbManager.registerDisplayPortAltModeInfoListener(mContext.getMainExecutor(), this);
+ } catch (IllegalStateException e) {
+ Slog.e(TAG, "Failed to register listener", e);
+ }
+ }
+
+ /**
+ * Callback upon changes in {@link DisplayPortAltModeInfo}.
+ * @param portId String describing the {@link android.hardware.usb.UsbPort} that was changed.
+ * @param info New {@link DisplayPortAltModeInfo} for the corresponding portId.
+ */
+ @Override
+ public void onDisplayPortAltModeInfoChanged(@NonNull String portId,
+ @NonNull DisplayPortAltModeInfo info) {
+ if (mListener == null) {
+ return;
+ }
+
+ if (DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED == info.getPartnerSinkStatus()
+ && DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE == info.getCableStatus()
+ ) {
+ mListener.onCableNotCapableDisplayPort();
+ return;
+ }
+
+ if (LINK_TRAINING_STATUS_FAILURE == info.getLinkTrainingStatus()) {
+ mListener.onDisplayPortLinkTrainingFailure();
+ return;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java
index 7d83e90eda9d..5cdef38cd45c 100644
--- a/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java
+++ b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java
@@ -34,12 +34,16 @@ import com.android.server.display.feature.DisplayManagerFlags;
/**
* Manages notifications for {@link com.android.server.display.DisplayManagerService}.
*/
-public class DisplayNotificationManager {
+public class DisplayNotificationManager implements ConnectedDisplayUsbErrorsDetector.Listener {
/** Dependency injection interface for {@link DisplayNotificationManager} */
public interface Injector {
/** Get {@link NotificationManager} service or null if not available. */
@Nullable
NotificationManager getNotificationManager();
+
+ /** Get {@link ConnectedDisplayUsbErrorsDetector} or null if not available. */
+ @Nullable
+ ConnectedDisplayUsbErrorsDetector getUsbErrorsDetector();
}
private static final String TAG = "DisplayNotificationManager";
@@ -52,9 +56,22 @@ public class DisplayNotificationManager {
private final Context mContext;
private final boolean mConnectedDisplayErrorHandlingEnabled;
private NotificationManager mNotificationManager;
+ private ConnectedDisplayUsbErrorsDetector mConnectedDisplayUsbErrorsDetector;
public DisplayNotificationManager(final DisplayManagerFlags flags, final Context context) {
- this(flags, context, () -> context.getSystemService(NotificationManager.class));
+ this(flags, context, new Injector() {
+ @Nullable
+ @Override
+ public NotificationManager getNotificationManager() {
+ return context.getSystemService(NotificationManager.class);
+ }
+
+ @Nullable
+ @Override
+ public ConnectedDisplayUsbErrorsDetector getUsbErrorsDetector() {
+ return new ConnectedDisplayUsbErrorsDetector(flags, context);
+ }
+ });
}
@VisibleForTesting
@@ -75,6 +92,44 @@ public class DisplayNotificationManager {
Slog.e(TAG, "onBootCompleted: NotificationManager is null");
return;
}
+
+ mConnectedDisplayUsbErrorsDetector = mInjector.getUsbErrorsDetector();
+ if (mConnectedDisplayUsbErrorsDetector != null) {
+ mConnectedDisplayUsbErrorsDetector.registerListener(this);
+ }
+ }
+
+ /**
+ * Display error notification upon DisplayPort link training failure.
+ */
+ @Override
+ public void onDisplayPortLinkTrainingFailure() {
+ if (!mConnectedDisplayErrorHandlingEnabled) {
+ Slog.d(TAG, "onDisplayPortLinkTrainingFailure:"
+ + " mConnectedDisplayErrorHandlingEnabled is false");
+ return;
+ }
+
+ sendErrorNotification(createErrorNotification(
+ R.string.connected_display_unavailable_notification_title,
+ R.string.connected_display_unavailable_notification_content));
+ }
+
+ /**
+ * Display error notification upon cable not capable of DisplayPort connected to a device
+ * capable of DisplayPort.
+ */
+ @Override
+ public void onCableNotCapableDisplayPort() {
+ if (!mConnectedDisplayErrorHandlingEnabled) {
+ Slog.d(TAG, "onCableNotCapableDisplayPort:"
+ + " mConnectedDisplayErrorHandlingEnabled is false");
+ return;
+ }
+
+ sendErrorNotification(createErrorNotification(
+ R.string.connected_display_cable_dont_support_displays_notification_title,
+ R.string.connected_display_cable_dont_support_displays_notification_content));
}
/**
diff --git a/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java
new file mode 100644
index 000000000000..d5a92cbc927f
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.notifications;
+
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED;
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_ENABLED;
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE;
+import static android.hardware.usb.DisplayPortAltModeInfo.LINK_TRAINING_STATUS_FAILURE;
+
+import static org.junit.Assume.assumeFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.usb.DisplayPortAltModeInfo;
+import android.hardware.usb.UsbManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.feature.DisplayManagerFlags;
+import com.android.server.display.notifications.ConnectedDisplayUsbErrorsDetector.Injector;
+
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link ConnectedDisplayUsbErrorsDetector}
+ * Run: atest ConnectedDisplayUsbErrorsDetectorTest
+ */
+@SmallTest
+@RunWith(TestParameterInjector.class)
+public class ConnectedDisplayUsbErrorsDetectorTest {
+ @Mock
+ private Injector mMockedInjector;
+ @Mock
+ private UsbManager mMockedUsbManager;
+ @Mock
+ private DisplayManagerFlags mMockedFlags;
+ @Mock
+ private ConnectedDisplayUsbErrorsDetector.Listener mMockedListener;
+
+ /** Setup tests. */
+ @Before
+ public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testNoErrorTypes(
+ @TestParameter final boolean isUsbManagerAvailable,
+ @TestParameter final boolean isUsbErrorsNotificationEnabled) {
+ // This is tested in #testErrorOnUsbCableNotCapableDp and #testErrorOnDpLinkTrainingFailure
+ assumeFalse(isUsbManagerAvailable && isUsbErrorsNotificationEnabled);
+ var detector = createErrorsDetector(isUsbManagerAvailable, isUsbErrorsNotificationEnabled);
+ // None of these should trigger an error now.
+ detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnUsbCableNotCapableDp());
+ detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnDpLinkTrainingFailure());
+ verify(mMockedUsbManager, never()).registerDisplayPortAltModeInfoListener(any(), any());
+ verify(mMockedListener, never()).onCableNotCapableDisplayPort();
+ verify(mMockedListener, never()).onDisplayPortLinkTrainingFailure();
+ }
+
+ @Test
+ public void testErrorOnUsbCableNotCapableDp() {
+ var detector = createErrorsDetector(/*isUsbManagerAvailable=*/ true,
+ /*isUsbErrorsNotificationEnabled=*/ true);
+ detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnUsbCableNotCapableDp());
+ verify(mMockedUsbManager).registerDisplayPortAltModeInfoListener(any(), any());
+ verify(mMockedListener).onCableNotCapableDisplayPort();
+ verify(mMockedListener, never()).onDisplayPortLinkTrainingFailure();
+ }
+
+ @Test
+ public void testErrorOnDpLinkTrainingFailure() {
+ var detector = createErrorsDetector(/*isUsbManagerAvailable=*/ true,
+ /*isUsbErrorsNotificationEnabled=*/ true);
+ detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnDpLinkTrainingFailure());
+ verify(mMockedUsbManager).registerDisplayPortAltModeInfoListener(any(), any());
+ verify(mMockedListener, never()).onCableNotCapableDisplayPort();
+ verify(mMockedListener).onDisplayPortLinkTrainingFailure();
+ }
+
+ private ConnectedDisplayUsbErrorsDetector createErrorsDetector(
+ final boolean isUsbManagerAvailable,
+ final boolean isConnectedDisplayUsbErrorsNotificationEnabled) {
+ when(mMockedFlags.isConnectedDisplayErrorHandlingEnabled())
+ .thenReturn(isConnectedDisplayUsbErrorsNotificationEnabled);
+ when(mMockedInjector.getUsbManager()).thenReturn(
+ (isUsbManagerAvailable) ? mMockedUsbManager : null);
+ var detector = new ConnectedDisplayUsbErrorsDetector(mMockedFlags,
+ ApplicationProvider.getApplicationContext(), mMockedInjector);
+ detector.registerListener(mMockedListener);
+ return detector;
+ }
+
+ private DisplayPortAltModeInfo createInfoOnUsbCableNotCapableDp() {
+ return new DisplayPortAltModeInfo(
+ DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED,
+ DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE, -1, false, 0);
+ }
+
+ private DisplayPortAltModeInfo createInfoOnDpLinkTrainingFailure() {
+ return new DisplayPortAltModeInfo(
+ DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED,
+ DISPLAYPORT_ALT_MODE_STATUS_ENABLED, -1, false,
+ LINK_TRAINING_STATUS_FAILURE);
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java b/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java
index d7c35ed3d610..1d2034be4acb 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java
@@ -83,14 +83,35 @@ public class DisplayNotificationManagerTest {
}
@Test
+ public void testNotificationOnDisplayPortLinkTrainingFailure() {
+ var dnm = createDisplayNotificationManager(/*isNotificationManagerAvailable=*/ true,
+ /*isErrorHandlingEnabled=*/ true);
+ dnm.onDisplayPortLinkTrainingFailure();
+ assertExpectedNotification();
+ }
+
+ @Test
+ public void testNotificationOnCableNotCapableDisplayPort() {
+ var dnm = createDisplayNotificationManager(/*isNotificationManagerAvailable=*/ true,
+ /*isErrorHandlingEnabled=*/ true);
+ dnm.onCableNotCapableDisplayPort();
+ assertExpectedNotification();
+ }
+
+ @Test
public void testNoErrorNotification(
@TestParameter final boolean isNotificationManagerAvailable,
@TestParameter final boolean isErrorHandlingEnabled) {
- /* This case is tested by #testNotificationOnHotplugConnectionError */
+ /* This case is tested by #testNotificationOnHotplugConnectionError,
+ #testNotificationOnDisplayPortLinkTrainingFailure,
+ #testNotificationOnCableNotCapableDisplayPort */
assumeFalse(isNotificationManagerAvailable && isErrorHandlingEnabled);
var dnm = createDisplayNotificationManager(isNotificationManagerAvailable,
isErrorHandlingEnabled);
+ // None of these methods should trigger a notification now.
dnm.onHotplugConnectionError();
+ dnm.onDisplayPortLinkTrainingFailure();
+ dnm.onCableNotCapableDisplayPort();
verify(mMockedNotificationManager, never()).notify(anyString(), anyInt(), any());
}
@@ -101,6 +122,8 @@ public class DisplayNotificationManagerTest {
isErrorHandlingEnabled);
when(mMockedInjector.getNotificationManager()).thenReturn(
(isNotificationManagerAvailable) ? mMockedNotificationManager : null);
+ // Usb errors detector is tested in ConnectedDisplayUsbErrorsDetectorTest
+ when(mMockedInjector.getUsbErrorsDetector()).thenReturn(/* usbErrorsDetector= */ null);
final var displayNotificationManager = new DisplayNotificationManager(mMockedFlags,
ApplicationProvider.getApplicationContext(), mMockedInjector);
displayNotificationManager.onBootCompleted();