Merge "Refactor handling sequences in HdmiControlService"
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index 73726fa..c1b8bbc 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -48,4 +48,5 @@
void setSystemAudioMode(boolean enabled, IHdmiControlCallback callback);
void addSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener);
void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener);
+ void setControlEnabled(boolean enabled);
}
diff --git a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
index 8de6763..905214f 100644
--- a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
+++ b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
@@ -67,9 +67,7 @@
}
HdmiCecDeviceInfo device = mService.getDeviceInfo(activeAddress);
if (device == null) {
- // "New device action" initiated by <Active Source> does not require
- // "Routing change action".
- tv.addAndStartAction(new NewDeviceAction(tv, activeAddress, activePath, false));
+ tv.addAndStartAction(new NewDeviceAction(tv, activeAddress, activePath));
}
int currentActive = tv.getActiveSource();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index bf7e57b..87cabc6 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -143,6 +143,8 @@
return handleGetCecVersion(message);
case HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS:
return handleReportPhysicalAddress(message);
+ case HdmiCec.MESSAGE_ROUTING_CHANGE:
+ return handleRoutingChange(message);
case HdmiCec.MESSAGE_INITIATE_ARC:
return handleInitiateArc(message);
case HdmiCec.MESSAGE_TERMINATE_ARC:
@@ -245,6 +247,10 @@
return false;
}
+ protected boolean handleRoutingChange(HdmiCecMessage message) {
+ return false;
+ }
+
protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
return false;
}
@@ -471,41 +477,6 @@
}
}
- boolean isHdmiControlEnabled() {
- synchronized (mLock) {
- return !mInputChangeEnabled;
- }
- }
-
- /**
- * Whether the given path is located in the tail of current active path.
- *
- * @param path to be tested
- * @return true if the given path is located in the tail of current active path; otherwise,
- * false
- */
- // TODO: move this to local device tv.
- boolean isTailOfActivePath(int path) {
- synchronized (mLock) {
- // If active routing path is internal source, return false.
- if (mActiveRoutingPath == 0) {
- return false;
- }
- for (int i = 12; i >= 0; i -= 4) {
- int curActivePath = (mActiveRoutingPath >> i) & 0xF;
- if (curActivePath == 0) {
- return true;
- } else {
- int curPath = (path >> i) & 0xF;
- if (curPath != curActivePath) {
- return false;
- }
- }
- }
- return false;
- }
- }
-
@ServiceThreadOnly
HdmiCecMessageCache getCecMessageCache() {
assertRunOnServiceThread();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 862233c..7021dc5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -121,7 +121,7 @@
private void handleSelectInternalSource(IHdmiControlCallback callback) {
assertRunOnServiceThread();
// Seq #18
- if (isHdmiControlEnabled() && getActiveSource() != mAddress) {
+ if (mService.isControlEnabled() && getActiveSource() != mAddress) {
updateActiveSource(mAddress, mService.getPhysicalAddress());
// TODO: Check if this comes from <Text/Image View On> - if true, do nothing.
HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
@@ -185,7 +185,7 @@
void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
assertRunOnServiceThread();
// Seq #20
- if (!isHdmiControlEnabled() || portId == getActivePortId()) {
+ if (!mService.isControlEnabled() || portId == getActivePortId()) {
invokeCallback(callback, HdmiCec.RESULT_INCORRECT_MODE);
return;
}
@@ -243,8 +243,13 @@
@ServiceThreadOnly
protected boolean handleActiveSource(HdmiCecMessage message) {
assertRunOnServiceThread();
- int activePath = HdmiUtils.twoBytesToInt(message.getParams());
- ActiveSourceHandler.create(this, null).process(message.getSource(), activePath);
+ int address = message.getSource();
+ int path = HdmiUtils.twoBytesToInt(message.getParams());
+ if (getDeviceInfo(address) == null) {
+ handleNewDeviceAtTheTailOfActivePath(address, path);
+ } else {
+ ActiveSourceHandler.create(this, null).process(address, path);
+ }
return true;
}
@@ -286,10 +291,9 @@
protected boolean handleRequestActiveSource(HdmiCecMessage message) {
assertRunOnServiceThread();
// Seq #19
- int address = getDeviceInfo().getLogicalAddress();
- if (address == getActiveSource()) {
+ if (mAddress == getActiveSource()) {
mService.sendCecCommand(
- HdmiCecMessageBuilder.buildActiveSource(address, getActivePath()));
+ HdmiCecMessageBuilder.buildActiveSource(mAddress, getActivePath()));
}
return true;
}
@@ -320,15 +324,70 @@
return true;
}
- int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
- int logicalAddress = message.getSource();
+ int path = HdmiUtils.twoBytesToInt(message.getParams());
+ int address = message.getSource();
+ if (!isInDeviceList(path, address)) {
+ handleNewDeviceAtTheTailOfActivePath(address, path);
+ }
+ addAndStartAction(new NewDeviceAction(this, address, path));
+ return true;
+ }
- // If it is a new device and connected to the tail of active path,
- // it's required to change routing path.
- boolean requireRoutingChange = !isInDeviceList(physicalAddress, logicalAddress)
- && isTailOfActivePath(physicalAddress);
- addAndStartAction(new NewDeviceAction(this, message.getSource(), physicalAddress,
- requireRoutingChange));
+ private void handleNewDeviceAtTheTailOfActivePath(int address, int path) {
+ // Seq #22
+ if (isTailOfActivePath(path, getActivePath())) {
+ removeAction(RoutingControlAction.class);
+ int newPath = mService.portIdToPath(getActivePortId());
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange(
+ mAddress, getActivePath(), newPath));
+ addAndStartAction(new RoutingControlAction(this, getActivePortId(), null));
+ }
+ }
+
+ /**
+ * Whether the given path is located in the tail of current active path.
+ *
+ * @param path to be tested
+ * @param activePath current active path
+ * @return true if the given path is located in the tail of current active path; otherwise,
+ * false
+ */
+ static boolean isTailOfActivePath(int path, int activePath) {
+ // If active routing path is internal source, return false.
+ if (activePath == 0) {
+ return false;
+ }
+ for (int i = 12; i >= 0; i -= 4) {
+ int curActivePath = (activePath >> i) & 0xF;
+ if (curActivePath == 0) {
+ return true;
+ } else {
+ int curPath = (path >> i) & 0xF;
+ if (curPath != curActivePath) {
+ return false;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleRoutingChange(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ // Seq #21
+ byte[] params = message.getParams();
+ if (params.length != 4) {
+ Slog.w(TAG, "Wrong parameter: " + message);
+ return true;
+ }
+ int currentPath = HdmiUtils.twoBytesToInt(params);
+ if (HdmiUtils.isAffectingActiveRoutingPath(getActivePath(), currentPath)) {
+ int newPath = HdmiUtils.twoBytesToInt(params, 2);
+ setActivePath(newPath);
+ removeAction(RoutingControlAction.class);
+ addAndStartAction(new RoutingControlAction(this, newPath, null));
+ }
return true;
}
@@ -776,10 +835,43 @@
final void removeCecDevice(int address) {
assertRunOnServiceThread();
HdmiCecDeviceInfo info = removeDeviceInfo(address);
+ handleRemoveActiveRoutingPath(info.getPhysicalAddress());
mCecMessageCache.flushMessagesFrom(address);
mService.invokeDeviceEventListeners(info, false);
}
+ private void handleRemoveActiveRoutingPath(int path) {
+ // Seq #23
+ if (isTailOfActivePath(path, getActivePath())) {
+ removeAction(RoutingControlAction.class);
+ int newPath = mService.portIdToPath(getActivePortId());
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange(
+ mAddress, getActivePath(), newPath));
+ addAndStartAction(new RoutingControlAction(this, getActivePortId(), null));
+ }
+ }
+
+ @ServiceThreadOnly
+ void routingAtEnableTime() {
+ assertRunOnServiceThread();
+ // Seq #24
+ if (getActivePortId() != HdmiConstants.INVALID_PORT_ID) {
+ // TODO: Check if TV was not powered on due to <Text/Image View On>,
+ // TV is not in Preset Installation mode, not in initial setup mode, not
+ // in Software updating mode, not in service mode, for following actions.
+ removeAction(RoutingControlAction.class);
+ int newPath = mService.portIdToPath(getActivePortId());
+ mService.sendCecCommand(
+ HdmiCecMessageBuilder.buildRoutingChange(mAddress, getActivePath(), newPath));
+ addAndStartAction(new RoutingControlAction(this, getActivePortId(), null));
+ } else {
+ int activePath = mService.getPhysicalAddress();
+ setActivePath(activePath);
+ // TODO: Do following only when TV was not powered on due to <Text/Image View On>.
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(mAddress, activePath));
+ }
+ }
+
/**
* Returns the {@link HdmiCecDeviceInfo} instance whose physical address matches
* the given routing path. CEC devices use routing path for its physical address to
@@ -804,12 +896,12 @@
* in a device info list. However, both are minimal condition and it could
* be different device from the original one.
*
- * @param physicalAddress physical address of a device to be searched
* @param logicalAddress logical address of a device to be searched
+ * @param physicalAddress physical address of a device to be searched
* @return true if exist; otherwise false
*/
@ServiceThreadOnly
- boolean isInDeviceList(int physicalAddress, int logicalAddress) {
+ boolean isInDeviceList(int logicalAddress, int physicalAddress) {
assertRunOnServiceThread();
HdmiCecDeviceInfo device = getDeviceInfo(logicalAddress);
if (device == null) {
@@ -820,7 +912,7 @@
@Override
@ServiceThreadOnly
- void onHotplug(int portNo, boolean connected) {
+ void onHotplug(int portId, boolean connected) {
assertRunOnServiceThread();
// Tv device will have permanent HotplugDetectionAction.
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index ad95181..6397109 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -116,6 +116,11 @@
private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
new ArrayList<>();
+ // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
+ // handling will be disabled and no request will be handled.
+ @GuardedBy("mLock")
+ private boolean mHdmiControlEnabled;
+
// List of listeners registered by callers that want to get notified of
// system audio mode changes.
private final ArrayList<IHdmiSystemAudioModeChangeListener>
@@ -163,6 +168,7 @@
// TODO: Read the preference for SystemAudioMode and initialize mSystemAudioMode and
// start to monitor the preference value and invoke SystemAudioActionFromTv if needed.
+ mHdmiControlEnabled = true;
}
@ServiceThreadOnly
@@ -716,6 +722,29 @@
enforceAccessPermission();
HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
}
+
+ @Override
+ public void setControlEnabled(boolean enabled) {
+ enforceAccessPermission();
+ synchronized (mLock) {
+ mHdmiControlEnabled = enabled;
+ }
+ // TODO: Stop the running actions when disabled, and start
+ // address allocation/device discovery when enabled.
+ if (!enabled) {
+ return;
+ }
+ runOnServiceThread(new Runnable() {
+ @Override
+ public void run() {
+ HdmiCecLocalDeviceTv tv = tv();
+ if (tv == null) {
+ return;
+ }
+ tv.routingAtEnableTime();
+ }
+ });
+ }
}
@ServiceThreadOnly
@@ -875,4 +904,10 @@
AudioManager getAudioManager() {
return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
}
+
+ boolean isControlEnabled() {
+ synchronized (mLock) {
+ return mHdmiControlEnabled;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java
index 9b7cc8d..51d26ef 100644
--- a/services/core/java/com/android/server/hdmi/HdmiUtils.java
+++ b/services/core/java/com/android/server/hdmi/HdmiUtils.java
@@ -104,6 +104,17 @@
}
/**
+ * Assemble two bytes into single integer value.
+ *
+ * @param data to be assembled
+ * @param offset offset to the data to convert in the array
+ * @return assembled value
+ */
+ static int twoBytesToInt(byte[] data, int offset) {
+ return ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF);
+ }
+
+ /**
* Assemble three bytes into single integer value.
*
* @param data to be assembled
@@ -121,4 +132,65 @@
return list;
}
+ /**
+ * See if the new path is affecting the active path.
+ *
+ * @param activePath current active path
+ * @param newPath new path
+ * @return true if the new path changes the current active path
+ */
+ static boolean isAffectingActiveRoutingPath(int activePath, int newPath) {
+ // The new path affects the current active path if the parent of the new path
+ // is an ancestor of the active path.
+ // (1.1.0.0, 2.0.0.0) -> true, new path alters the parent
+ // (1.1.0.0, 1.2.0.0) -> true, new path is a sibling
+ // (1.1.0.0, 1.2.1.0) -> false, new path is a descendant of a sibling
+ // (1.0.0.0, 3.2.0.0) -> false, in a completely different path
+
+ // Get the parent of the new path by clearing the least significant
+ // non-zero nibble.
+ for (int i = 0; i <= 12; i += 4) {
+ int nibble = (newPath >> i) & 0xF;
+ if (nibble != 0) {
+ int mask = 0xFFF0 << i;
+ newPath &= mask;
+ break;
+ }
+ }
+ if (newPath == 0x0000) {
+ return true; // Top path always affects the active path
+ }
+ return isInActiveRoutingPath(activePath, newPath);
+ }
+
+ /**
+ * See if the new path is in the active path.
+ *
+ * @param activePath current active path
+ * @param newPath new path
+ * @return true if the new path in the active routing path
+ */
+ static boolean isInActiveRoutingPath(int activePath, int newPath) {
+ // Check each nibble of the currently active path and the new path till the position
+ // where the active nibble is not zero. For (activePath, newPath),
+ // (1.1.0.0, 1.0.0.0) -> true, new path is a parent
+ // (1.2.1.0, 1.2.1.2) -> true, new path is a descendant
+ // (1.1.0.0, 1.2.0.0) -> false, new path is a sibling
+ // (1.0.0.0, 2.0.0.0) -> false, in a completely different path
+ for (int i = 12; i >= 0; i -= 4) {
+ int nibbleActive = (activePath >> i) & 0xF;
+ if (nibbleActive == 0) {
+ break;
+ }
+ int nibbleNew = (newPath >> i) & 0xF;
+ if (nibbleNew == 0) {
+ break;
+ }
+ if (nibbleActive != nibbleNew) {
+ return false;
+ }
+ }
+ return true;
+ }
+
}
diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
index 4a49f09..f7e29e5 100644
--- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java
+++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
@@ -48,7 +48,6 @@
private final int mDeviceLogicalAddress;
private final int mDevicePhysicalAddress;
- private final boolean mRequireRoutingChange;
private int mVendorId;
private String mDisplayName;
@@ -62,12 +61,11 @@
* @param requireRoutingChange whether to initiate routing change or not
*/
NewDeviceAction(HdmiCecLocalDevice source, int deviceLogicalAddress,
- int devicePhysicalAddress, boolean requireRoutingChange) {
+ int devicePhysicalAddress) {
super(source);
mDeviceLogicalAddress = deviceLogicalAddress;
mDevicePhysicalAddress = devicePhysicalAddress;
mVendorId = HdmiCec.UNKNOWN_VENDOR_ID;
- mRequireRoutingChange = requireRoutingChange;
}
@Override
@@ -85,10 +83,6 @@
}
}
- if (mRequireRoutingChange) {
- startRoutingChange();
- }
-
mState = STATE_WAITING_FOR_SET_OSD_NAME;
if (mayProcessCommandIfCached(mDeviceLogicalAddress, HdmiCec.MESSAGE_SET_OSD_NAME)) {
return true;
@@ -152,22 +146,6 @@
return false;
}
- private void startRoutingChange() {
- // Stop existing routing control.
- removeAction(RoutingControlAction.class);
-
- // Send routing change. The the address is a path of the active port.
- int newPath = toTopMostPortPath(mDevicePhysicalAddress);
- sendCommand(HdmiCecMessageBuilder.buildRoutingChange(getSourceAddress(),
- localDevice().getActivePath(), newPath));
- addAndStartAction(new RoutingControlAction(localDevice(),
- localDevice().pathToPortId(newPath), null));
- }
-
- private static int toTopMostPortPath(int physicalAddress) {
- return physicalAddress & HdmiConstants.ROUTING_PATH_TOP_MASK;
- }
-
private boolean mayProcessCommandIfCached(int destAddress, int opcode) {
HdmiCecMessage message = getCecMessageCache().getMessage(destAddress, opcode);
if (message != null) {
diff --git a/services/core/java/com/android/server/hdmi/RoutingControlAction.java b/services/core/java/com/android/server/hdmi/RoutingControlAction.java
index 0d657b2..0c3bc0c 100644
--- a/services/core/java/com/android/server/hdmi/RoutingControlAction.java
+++ b/services/core/java/com/android/server/hdmi/RoutingControlAction.java
@@ -86,7 +86,7 @@
// If the routing path doesn't belong to the currently active one, we should
// ignore it since it might have come from other routing change sequence.
int routingPath = HdmiUtils.twoBytesToInt(params);
- if (isInActiveRoutingPath(mCurrentRoutingPath, routingPath)) {
+ if (HdmiUtils.isInActiveRoutingPath(mCurrentRoutingPath, routingPath)) {
return true;
}
mCurrentRoutingPath = routingPath;
@@ -108,15 +108,11 @@
if (isPowerStatusOnOrTransientToOn(devicePowerStatus)) {
sendSetStreamPath();
} else {
- // The whole action should be stopped here if the device is in standby mode.
- // We don't attempt to wake it up by sending <Set Stream Path>.
+ tv().updateActivePortId(tv().pathToPortId(mCurrentRoutingPath));
}
- invokeCallback(HdmiCec.RESULT_SUCCESS);
- finish();
- } else {
- // TV is going into standby mode.
- // TODO: Figure out what to do.
- }
+ }
+ invokeCallback(HdmiCec.RESULT_SUCCESS);
+ finish();
}
private int getTvPowerStatus() {
@@ -133,29 +129,6 @@
mCurrentRoutingPath));
}
- private static boolean isInActiveRoutingPath(int activePath, int newPath) {
- // Check each nibble of the currently active path and the new path till the position
- // where the active nibble is not zero. For (activePath, newPath),
- // (1.1.0.0, 1.0.0.0) -> true, new path is a parent
- // (1.2.1.0, 1.2.1.2) -> true, new path is a descendant
- // (1.1.0.0, 1.2.0.0) -> false, new path is a sibling
- // (1.0.0.0, 2.0.0.0) -> false, in a completely different path
- for (int i = 12; i >= 0; i -= 4) {
- int nibbleActive = (activePath >> i) & 0xF;
- if (nibbleActive == 0) {
- break;
- }
- int nibbleNew = (newPath >> i) & 0xF;
- if (nibbleNew == 0) {
- break;
- }
- if (nibbleActive != nibbleNew) {
- return false;
- }
- }
- return true;
- }
-
@Override
public void handleTimerEvent(int timeoutState) {
if (mState != timeoutState || mState == STATE_NONE) {
@@ -166,7 +139,7 @@
case STATE_WAIT_FOR_ROUTING_INFORMATION:
HdmiCecDeviceInfo device = tv().getDeviceInfoByPath(mCurrentRoutingPath);
if (device == null) {
- maybeChangeActiveInput(tv().pathToPortId(mCurrentRoutingPath));
+ tv().updateActivePortId(tv().pathToPortId(mCurrentRoutingPath));
} else {
// TODO: Also check followings and then proceed:
// if routing change was neither triggered by TV at CEC enable time, nor
@@ -184,9 +157,8 @@
case STATE_WAIT_FOR_REPORT_POWER_STATUS:
int tvPowerStatus = getTvPowerStatus();
if (isPowerStatusOnOrTransientToOn(tvPowerStatus)) {
- if (!maybeChangeActiveInput(localDevice().pathToPortId(mCurrentRoutingPath))) {
- sendSetStreamPath();
- }
+ tv().updateActivePortId(tv().pathToPortId(mCurrentRoutingPath));
+ sendSetStreamPath();
}
invokeCallback(HdmiCec.RESULT_SUCCESS);
finish();
@@ -194,18 +166,6 @@
}
}
- // Called whenever an HDMI input of the TV shall become the active input.
- private boolean maybeChangeActiveInput(int path) {
- if (localDevice().getActivePortId() == localDevice().pathToPortId(path)) {
- return false;
- }
- // TODO: Remember the currently active input
- // if PAP/PIP is active, move the focus to the right window, otherwise switch
- // the port.
- // Show the OSD input change banner.
- return true;
- }
-
private void queryDevicePowerStatus(int address, SendMessageCallback callback) {
sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), address),
callback);
@@ -216,7 +176,8 @@
mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
addTimer(mState, TIMEOUT_REPORT_POWER_STATUS_MS);
} else {
- maybeChangeActiveInput(localDevice().pathToPortId(mCurrentRoutingPath));
+ tv().updateActivePortId(tv().pathToPortId(mCurrentRoutingPath));
+ sendSetStreamPath();
}
}