diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index bfbaefe9c098e..7552a6a0dda1d 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -162,7 +162,7 @@ final class Constants { // Constants related to operands of HDMI CEC commands. // Refer to CEC Table 29 in HDMI Spec v1.4b. // [Abort Reason] - static final int ABORT_UNRECOGNIZED_MODE = 0; + static final int ABORT_UNRECOGNIZED_OPCODE = 0; static final int ABORT_NOT_IN_CORRECT_MODE = 1; static final int ABORT_CANNOT_PROVIDE_SOURCE = 2; static final int ABORT_INVALID_OPERAND = 3; diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index c16be50731bee..aedd632dcc3e7 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -172,7 +172,7 @@ abstract class HdmiCecLocalDevice { * @return true if consumed a message; otherwise, return false. */ @ServiceThreadOnly - final boolean dispatchMessage(HdmiCecMessage message) { + boolean dispatchMessage(HdmiCecMessage message) { assertRunOnServiceThread(); int dest = message.getDestination(); if (dest != mAddress && dest != Constants.ADDR_BROADCAST) { @@ -309,7 +309,7 @@ abstract class HdmiCecLocalDevice { mService.sendCecCommand( HdmiCecMessageBuilder.buildFeatureAbortCommand(mAddress, message.getSource(), Constants.MESSAGE_GET_MENU_LANGUAGE, - Constants.ABORT_UNRECOGNIZED_MODE)); + Constants.ABORT_UNRECOGNIZED_OPCODE)); return true; } @@ -381,7 +381,7 @@ abstract class HdmiCecLocalDevice { return false; } - private static boolean isPowerOnOrToggleCommand(HdmiCecMessage message) { + static boolean isPowerOnOrToggleCommand(HdmiCecMessage message) { byte[] params = message.getParams(); return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER @@ -389,7 +389,7 @@ abstract class HdmiCecLocalDevice { || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION); } - private static boolean isPowerOffOrToggleCommand(HdmiCecMessage message) { + static boolean isPowerOffOrToggleCommand(HdmiCecMessage message) { byte[] params = message.getParams(); return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER @@ -431,7 +431,7 @@ abstract class HdmiCecLocalDevice { Slog.v(TAG, "Wrong direct vendor command. Replying with "); mService.sendCecCommand(HdmiCecMessageBuilder.buildFeatureAbortCommand(mAddress, message.getSource(), Constants.MESSAGE_VENDOR_COMMAND_WITH_ID, - Constants.ABORT_UNRECOGNIZED_MODE)); + Constants.ABORT_UNRECOGNIZED_OPCODE)); } else { Slog.v(TAG, "Wrong broadcast vendor command. Ignoring"); } @@ -444,9 +444,10 @@ abstract class HdmiCecLocalDevice { } protected boolean handleRecordTvScreen(HdmiCecMessage message) { - // The default behavior of is replying with "Refused". + // The default behavior of is replying with + // "Cannot provide source". mService.sendCecCommand(HdmiCecMessageBuilder.buildFeatureAbortCommand(mAddress, - message.getSource(), message.getOpcode(), Constants.ABORT_REFUSED)); + message.getSource(), message.getOpcode(), Constants.ABORT_CANNOT_PROVIDE_SOURCE)); return true; } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index eda7b184507be..a4550a2b3b709 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -100,12 +100,15 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // If true, TV wakes itself up when receiving . private boolean mAutoWakeup; + private final HdmiCecStandbyModeHandler mStandbyHandler; + HdmiCecLocalDeviceTv(HdmiControlService service) { super(service, HdmiCecDeviceInfo.DEVICE_TV); mPrevPortId = Constants.INVALID_PORT_ID; mAutoDeviceOff = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, true); mAutoWakeup = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, true); + mStandbyHandler = new HdmiCecStandbyModeHandler(service, this); } @Override @@ -135,6 +138,16 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { SystemProperties.set(Constants.PROPERTY_PREFERRED_ADDRESS_TV, String.valueOf(addr)); } + @Override + @ServiceThreadOnly + boolean dispatchMessage(HdmiCecMessage message) { + assertRunOnServiceThread(); + if (mService.isPowerStandby() && mStandbyHandler.handleCommand(message)) { + return true; + } + return super.onMessage(message); + } + /** * Performs the action 'device select', or 'one touch play' initiated by TV. * @@ -787,8 +800,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } private boolean isSystemAudioOn() { - - synchronized (mLock) { return mSystemAudioActivated; } @@ -1183,6 +1194,12 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mService.writeBooleanSetting(Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, enabled); } + @ServiceThreadOnly + boolean getAutoWakeup() { + assertRunOnServiceThread(); + return mAutoWakeup; + } + @Override @ServiceThreadOnly protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java b/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java new file mode 100644 index 0000000000000..c6531259c281b --- /dev/null +++ b/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2014 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.hdmi; + +import android.util.SparseArray; + +/** + * This class handles the incoming messages when HdmiCecService is in the standby mode. + */ +public final class HdmiCecStandbyModeHandler { + + private interface CecMessageHandler { + boolean handle(HdmiCecMessage message); + } + + private static final class Bystander implements CecMessageHandler { + @Override + public boolean handle(HdmiCecMessage message) { + return true; + } + } + + private static final class Bypasser implements CecMessageHandler { + @Override + public boolean handle(HdmiCecMessage message) { + return false; + } + } + + private final class Aborter implements CecMessageHandler { + private final int mReason; + public Aborter(int reason) { + mReason = reason; + } + @Override + public boolean handle(HdmiCecMessage message) { + int src = message.getSource(); + int dest = message.getDestination(); + if (src == Constants.ADDR_BROADCAST || dest == Constants.ADDR_BROADCAST) { + // Do not send on the message from the unassigned device + // or the broadcasted message. + return true; + } + HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand( + dest, src, message.getOpcode(), mReason); + mService.sendCecCommand(cecMessage); + return true; + } + } + + private final class AutoOnHandler implements CecMessageHandler { + @Override + public boolean handle(HdmiCecMessage message) { + if (!mTv.getAutoWakeup()) { + mAborterRefused.handle(message); + return true; + } + return false; + } + } + + private final class UserControlProcessedHandler implements CecMessageHandler { + @Override + public boolean handle(HdmiCecMessage message) { + // The power status here is always standby. + if (HdmiCecLocalDevice.isPowerOnOrToggleCommand(message)) { + return false; + } else if (HdmiCecLocalDevice.isPowerOffOrToggleCommand(message)) { + return true; + } + return mAborterIncorrectMode.handle(message); + } + } + + private final HdmiControlService mService; + private final HdmiCecLocalDeviceTv mTv; + + private final SparseArray mCecMessageHandlers = new SparseArray<>(); + private final CecMessageHandler mDefaultHandler = new Aborter( + Constants.ABORT_UNRECOGNIZED_OPCODE); + private final CecMessageHandler mAborterIncorrectMode = new Aborter( + Constants.ABORT_NOT_IN_CORRECT_MODE); + private final CecMessageHandler mAborterRefused = new Aborter(Constants.ABORT_REFUSED); + private final CecMessageHandler mAutoOnHandler = new AutoOnHandler(); + private final CecMessageHandler mBypasser = new Bypasser(); + private final CecMessageHandler mBystander = new Bystander(); + private final UserControlProcessedHandler + mUserControlProcessedHandler = new UserControlProcessedHandler(); + + public HdmiCecStandbyModeHandler(HdmiControlService service, HdmiCecLocalDeviceTv tv) { + mService = service; + mTv = tv; + + addHandler(Constants.MESSAGE_IMAGE_VIEW_ON, mAutoOnHandler); + addHandler(Constants.MESSAGE_TEXT_VIEW_ON, mAutoOnHandler); + + addHandler(Constants.MESSAGE_ACTIVE_SOURCE, mBystander); + addHandler(Constants.MESSAGE_REQUEST_ACTIVE_SOURCE, mBystander); + addHandler(Constants.MESSAGE_ROUTING_CHANGE, mBystander); + addHandler(Constants.MESSAGE_ROUTING_INFORMATION, mBystander); + addHandler(Constants.MESSAGE_SET_STREAM_PATH, mBystander); + addHandler(Constants.MESSAGE_STANDBY, mBystander); + addHandler(Constants.MESSAGE_SET_MENU_LANGUAGE, mBystander); + addHandler(Constants.MESSAGE_DEVICE_VENDOR_ID, mBystander); + addHandler(Constants.MESSAGE_USER_CONTROL_RELEASED, mBystander); + addHandler(Constants.MESSAGE_REPORT_POWER_STATUS, mBystander); + addHandler(Constants.MESSAGE_FEATURE_ABORT, mBystander); + addHandler(Constants.MESSAGE_INACTIVE_SOURCE, mBystander); + addHandler(Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS, mBystander); + addHandler(Constants.MESSAGE_REPORT_AUDIO_STATUS, mBystander); + + // If TV supports the following messages during power-on, ignore them and do nothing, + // else reply with ["Unrecognized Opcode"] + // , , , + addHandler(Constants.MESSAGE_RECORD_STATUS, mBystander); + + // If TV supports the following messages during power-on, reply with ["Not + // in correct mode to respond"], else reply with ["Unrecognized Opcode"] + // ,