Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dfb1cfad authored by Oleg Blinnikov's avatar Oleg Blinnikov
Browse files

Revert "Revert "Connected display usb errors notification""

This reverts commit db16cfd0.

Reason for revert: Test was lacking USB permission, it is fixed by adding this permission to AndroidManifest

Change-Id: I27589579b76e64d21dfee669ad99bc052d9fa5fa
Bug: 294345033
Bug: 283461472
Test: atest ConnectedDisplayUsbErrorsDetectorTest DisplayManagerServiceTest
parent db16cfd0
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -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] -->
+2 −0
Original line number Diff line number Diff line
@@ -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"/>
+8 −1
Original line number Diff line number Diff line
@@ -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: "
+132 −0
Original line number Diff line number Diff line
/*
 * 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;
        }
    }
}
+57 −2
Original line number Diff line number Diff line
@@ -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));
    }

    /**
Loading