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

Commit cb8e22bb authored by Paul Colta's avatar Paul Colta Committed by Android (Google) Code Review
Browse files

Merge "HDMI: Call CEC API in TIF for TV to assert active source" into main

parents 1fcf3049 d538c4c0
Loading
Loading
Loading
Loading
+5 −5
Original line number Diff line number Diff line
@@ -25,8 +25,8 @@ import com.android.internal.annotations.VisibleForTesting;
 * Feature action that sends <Request Active Source> message and waits for <Active Source>.
 *
 * For TV panels, this action has a delay before sending <Request Active Source>. This is because it
 * should wait for a possible request from LauncherX and can be cancelled if an <Active Source>
 * message was received or the TV switched to another input.
 * should wait for a possible request from LauncherX or TIF (TV Input Framework) and can be
 * cancelled if an <Active Source> message was received or the TV switched to another input.
 */
public class RequestActiveSourceAction extends HdmiCecFeatureAction {
    private static final String TAG = "RequestActiveSourceAction";
@@ -40,9 +40,9 @@ public class RequestActiveSourceAction extends HdmiCecFeatureAction {
    // Number of retries <Request Active Source> is sent if no device answers this message.
    private static final int MAX_SEND_RETRY_COUNT = 1;

    // Timeout to wait for the LauncherX API call to be completed.
    // Timeout to wait for LauncherX or TIF to call the CEC API.
    @VisibleForTesting
    protected static final int TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS = 10000;
    protected static final int TIMEOUT_WAIT_FOR_TV_ASSERT_ACTIVE_SOURCE_MS = 15000;

    private int mSendRetryCount = 0;

@@ -67,7 +67,7 @@ public class RequestActiveSourceAction extends HdmiCecFeatureAction {
        // We wait for default timeout to allow the message triggered by the LauncherX API call to
        // be sent by the TV and another default timeout in case the message has to be answered
        // (e.g. TV sent a <Set Stream Path> or <Routing Change>).
        addTimer(mState, TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS);
        addTimer(mState, TIMEOUT_WAIT_FOR_TV_ASSERT_ACTIVE_SOURCE_MS);
        return true;
    }

+94 −2
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
import static android.media.tv.flags.Flags.tifUnbindInactiveTis;
import static android.media.tv.flags.Flags.kidsModeTvdbSharing;
import static android.media.tv.flags.Flags.hdmiControlEnhancedBehavior;

import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -44,8 +45,10 @@ import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.graphics.Rect;
import android.hardware.hdmi.HdmiClient;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiTvClient;
import android.media.AudioPresentation;
import android.media.PlaybackParams;
import android.media.tv.AdBuffer;
@@ -138,6 +141,8 @@ public final class TvInputManagerService extends SystemService {
    private static final String PERMISSION_ACCESS_WATCHED_PROGRAMS =
            "com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS";
    private static final long UPDATE_HARDWARE_TIS_BINDING_DELAY_IN_MILLIS = 10 * 1000; // 10 seconds
    private static final long SET_TV_AS_ACTIVE_SOURCE_IF_NO_REQUEST_DELAY_IN_MILLIS
            = 10 * 1000; // 10 seconds

    // There are two different formats of DVB frontend devices. One is /dev/dvb%d.frontend%d,
    // another one is /dev/dvb/adapter%d/frontend%d. Followings are the patterns for selecting the
@@ -185,6 +190,8 @@ public final class TvInputManagerService extends SystemService {
    private final HashSet<String> mExternalInputLoggingDeviceOnScreenDisplayNames =
            new HashSet<String>();
    private final List<String> mExternalInputLoggingDeviceBrandNames = new ArrayList<String>();
    private HdmiControlManager mHdmiControlManager = null;
    private HdmiTvClient mHdmiTvClient = null;

    public TvInputManagerService(Context context) {
        super(context);
@@ -197,7 +204,12 @@ public final class TvInputManagerService extends SystemService {
        mActivityManager =
                (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE);
        mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);

        mHdmiControlManager = mContext.getSystemService(HdmiControlManager.class);
        if (mHdmiControlManager == null) {
            Slog.w(TAG, "HdmiControlManager is null!");
        } else {
            mHdmiTvClient = mHdmiControlManager.getTvClient();
        }
        synchronized (mLock) {
            getOrCreateUserStateLocked(mCurrentUserId);
        }
@@ -208,6 +220,42 @@ public final class TvInputManagerService extends SystemService {
    @Override
    public void onStart() {
        publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());

        if (!hdmiControlEnhancedBehavior()) {
           return;
        }

        // To ensure the TV claims CEC active source status correctly, a receiver is registered to
        // monitor wake-up and sleep intents. Upon wake-up, this receiver sends a delayed message
        // triggering a TIF call into a CEC API to claim TV as the active source.
        // However, the API call is cancelled if the TV switches inputs or goes to sleep.
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SCREEN_ON);
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        mContext.registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                switch (action) {
                    case Intent.ACTION_SCREEN_ON:
                        Slog.w(TAG, "The TV woke up.");
                        mMessageHandler.removeMessages(
                                MessageHandler.MSG_CHECK_TV_AS_ACTIVE_SOURCE);
                        Message msg = mMessageHandler
                                .obtainMessage(MessageHandler.MSG_CHECK_TV_AS_ACTIVE_SOURCE);
                        mMessageHandler.sendMessageDelayed(msg,
                                SET_TV_AS_ACTIVE_SOURCE_IF_NO_REQUEST_DELAY_IN_MILLIS);
                        break;
                    case Intent.ACTION_SCREEN_OFF:
                        Slog.w(TAG, "The TV turned off.");
                        mMessageHandler.removeMessages(
                                MessageHandler.MSG_CHECK_TV_AS_ACTIVE_SOURCE);
                        break;
                    default:
                        return;
                }
            }
        }, filter);
    }

