}
HdmiCecDeviceInfo device = mService.getDeviceInfo(deviceLogicalAddress);
if (device == null) {
- // TODO: Start new device action (Device Discovery) sequence 5.
+ // "New device action" initiated by <Active Source> does not require
+ // "Routing change action".
+ mService.addAndStartAction(new NewDeviceAction(mService, mSourceAddress,
+ deviceLogicalAddress, routingPath, false));
}
if (!mService.isInPresetInstallationMode()) {
private static final int DEVICE_POLLING_RETRY = 1;
- // TODO: Move this to common place
- private static final int INVALID_PHYSICAL_ADDRESS = 0xFFFF;
-
/**
* Interface used to report result of device discovery.
*/
private static final class DeviceInfo {
private final int mLogicalAddress;
- private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
+ private int mPhysicalAddress = HdmiConstants.INVALID_PHYSICAL_ADDRESS;
private int mVendorId = HdmiCec.UNKNOWN_VENDOR_ID;
private String mDisplayName = "";
private int mDeviceType = HdmiCec.DEVICE_INACTIVE;
Slog.e(TAG, "Callback failed:" + e);
}
}
+
+ int getTargetAddress() {
+ return mTarget.getLogicalAddress();
+ }
}
return onMessage(message);
}
- protected boolean onMessage(HdmiCecMessage message) {
+ protected final boolean onMessage(HdmiCecMessage message) {
switch (message.getOpcode()) {
case HdmiCec.MESSAGE_GET_MENU_LANGUAGE:
return handleGetMenuLanguage(message);
return handleGiveDeviceVendorId();
case HdmiCec.MESSAGE_GET_CEC_VERSION:
return handleGetCecVersion(message);
+ case HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS:
+ return handleReportPhysicalAddress(message);
default:
return false;
}
return false;
}
+ protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
+ return false;
+ }
+
final void handleAddressAllocated(int logicalAddress) {
mAddress = mPreferredAddress = logicalAddress;
onAddressAllocated(logicalAddress);
package com.android.server.hdmi;
-import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.hdmi.HdmiCec;
import android.hardware.hdmi.HdmiCecDeviceInfo;
import android.hardware.hdmi.HdmiCecMessage;
+import android.hardware.hdmi.IHdmiControlCallback;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
+
import java.util.Collections;
import java.util.List;
import java.util.Locale;
mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
mAddress, mService.getVendorId()));
- mService.launchDeviceDiscovery(mAddress);
+ launchDeviceDiscovery();
// TODO: Start routing control action, device discovery action.
}
- @Override
- protected boolean onMessage(HdmiCecMessage message) {
- switch (message.getOpcode()) {
- case HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS:
- return handleReportPhysicalAddress(message);
- default:
- return super.onMessage(message);
- }
- }
-
/**
* Performs the action 'device select', or 'one touch play' initiated by TV.
*
return true;
}
- private boolean handleReportPhysicalAddress(HdmiCecMessage message) {
+ @Override
+ protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
// Ignore if [Device Discovery Action] is going on.
if (mService.hasAction(DeviceDiscoveryAction.class)) {
Slog.i(TAG, "Ignore unrecognizable <Report Physical Address> "
}
int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
- mService.addAndStartAction(new NewDeviceAction(mService,
- mAddress, message.getSource(), physicalAddress));
+ int logicalAddress = message.getSource();
+ // If it is a new device and connected to the tail of active path,
+ // it's required to change routing path.
+ boolean requireRoutingChange = !mService.isInDeviceList(physicalAddress, logicalAddress)
+ && mService.isTailOfActivePath(physicalAddress);
+ mService.addAndStartAction(new NewDeviceAction(mService,
+ mAddress, message.getSource(), physicalAddress,
+ requireRoutingChange));
return true;
}
HdmiConstants.ABORT_REFUSED));
return true;
}
+
+ private void launchDeviceDiscovery() {
+ mService.clearAllDeviceInfo();
+ // TODO: Move the following callback to HdmiLocalDeviceTv.
+ DeviceDiscoveryAction action = new DeviceDiscoveryAction(mService, mAddress,
+ new DeviceDiscoveryCallback() {
+ @Override
+ public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) {
+ for (HdmiCecDeviceInfo info : deviceInfos) {
+ mService.addCecDevice(info);
+ }
+
+ // Since we removed all devices when it's start and
+ // device discovery action does not poll local devices,
+ // we should put device info of local device manually here
+ for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
+ mService.addCecDevice(device.getDeviceInfo());
+ }
+
+ mService.addAndStartAction(new HotplugDetectionAction(mService,
+ mAddress));
+ }
+ });
+ mService.addAndStartAction(action);
+ }
}
*/
static final int FLAG_HDMI_OPTION_SYSTEM_CEC_CONTROL = 3;
+ static final int INVALID_PORT_ID = -1;
+ static final int INVALID_PHYSICAL_ADDRESS = 0xFFFF;
+
private HdmiConstants() { /* cannot be instantiated */ }
}
import android.hardware.hdmi.HdmiCec;
import android.hardware.hdmi.HdmiCecDeviceInfo;
import android.hardware.hdmi.HdmiCecMessage;
+import android.hardware.hdmi.HdmiHotplugEvent;
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.hdmi.IHdmiControlService;
import com.android.internal.annotations.GuardedBy;
import com.android.server.SystemService;
-import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
import java.util.ArrayList;
HdmiPortInfo portInfo = getPortInfo(portId);
if (portInfo == null) {
Slog.e(TAG, "Cannot find the port info: " + portId);
- return 0xFFFF; // Use HdmiConstants.INVALID_PHYSICAL_ADDRESS;
+ return HdmiConstants.INVALID_PHYSICAL_ADDRESS;
}
return portInfo.getAddress();
}
return info.getId();
}
}
- return -1; // Use HdmiConstants.INVALID_PORT_ID;
+ return HdmiConstants.INVALID_PORT_ID;
}
/**
void setSystemAudioMode(boolean on) {
synchronized (mLock) {
- mSystemAudioMode = on;
+ if (on != mSystemAudioMode) {
+ mSystemAudioMode = on;
+ // TODO: Need to set the preference for SystemAudioMode.
+ // TODO: Need to handle the notification of changing the mode and
+ // to identify the notification should be handled in the service or TvSettings.
+ }
}
}
}
}
+ /**
+ * Whether a device of the specified physical address is connected to ARC enabled port.
+ */
+ boolean isConnectedToArcPort(int physicalAddress) {
+ for (HdmiPortInfo portInfo : mPortInfo) {
+ if (hasSameTopPort(portInfo.getAddress(), physicalAddress)
+ && portInfo.isArcSupported()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
// See if we have an action of a given type in progress.
<T extends FeatureAction> boolean hasAction(final Class<T> clazz) {
for (FeatureAction action : mActions) {
return false;
}
+ // Returns all actions matched with given class type.
+ <T extends FeatureAction> List<T> getActions(final Class<T> clazz) {
+ ArrayList<T> actions = new ArrayList<>();
+ for (FeatureAction action : mActions) {
+ if (action.getClass().equals(clazz)) {
+ actions.add((T) action);
+ }
+ }
+ return actions;
+ }
+
/**
* Remove the given {@link FeatureAction} object from the action queue.
*
}
/**
+ * Returns whether ARC is enabled or not.
+ */
+ boolean getArcStatus() {
+ synchronized (mLock) {
+ return mArcStatusEnabled;
+ }
+ }
+
+ /**
* Transmit a CEC command to CEC bus.
*
* @param command CEC command to send out
return dispatchMessageToLocalDevice(message);
}
+ private boolean dispatchMessageToAction(HdmiCecMessage message) {
+ for (FeatureAction action : mActions) {
+ if (action.processCommand(message)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
if (device.dispatchMessage(message)) {
return true;
}
}
+
+ Slog.w(TAG, "Unhandled cec command:" + message);
return false;
}
* @param connected whether to be plugged in or not
*/
void onHotplug(int portNo, boolean connected) {
- // TODO: Start "RequestArcInitiationAction" if ARC port.
+ assertRunOnServiceThread();
+ // TODO: delegate onHotplug event to each local device.
+
+ // Tv device will have permanent HotplugDetectionAction.
+ List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class);
+ if (!hotplugActions.isEmpty()) {
+ // Note that hotplug action is single action running on a machine.
+ // "pollAllDevicesNow" cleans up timer and start poll action immediately.
+ hotplugActions.get(0).pollAllDevicesNow();
+ }
+
+ announceHotplugEvent(portNo, connected);
}
/**
return strategy | iterationStrategy;
}
+ void clearAllDeviceInfo() {
+ assertRunOnServiceThread();
+ mCecController.clearDeviceInfoList();
+ }
+
+ List<HdmiCecLocalDevice> getAllLocalDevices() {
+ assertRunOnServiceThread();
+ return mCecController.getLocalDeviceList();
+ }
+
/**
- * Launch device discovery sequence. It starts with clearing the existing device info list.
- * Note that it assumes that logical address of all local devices is already allocated.
+ * Whether a device of the specified physical address and logical address exists
+ * in a device info list. However, both are minimal condition and it could
+ * be different device from the original one.
*
- * @param sourceAddress a logical address of tv
+ * @param physicalAddress physical address of a device to be searched
+ * @param logicalAddress logical address of a device to be searched
+ * @return true if exist; otherwise false
*/
- void launchDeviceDiscovery(final int sourceAddress) {
- // At first, clear all existing device infos.
- mCecController.clearDeviceInfoList();
- // TODO: flush cec message cache when CEC is turned off.
-
- DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, sourceAddress,
- new DeviceDiscoveryCallback() {
- @Override
- public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) {
- for (HdmiCecDeviceInfo info : deviceInfos) {
- addCecDevice(info);
- }
-
- // Add device info of all local devices.
- for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
- addCecDevice(device.getDeviceInfo());
- }
-
- addAndStartAction(new HotplugDetectionAction(HdmiControlService.this,
- sourceAddress));
- }
- });
- addAndStartAction(action);
+ boolean isInDeviceList(int physicalAddress, int logicalAddress) {
+ assertRunOnServiceThread();
+ HdmiCecDeviceInfo device = mCecController.getDeviceInfo(logicalAddress);
+ if (device == null) {
+ return false;
+ }
+ return device.getPhysicalAddress() == physicalAddress;
}
private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) {
addAndStartAction(action);
}
- private boolean dispatchMessageToAction(HdmiCecMessage message) {
- for (FeatureAction action : mActions) {
- if (action.processCommand(message)) {
- return true;
- }
- }
- Slog.w(TAG, "Unsupported cec command:" + message);
- return false;
- }
-
private void handleSetSystemAudioMode(HdmiCecMessage message) {
if (dispatchMessageToAction(message) || !isMessageForSystemAudio(message)) {
return;
}
}
- void addCecDevice(HdmiCecDeviceInfo info) {
- mCecController.addDeviceInfo(info);
- }
-
private void enforceAccessPermission() {
getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
}
});
}
-
@Override
public void oneTouchPlay(final IHdmiControlCallback callback) {
enforceAccessPermission();
}
/**
+ * Called when a device is newly added or a new device is detected.
+ *
+ * @param info device info of a new device.
+ */
+ void addCecDevice(HdmiCecDeviceInfo info) {
+ mCecController.addDeviceInfo(info);
+
+ // TODO: announce new device detection.
+ }
+
+ /**
* Called when a device is removed or removal of device is detected.
*
* @param address a logical address of a device to be removed
void removeCecDevice(int address) {
mCecController.removeDeviceInfo(address);
mCecMessageCache.flushMessagesFrom(address);
+
+ // TODO: announce a device removal.
+ }
+
+ private void announceHotplugEvent(int portNo, boolean connected) {
+ HdmiHotplugEvent event = new HdmiHotplugEvent(portNo, connected);
+ synchronized (mLock) {
+ for (IHdmiHotplugEventListener listener : mHotplugEventListeners) {
+ invokeHotplugEventListener(listener, event);
+ }
+ }
+ }
+
+ private void invokeHotplugEventListener(IHdmiHotplugEventListener listener,
+ HdmiHotplugEvent event) {
+ try {
+ listener.onReceived(event);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
+ }
}
HdmiCecMessageCache getCecMessageCache() {
return mCecMessageCache;
}
+
+ private static boolean hasSameTopPort(int path1, int path2) {
+ return (path1 & HdmiConstants.ROUTING_PATH_TOP_MASK)
+ == (path2 & HdmiConstants.ROUTING_PATH_TOP_MASK);
+ }
+
+ /**
+ * 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) {
+ // 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;
+ }
}
}
}
+ /**
+ * Start device polling immediately.
+ */
+ void pollAllDevicesNow() {
+ // Clear existing timer to avoid overlapped execution
+ mActionTimer.clearTimerMessage();
+
+ mTimeoutCount = 0;
+ mState = STATE_WAIT_FOR_NEXT_POLLING;
+ pollAllDevices();
+
+ addTimer(mState, POLLING_INTERVAL_MS);
+ }
+
// This method is called every 5 seconds.
private void pollDevices() {
// All device check called every 15 seconds.
}
private void addDevice(int addedAddress) {
- // TODO: implement this.
+ // Send <Give Physical Address>.
+ sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(mSourceAddress, addedAddress));
}
private void removeDevice(int removedAddress) {
+ // TODO: move the following logic to local device once move many logic to
+ // local device.
+ mayChangeRoutingPath(removedAddress);
+ mayCancelDeviceSelect(removedAddress);
+ mayCancelOneTouchRecord(removedAddress);
+ mayDisableSystemAudioAndARC(removedAddress);
+
mService.removeCecDevice(removedAddress);
- // TODO: implements following steps.
- // 1. Launch routing control sequence
- // 2. Stop one touch play sequence if removed device is the device to be selected.
- // 3. If audio system, start system audio off and arc off
- // 4. Inform device remove to others
+ }
+
+ private void mayChangeRoutingPath(int address) {
+ // TODO: if removed address is current active source, it should change active source
+ // path new one. we can keep previous selection or can choose default input,
+ // such as internal tuner. Consider send intent to app so that app
+ // can handle it.
+ }
+
+ private void mayCancelDeviceSelect(int address) {
+ List<DeviceSelectAction> actions = mService.getActions(DeviceSelectAction.class);
+ if (actions.isEmpty()) {
+ return;
+ }
+
+ // Should ave only one Device Select Action
+ DeviceSelectAction action = actions.get(0);
+ if (action.getTargetAddress() == address) {
+ mService.removeAction(DeviceSelectAction.class);
+ }
+ }
+
+ private void mayCancelOneTouchRecord(int address) {
+ // TODO: implement this.
+ }
+
+ private void mayDisableSystemAudioAndARC(int address) {
+ if (HdmiCec.getTypeFromAddress(address) != HdmiCec.DEVICE_AUDIO_SYSTEM) {
+ return;
+ }
+
+ // Turn off system audio mode.
+ mService.setSystemAudioMode(false);
+ if (mService.getArcStatus()) {
+ mService.addAndStartAction(
+ new RequestArcTerminationAction(mService, mSourceAddress, address));
+ }
+
}
}
private final int mDeviceLogicalAddress;
private final int mDevicePhysicalAddress;
+ private final boolean mRequireRoutingChange;
private int mVendorId;
private String mDisplayName;
* @param sourceAddress logical address to be used as source address
* @param deviceLogicalAddress logical address of the device in interest
* @param devicePhysicalAddress physical address of the device in interest
+ * @param requireRoutingChange whether to initiate routing change or not
*/
NewDeviceAction(HdmiControlService service, int sourceAddress, int deviceLogicalAddress,
- int devicePhysicalAddress) {
+ int devicePhysicalAddress, boolean requireRoutingChange) {
super(service, sourceAddress);
mDeviceLogicalAddress = deviceLogicalAddress;
mDevicePhysicalAddress = devicePhysicalAddress;
mVendorId = HdmiCec.UNKNOWN_VENDOR_ID;
+ mRequireRoutingChange = requireRoutingChange;
}
@Override
public boolean start() {
+ if (HdmiCec.getTypeFromAddress(mSourceAddress) == HdmiCec.DEVICE_AUDIO_SYSTEM) {
+ if (mService.getAvrDeviceInfo() == null) {
+ // TODO: Start system audio initiation action
+ }
+
+ // If new device is connected through ARC enabled port,
+ // initiates ARC channel establishment.
+ if (mService.isConnectedToArcPort(mDevicePhysicalAddress)) {
+ mService.addAndStartAction(new RequestArcInitiationAction(mService, mSourceAddress,
+ mDeviceLogicalAddress));
+ }
+ }
+
+ if (mRequireRoutingChange) {
+ startRoutingChange();
+ }
+
mState = STATE_WAITING_FOR_SET_OSD_NAME;
if (mayProcessCommandIfCached(mDeviceLogicalAddress, HdmiCec.MESSAGE_SET_OSD_NAME)) {
return true;
return false;
}
+ private void startRoutingChange() {
+ // Stop existing routing control.
+ mService.removeAction(RoutingControlAction.class);
+
+ // Send routing change. The the address is a path of the active port.
+ int newPath = toTopMostPortPath(mDevicePhysicalAddress);
+ sendCommand(HdmiCecMessageBuilder.buildRoutingChange(mSourceAddress,
+ mService.getActivePath(), newPath));
+ mService.addAndStartAction(new RoutingControlAction(mService, mSourceAddress,
+ mService.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 = mService.getCecMessageCache().getMessage(destAddress, opcode);
if (message != null) {
package com.android.server.hdmi;
-import java.util.concurrent.TimeUnit;
-
-import android.hardware.hdmi.IHdmiControlCallback;
+import android.annotation.Nullable;
import android.hardware.hdmi.HdmiCec;
import android.hardware.hdmi.HdmiCecDeviceInfo;
import android.hardware.hdmi.HdmiCecMessage;
+import android.hardware.hdmi.IHdmiControlCallback;
import android.os.RemoteException;
import android.util.Slog;
// Time out in milliseconds used for <Report Power Status>
private static final int TIMEOUT_REPORT_POWER_STATUS_MS = 1000;
- private final IHdmiControlCallback mCallback;
+ @Nullable private final IHdmiControlCallback mCallback;
// The latest routing path. Updated by each <Routing Information> from CEC switches.
private int mCurrentRoutingPath;
case STATE_WAIT_FOR_ROUTING_INFORMATION:
HdmiCecDeviceInfo device = mService.getDeviceInfoByPath(mCurrentRoutingPath);
if (device == null) {
- maybeChangeActiveInput(mCurrentRoutingPath);
+ maybeChangeActiveInput(mService.pathToPortId(mCurrentRoutingPath));
} else {
// TODO: Also check followings and then proceed:
// if routing change was neither triggered by TV at CEC enable time, nor
case STATE_WAIT_FOR_REPORT_POWER_STATUS:
int tvPowerStatus = getTvPowerStatus();
if (isPowerStatusOnOrTransientToOn(tvPowerStatus)) {
- if (!maybeChangeActiveInput(mCurrentRoutingPath)) {
+ if (!maybeChangeActiveInput(mService.pathToPortId(mCurrentRoutingPath))) {
sendSetStreamPath();
}
}
mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
addTimer(mState, TIMEOUT_REPORT_POWER_STATUS_MS);
} else {
- maybeChangeActiveInput(mCurrentRoutingPath);
+ maybeChangeActiveInput(mService.pathToPortId(mCurrentRoutingPath));
}
}
- // Given the HDMI port id, return the port address.
- private int portToPath(int portId) {
- return mService.getPortInfo(portId).getAddress();
- }
-
private void invokeCallback(int result) {
if (mCallback == null) {
return;