import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
+
/**
* Handles CEC command <Active Source>.
* <p>
/**
* Handles the incoming active source command.
*
- * @param activeAddress logical address of the device to be the active source
- * @param activePath routing path of the device to be the active source
+ * @param newActive new active source information
*/
- void process(int activeAddress, int activePath) {
+ void process(ActiveSource newActive) {
// Seq #17
HdmiCecLocalDeviceTv tv = mSource;
- if (getSourcePath() == activePath && tv.getActiveSource() == getSourceAddress()) {
+ ActiveSource activeSource = tv.getActiveSource();
+ if (activeSource.equals(newActive)) {
invokeCallback(HdmiControlManager.RESULT_SUCCESS);
return;
}
- HdmiCecDeviceInfo device = mService.getDeviceInfo(activeAddress);
+ HdmiCecDeviceInfo device = mService.getDeviceInfo(newActive.logicalAddress);
if (device == null) {
- tv.startNewDeviceAction(activeAddress, activePath);
+ tv.startNewDeviceAction(newActive);
}
- int currentActive = tv.getActiveSource();
- int currentPath = tv.getActivePath();
+ ActiveSource current = tv.getActiveSource();
if (!tv.isProhibitMode()) {
- tv.updateActiveSource(activeAddress, activePath);
- if (currentActive != activeAddress && currentPath != activePath) {
- tv.updateActivePortId(mService.pathToPortId(activePath));
+ tv.updateActiveSource(newActive);
+ if (!current.equals(newActive)) {
+ boolean notifyInputChange = (mCallback == null);
+ tv.updateActiveInput(newActive.physicalAddress, notifyInputChange);
}
invokeCallback(HdmiControlManager.RESULT_SUCCESS);
} else {
// TV is in a mode that should keep its current source/input from
// being changed for its operation. Reclaim the active source
// or switch the port back to the one used for the current mode.
- if (currentActive == getSourceAddress()) {
- HdmiCecMessage activeSource =
- HdmiCecMessageBuilder.buildActiveSource(currentActive, currentPath);
- mService.sendCecCommand(activeSource);
- tv.updateActiveSource(currentActive, currentPath);
+ if (current.logicalAddress == getSourceAddress()) {
+ HdmiCecMessage activeSourceCommand = HdmiCecMessageBuilder.buildActiveSource(
+ current.logicalAddress, current.physicalAddress);
+ mService.sendCecCommand(activeSourceCommand);
+ tv.updateActiveSource(current);
invokeCallback(HdmiControlManager.RESULT_SUCCESS);
} else {
HdmiCecMessage routingChange = HdmiCecMessageBuilder.buildRoutingChange(
- getSourceAddress(), activePath, currentPath);
+ getSourceAddress(), newActive.physicalAddress, current.physicalAddress);
mService.sendCecCommand(routingChange);
- tv.addAndStartAction(new RoutingControlAction(tv, currentPath, true, mCallback));
+ tv.addAndStartAction(
+ new RoutingControlAction(tv, current.physicalAddress, true, mCallback));
}
}
}
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
/**
return false;
case STATE_WAIT_FOR_ACTIVE_SOURCE:
if (opcode == Constants.MESSAGE_ACTIVE_SOURCE) {
- int activePath = HdmiUtils.twoBytesToInt(params);
+ int physicalAddress = HdmiUtils.twoBytesToInt(params);
ActiveSourceHandler
.create((HdmiCecLocalDeviceTv) localDevice(), mCallback)
- .process(cmd.getSource(), activePath);
+ .process(ActiveSource.of(cmd.getSource(), physicalAddress));
finish();
return true;
}
protected int mPreferredAddress;
protected HdmiCecDeviceInfo mDeviceInfo;
+ static class ActiveSource {
+ int logicalAddress;
+ int physicalAddress;
+
+ public ActiveSource(int logical, int physical) {
+ logicalAddress = logical;
+ physicalAddress = physical;
+ }
+ public static ActiveSource of(int logical, int physical) {
+ return new ActiveSource(logical, physical);
+ }
+ public boolean isValid() {
+ return HdmiUtils.isValidAddress(logicalAddress);
+ }
+ public boolean equals(int logical, int physical) {
+ return logicalAddress == logical && physicalAddress == physical;
+ }
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ActiveSource) {
+ ActiveSource that = (ActiveSource) obj;
+ return that.logicalAddress == logicalAddress &&
+ that.physicalAddress == physicalAddress;
+ }
+ return false;
+ }
+ @Override
+ public int hashCode() {
+ return logicalAddress * 29 + physicalAddress;
+ }
+ }
// Logical address of the active source.
@GuardedBy("mLock")
- private int mActiveSource;
+ protected final ActiveSource mActiveSource =
+ new ActiveSource(-1, Constants.INVALID_PHYSICAL_ADDRESS);
// Active routing path. Physical address of the active source but not all the time, such as
// when the new active source does not claim itself to be one. Note that we don't keep
return mService.isConnectedToArcPort(path);
}
- int getActiveSource() {
+ ActiveSource getActiveSource() {
synchronized (mLock) {
return mActiveSource;
}
}
- void setActiveSource(int source) {
+ void setActiveSource(ActiveSource newActive) {
+ setActiveSource(newActive.logicalAddress, newActive.physicalAddress);
+ }
+
+ void setActiveSource(HdmiCecDeviceInfo info) {
+ setActiveSource(info.getLogicalAddress(), info.getPhysicalAddress());
+ }
+
+ void setActiveSource(int logicalAddress, int physicalAddress) {
synchronized (mLock) {
- mActiveSource = source;
+ mActiveSource.logicalAddress = logicalAddress;
+ mActiveSource.physicalAddress = physicalAddress;
}
}
}
}
- void updateActiveDevice(int logicalAddress, int physicalAddress) {
- synchronized (mLock) {
- mActiveSource = logicalAddress;
- mActiveRoutingPath = physicalAddress;
- }
- }
-
@ServiceThreadOnly
HdmiCecMessageCache getCecMessageCache() {
assertRunOnServiceThread();
if (targetAddress == Constants.ADDR_INTERNAL) {
handleSelectInternalSource();
// Switching to internal source is always successful even when CEC control is disabled.
- setActiveSource(targetAddress);
+ setActiveSource(targetAddress, mService.getPhysicalAddress());
invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
return;
}
if (!mService.isControlEnabled()) {
- setActiveSource(targetAddress);
+ HdmiCecDeviceInfo info = getDeviceInfo(targetAddress);
+ if (info != null) {
+ setActiveSource(info);
+ }
invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
return;
}
private void handleSelectInternalSource() {
assertRunOnServiceThread();
// Seq #18
- if (mService.isControlEnabled() && getActiveSource() != mAddress) {
+ if (mService.isControlEnabled() && mActiveSource.logicalAddress != mAddress) {
updateActiveSource(mAddress, mService.getPhysicalAddress());
// TODO: Check if this comes from <Text/Image View On> - if true, do nothing.
HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
}
@ServiceThreadOnly
- void updateActiveSource(int activeSource, int activePath) {
+ void updateActiveSource(int logicalAddress, int physicalAddress) {
+ assertRunOnServiceThread();
+ updateActiveSource(ActiveSource.of(logicalAddress, physicalAddress));
+ }
+
+ @ServiceThreadOnly
+ void updateActiveSource(ActiveSource newActive) {
assertRunOnServiceThread();
// Seq #14
- if (activeSource == getActiveSource() && activePath == getActivePath()) {
+ if (mActiveSource.equals(newActive)) {
return;
}
- setActiveSource(activeSource);
- setActivePath(activePath);
- if (getDeviceInfo(activeSource) != null && activeSource != mAddress) {
- if (mService.pathToPortId(activePath) == getActivePortId()) {
+ setActiveSource(newActive);
+ int logicalAddress = newActive.logicalAddress;
+ if (getDeviceInfo(logicalAddress) != null && logicalAddress != mAddress) {
+ if (mService.pathToPortId(newActive.physicalAddress) == getActivePortId()) {
setPrevPortId(getActivePortId());
}
// TODO: Show the OSD banner related to the new active source device.
}
@ServiceThreadOnly
- void updateActivePortId(int portId) {
+ void updateActiveInput(int path, boolean notifyInputChange) {
assertRunOnServiceThread();
// Seq #15
+ int portId = mService.pathToPortId(path);
if (portId == getActivePortId()) {
return;
}
+ setActivePath(path);
setPrevPortId(portId);
- // TODO: Actually switch the physical port here. Handle PAP/PIP as well.
- // Show OSD port change banner
- mService.invokeInputChangeListener(getActiveSource());
+ // TODO: Handle PAP/PIP case.
+ // Show OSD port change banner
+ if (notifyInputChange) {
+ ActiveSource activeSource = getActiveSource();
+ HdmiCecDeviceInfo info = getDeviceInfo(activeSource.logicalAddress);
+ if (info == null) {
+ info = new HdmiCecDeviceInfo(Constants.ADDR_INVALID, path, portId,
+ HdmiCecDeviceInfo.DEVICE_RESERVED, 0, null);
+ }
+ mService.invokeInputChangeListener(info);
+ }
}
@ServiceThreadOnly
invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
return;
}
+ if (portId == getActivePortId()) {
+ invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
+ return;
+ }
+ setActiveSource(Constants.ADDR_INVALID, Constants.INVALID_PHYSICAL_ADDRESS);
if (!mService.isControlEnabled()) {
setActivePortId(portId);
invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
return;
}
- if (portId == getActivePortId()) {
- invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
- return;
- }
- setActivePortId(portId);
// TODO: Return immediately if the operation is triggered by <Text/Image View On>
- // and this is the first notification about the active input after power-on
- // (switch to HDMI didn't happen so far but is expected to happen soon).
- removeAction(RoutingControlAction.class);
-
- int oldPath = mService.portIdToPath(mService.portIdToPath(getActivePortId()));
+ // and this is the first notification about the active input after power-on
+ // (switch to HDMI didn't happen so far but is expected to happen soon).
+ int oldPath = mService.portIdToPath(getActivePortId());
int newPath = mService.portIdToPath(portId);
HdmiCecMessage routingChange =
HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath);
mService.sendCecCommand(routingChange);
+ removeAction(RoutingControlAction.class);
addAndStartAction(new RoutingControlAction(this, newPath, false, callback));
}
action.get(0).processKeyEvent(keyCode, isPressed);
} else {
if (isPressed) {
- addAndStartAction(new SendKeyAction(this, getActiveSource(), keyCode));
+ int logicalAddress = getActiveSource().logicalAddress;
+ addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode));
} else {
Slog.w(TAG, "Discard key release event");
}
@ServiceThreadOnly
protected boolean handleActiveSource(HdmiCecMessage message) {
assertRunOnServiceThread();
- int address = message.getSource();
- int path = HdmiUtils.twoBytesToInt(message.getParams());
- if (getDeviceInfo(address) == null) {
- handleNewDeviceAtTheTailOfActivePath(path);
+ int logicalAddress = message.getSource();
+ int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
+ if (getDeviceInfo(logicalAddress) == null) {
+ handleNewDeviceAtTheTailOfActivePath(physicalAddress);
} else {
- ActiveSourceHandler.create(this, null).process(address, path);
+ ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
+ ActiveSourceHandler.create(this, null).process(activeSource);
}
return true;
}
// Seq #10
// Ignore <Inactive Source> from non-active source device.
- if (getActiveSource() != message.getSource()) {
+ if (getActiveSource().logicalAddress != message.getSource()) {
return true;
}
if (isProhibitMode()) {
}
// TODO: Switch the TV freeze mode off
- setActivePortId(portId);
doManualPortSwitching(portId, null);
setPrevPortId(Constants.INVALID_PORT_ID);
}
protected boolean handleRequestActiveSource(HdmiCecMessage message) {
assertRunOnServiceThread();
// Seq #19
- if (mAddress == getActiveSource()) {
+ if (mAddress == getActiveSource().logicalAddress) {
mService.sendCecCommand(
HdmiCecMessageBuilder.buildActiveSource(mAddress, getActivePath()));
}
if (!isInDeviceList(path, address)) {
handleNewDeviceAtTheTailOfActivePath(path);
}
- startNewDeviceAction(address, path);
+ startNewDeviceAction(ActiveSource.of(address, path));
return true;
}
- void startNewDeviceAction(int address, int path) {
+ void startNewDeviceAction(ActiveSource activeSource) {
for (NewDeviceAction action : getActions(NewDeviceAction.class)) {
// If there is new device action which has the same logical address and path
// ignore new request.
// in. However, TV can detect a new device from HotPlugDetectionAction,
// which sends <Give Physical Address> to the source for newly detected
// device.
- if (action.isActionOf(address, path)) {
+ if (action.isActionOf(activeSource)) {
return;
}
}
- addAndStartAction(new NewDeviceAction(this, address, path));
+ addAndStartAction(new NewDeviceAction(this, activeSource.logicalAddress,
+ activeSource.physicalAddress));
}
private void handleNewDeviceAtTheTailOfActivePath(int path) {
runOnServiceThread(new Runnable() {
@Override
public void run() {
+ if (callback == null) {
+ Slog.e(TAG, "Callback cannot be null");
+ return;
+ }
HdmiCecLocalDeviceTv tv = tv();
if (tv == null) {
Slog.w(TAG, "Local tv device not available");
runOnServiceThread(new Runnable() {
@Override
public void run() {
+ if (callback == null) {
+ Slog.e(TAG, "Callback cannot be null");
+ return;
+ }
HdmiCecLocalDeviceTv tv = tv();
if (tv == null) {
Slog.w(TAG, "Local tv device not available");
}
}
- void invokeInputChangeListener(int activeAddress) {
+ void invokeInputChangeListener(HdmiCecDeviceInfo info) {
synchronized (mLock) {
if (mInputChangeListener != null) {
- HdmiCecDeviceInfo activeSource = getDeviceInfo(activeAddress);
try {
- mInputChangeListener.onChanged(activeSource);
+ mInputChangeListener.onChanged(info);
} catch (RemoteException e) {
Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
}
import android.hardware.hdmi.HdmiCecDeviceInfo;
import android.util.Slog;
+import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
import java.io.UnsupportedEncodingException;
/**
}
}
- boolean isActionOf(int address, int path) {
- return (mDeviceLogicalAddress == address) && (mDevicePhysicalAddress == path);
+ boolean isActionOf(ActiveSource activeSource) {
+ return (mDeviceLogicalAddress == activeSource.logicalAddress)
+ && (mDevicePhysicalAddress == activeSource.physicalAddress);
}
}
// true if <Give Power Status> should be sent once the new active routing path is determined.
private final boolean mQueryDevicePowerStatus;
+ // If set to true, call {@link HdmiControlService#invokeInputChangeListener()} when
+ // the routing control/active source change happens. The listener should be called if
+ // the events are triggered by external events such as manual switch port change or incoming
+ // <Inactive Source> command.
+ private final boolean mNotifyInputChange;
+
@Nullable private final IHdmiControlCallback mCallback;
// The latest routing path. Updated by each <Routing Information> from CEC switches.
mCallback = callback;
mCurrentRoutingPath = path;
mQueryDevicePowerStatus = queryDevicePowerStatus;
+ // Callback is non-null when routing control action is brought up by binder API. Use
+ // this as an indicator for the input change notification. These API calls will get
+ // the result through this callback, not through notification. Any other events that
+ // trigger the routing control is external, for which notifcation is used.
+ mNotifyInputChange = (callback == null);
}
@Override
if (isPowerOnOrTransient(devicePowerStatus)) {
sendSetStreamPath();
} else {
- tv().updateActivePortId(tv().pathToPortId(mCurrentRoutingPath));
+ tv().updateActiveInput(mCurrentRoutingPath, mNotifyInputChange);
}
}
finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
}
});
} else {
- tv().updateActivePortId(tv().pathToPortId(mCurrentRoutingPath));
+ tv().updateActiveInput(mCurrentRoutingPath, mNotifyInputChange);
finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
}
return;
case STATE_WAIT_FOR_REPORT_POWER_STATUS:
if (isPowerOnOrTransient(getTvPowerStatus())) {
- tv().updateActivePortId(tv().pathToPortId(mCurrentRoutingPath));
+ tv().updateActiveInput(mCurrentRoutingPath, mNotifyInputChange);
sendSetStreamPath();
}
finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
addTimer(mState, TIMEOUT_REPORT_POWER_STATUS_MS);
} else {
- tv().updateActivePortId(tv().pathToPortId(mCurrentRoutingPath));
+ tv().updateActiveInput(mCurrentRoutingPath, mNotifyInputChange);
sendSetStreamPath();
finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
}