    @Override
@@ -4503,6 +4551,7 @@ public final class TvInputManagerService extends SystemService {
        static final int MSG_LOG_WATCH_END = 2;
        static final int MSG_SWITCH_CONTENT_RESOLVER = 3;
        static final int MSG_UPDATE_HARDWARE_TIS_BINDING = 4;
        static final int MSG_CHECK_TV_AS_ACTIVE_SOURCE = 5;

        private ContentResolver mContentResolver;

@@ -4575,8 +4624,27 @@ public final class TvInputManagerService extends SystemService {
                        args.recycle();
                    }
                    break;
                case MSG_CHECK_TV_AS_ACTIVE_SOURCE:
                    synchronized (mLock) {
                        if (mOnScreenInputId == null) {
                            assertTvAsCecActiveSourceLocked();
                            break;
                        }
                        // TV that switched to a different input, but not an HDMI input
                        // (e.g. composite) can also assert active source.
                        UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
                        TvInputState inputState = userState.inputMap.get(mOnScreenInputId);
                        if (inputState == null) {
                            Slog.w(TAG, "Unexpected null TvInputState.");
                            break;
                        }
                        if (inputState.info.getType() != TvInputInfo.TYPE_HDMI) {
                            assertTvAsCecActiveSourceLocked();
                        }
                    }
                    break;
                default: {
                    Slog.w(TAG, "unhandled message code: " + msg.what);
                    Slog.w(TAG, "Unhandled message code: " + msg.what);
                    break;
                }
            }
@@ -4822,6 +4890,30 @@ public final class TvInputManagerService extends SystemService {
        }
    }

    @GuardedBy("mLock")
    private void assertTvAsCecActiveSourceLocked() {
        if (mHdmiTvClient == null) {
            Slog.w(TAG, "HdmiTvClient is null!");
            return;
        }
        mHdmiTvClient.selectDevice(HdmiDeviceInfo.DEVICE_TV,
                mContext.getMainExecutor(),
                new HdmiClient.OnDeviceSelectedListener() {
                    @Override
                    public void onDeviceSelected(int result,
                            int logicalAddress) {
                        if (result == HdmiControlManager.RESULT_SUCCESS) {
                            Slog.w(TAG,
                                    "Setting TV as the active CEC device was successful.");
                        } else {
                            Slog.w(TAG,
                                    "Setting TV as the active CEC device failed with result "
                                            + result);
                        }
                    }
                });
    }

    private static class SessionNotFoundException extends IllegalArgumentException {
        public SessionNotFoundException(String name) {
            super(name);
+6 −6
Original line number Diff line number Diff line
@@ -28,7 +28,7 @@ import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE;
import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF;
import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
import static com.android.server.hdmi.RequestActiveSourceAction.TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS;
import static com.android.server.hdmi.RequestActiveSourceAction.TIMEOUT_WAIT_FOR_TV_ASSERT_ACTIVE_SOURCE_MS;
import static com.android.server.hdmi.RoutingControlAction.TIMEOUT_ROUTING_INFORMATION_MS;
import static com.android.server.hdmi.RequestSadAction.RETRY_COUNTER_MAX;

@@ -1877,7 +1877,7 @@ public class HdmiCecLocalDeviceTvTest {
        mTestLooper.dispatchAll();

        // Skip the LauncherX API timeout.
        mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS);
        mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_TV_ASSERT_ACTIVE_SOURCE_MS);
        mTestLooper.dispatchAll();

        assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -1910,7 +1910,7 @@ public class HdmiCecLocalDeviceTvTest {
        mTestLooper.dispatchAll();

        // Skip the LauncherX API timeout.
        mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS);
        mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_TV_ASSERT_ACTIVE_SOURCE_MS);
        mTestLooper.dispatchAll();

        assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -1946,7 +1946,7 @@ public class HdmiCecLocalDeviceTvTest {
        mTestLooper.dispatchAll();

        // Skip the LauncherX API timeout.
        mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS);
        mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_TV_ASSERT_ACTIVE_SOURCE_MS);
        mTestLooper.dispatchAll();

        assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -1989,7 +1989,7 @@ public class HdmiCecLocalDeviceTvTest {
        mTestLooper.dispatchAll();

        // Skip the LauncherX API timeout.
        mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS);
        mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_TV_ASSERT_ACTIVE_SOURCE_MS);
        mTestLooper.dispatchAll();

        assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -2026,7 +2026,7 @@ public class HdmiCecLocalDeviceTvTest {
        mHdmiControlService.sendCecCommand(setStreamPathFromTv);

        // Skip the LauncherX API timeout.
        mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS);
        mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_TV_ASSERT_ACTIVE_SOURCE_MS);
        mTestLooper.dispatchAll();

        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestActiveSource);