diff options
| author | 2019-01-14 18:17:43 +0000 | |
|---|---|---|
| committer | 2019-01-14 18:17:43 +0000 | |
| commit | 726a71c0db4f4c842394e9251c90093402f9c3fd (patch) | |
| tree | dc95b91010ecfd2c8cc24c26a6ce5b379833c90d /services | |
| parent | 9114be68cd85d820646d67032dc3f0d27879cb88 (diff) | |
| parent | c00cd4e0342f688cf53aa58a52b82ea9d14c3ad2 (diff) | |
Merge changes Iac5d191d,Ia090bd70,I61f7bf38,Ibc9b7071,I889b6cdc, ...
* changes:
Add launchDeviceDiscovery when devices just plugged into the current device or the current device just conneted to a TV.
Fix pathToPort logic in HdmiControlService
Update the power status of an existing hdmi device with TIF once receive Report Power Status or Active Source from the existing device.
Add HDMI device info into TIF once receive report Physical Address or Set Osd Name from a new device.
Change the pathToPort(int path) method in HdmiControlService to apply to not only TV device.
Modify on hotPlug logic for Audio devices
Add array and add/remove methods to track connected device info
Modify doManualPortSwitching logic in Audio System
Add HdmiSwitchClient and move isSwitch property to system ro property
Wake up device when device is in dozing but CEC power status is on.
Diffstat (limited to 'services')
10 files changed, 469 insertions, 48 deletions
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index 6f5a19612895..ff029c194f12 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -343,14 +343,6 @@ final class Constants { static final String PROPERTY_HDMI_CEC_NEVER_ASSIGN_LOGICAL_ADDRESSES = "ro.hdmi.property_hdmi_cec_never_assign_logical_addresses"; - /** - * Property to indicate if the current device is a cec switch device. - * - * <p> Default is false. - */ - static final String PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH = - "ro.hdmi.property_is_device_hdmi_cec_switch"; - // Set to false to allow playback device to go to suspend mode even // when it's an active source. True by default. static final String PROPERTY_KEEP_AWAKE = "persist.sys.hdmi.keep_awake"; diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java index b75e75feabb3..ba21b7882afd 100755 --- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java +++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java @@ -28,7 +28,7 @@ import java.util.List; /** * Feature action that handles device discovery sequences. - * Device discovery is launched when TV device is woken from "Standby" state + * Device discovery is launched when device is woken from "Standby" state * or enabled "Control for Hdmi" from disabled state. * * <p>Device discovery goes through the following steps. @@ -89,6 +89,7 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction { private final DeviceDiscoveryCallback mCallback; private int mProcessedDeviceCount = 0; private int mTimeoutRetry = 0; + private boolean mIsTvDevice = source().mService.isTvDevice(); /** * Constructor. @@ -266,15 +267,19 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction { current.mPortId = getPortId(current.mPhysicalAddress); current.mDeviceType = params[2] & 0xFF; - tv().updateCecSwitchInfo(current.mLogicalAddress, current.mDeviceType, + // TODO(amyjojo): check if non-TV device needs to update cec switch info. + // This is to manager CEC device separately in case they don't have address. + if (mIsTvDevice) { + tv().updateCecSwitchInfo(current.mLogicalAddress, current.mDeviceType, current.mPhysicalAddress); - + } increaseProcessedDeviceCount(); checkAndProceedStage(); } private int getPortId(int physicalAddress) { - return tv().getPortId(physicalAddress); + return mIsTvDevice ? tv().getPortId(physicalAddress) + : source().getPortId(physicalAddress); } private void handleSetOsdName(HdmiCecMessage cmd) { @@ -345,7 +350,9 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction { mCallback.onDeviceDiscoveryDone(result); finish(); // Process any commands buffered while device discovery action was in progress. - tv().processAllDelayedMessages(); + if (mIsTvDevice) { + tv().processAllDelayedMessages(); + } } private void checkAndProceedStage() { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 6e1b0181adbf..32dc02613512 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -916,6 +916,11 @@ abstract class HdmiCecLocalDevice { setActivePath(mService.portIdToPath(portId)); } + // Returns the id of the port that the target device is connected to. + int getPortId(int physicalAddress) { + return mService.pathToPortId(physicalAddress); + } + @ServiceThreadOnly HdmiCecMessageCache getCecMessageCache() { assertRunOnServiceThread(); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java index 8cfe47f3d75e..0e4e3342451c 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java @@ -23,19 +23,25 @@ import android.annotation.Nullable; import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; +import android.hardware.hdmi.HdmiPortInfo; import android.hardware.hdmi.IHdmiControlCallback; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.media.AudioSystem; import android.media.tv.TvContract; import android.os.SystemProperties; +import android.util.Slog; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.hdmi.Constants.AudioCodec; +import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; +import java.io.UnsupportedEncodingException; import java.util.HashMap; +import java.util.List; /** * Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android @@ -71,6 +77,10 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { // processing. private final HashMap<Integer, String> mTvInputs = new HashMap<>(); + // Map-like container of all cec devices. + // device id is used as key of container. + private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>(); + protected HdmiCecLocalDeviceAudioSystem(HdmiControlService service) { super(service, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); mSystemAudioControlFeatureEnabled = true; @@ -86,6 +96,132 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { "com.droidlogic.tvinput/.services.Hdmi3InputService/HW7"); } + /** + * Called when a device is newly added or a new device is detected or + * an existing device is updated. + * + * @param info device info of a new device. + */ + @ServiceThreadOnly + final void addCecDevice(HdmiDeviceInfo info) { + assertRunOnServiceThread(); + HdmiDeviceInfo old = addDeviceInfo(info); + if (info.getPhysicalAddress() == mService.getPhysicalAddress()) { + // The addition of the device itself should not be notified. + // Note that different logical address could still be the same local device. + return; + } + if (old == null) { + invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); + } else if (!old.equals(info)) { + invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); + invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); + } + } + + /** + * Called when a device is removed or removal of device is detected. + * + * @param address a logical address of a device to be removed + */ + @ServiceThreadOnly + final void removeCecDevice(int address) { + assertRunOnServiceThread(); + HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address)); + + mCecMessageCache.flushMessagesFrom(address); + invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); + } + + /** + * Called when a device is updated. + * + * @param info device info of the updating device. + */ + @ServiceThreadOnly + final void updateCecDevice(HdmiDeviceInfo info) { + assertRunOnServiceThread(); + HdmiDeviceInfo old = addDeviceInfo(info); + + if (old == null) { + invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); + } else if (!old.equals(info)) { + invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE); + } + } + + /** + * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same + * logical address as new device info's. + * + * @param deviceInfo a new {@link HdmiDeviceInfo} to be added. + * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo} + * that has the same logical address as new one has. + */ + @ServiceThreadOnly + @VisibleForTesting + protected HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) { + assertRunOnServiceThread(); + HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress()); + if (oldDeviceInfo != null) { + removeDeviceInfo(deviceInfo.getId()); + } + mDeviceInfos.append(deviceInfo.getId(), deviceInfo); + return oldDeviceInfo; + } + + /** + * Remove a device info corresponding to the given {@code logicalAddress}. + * It returns removed {@link HdmiDeviceInfo} if exists. + * + * @param id id of device to be removed + * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null} + */ + @ServiceThreadOnly + private HdmiDeviceInfo removeDeviceInfo(int id) { + assertRunOnServiceThread(); + HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id); + if (deviceInfo != null) { + mDeviceInfos.remove(id); + } + return deviceInfo; + } + + /** + * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}. + * + * @param logicalAddress logical address of the device to be retrieved + * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}. + * Returns null if no logical address matched + */ + @ServiceThreadOnly + HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) { + assertRunOnServiceThread(); + return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress)); + } + + private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) { + mService.invokeDeviceEventListeners(info, status); + } + + @Override + @ServiceThreadOnly + void onHotplug(int portId, boolean connected) { + assertRunOnServiceThread(); + if (connected) { + mService.wakeUp(); + } + if (mService.getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) { + mCecMessageCache.flushAll(); + } else { + if (connected) { + launchDeviceDiscovery(); + } else { + // TODO(amyjojo): remove device from mDeviceInfo + } + } + } + @Override @ServiceThreadOnly protected void onStandby(boolean initiatedByCec, int standbyAction) { @@ -116,6 +252,8 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { boolean lastSystemAudioControlStatus = SystemProperties.getBoolean(Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL, true); systemAudioControlOnPowerOn(systemAudioControlOnPowerOnProp, lastSystemAudioControlStatus); + clearDeviceInfoList(); + launchDeviceDiscovery(); startQueuedActions(); } @@ -152,6 +290,78 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { @Override @ServiceThreadOnly + protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { + assertRunOnServiceThread(); + int path = HdmiUtils.twoBytesToInt(message.getParams()); + int address = message.getSource(); + int type = message.getParams()[2]; + + // Ignore if [Device Discovery Action] is going on. + if (hasAction(DeviceDiscoveryAction.class)) { + Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message); + return true; + } + + // Update the device info with TIF, note that the same device info could have added in + // device discovery and we do not want to override it with default OSD name. Therefore we + // need the following check to skip redundant device info updating. + HdmiDeviceInfo oldDevice = getCecDeviceInfo(address); + if (oldDevice == null || oldDevice.getPhysicalAddress() != path) { + addCecDevice(new HdmiDeviceInfo( + address, path, mService.pathToPortId(path), type, + Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(address))); + // if we are adding a new device info, send out a give osd name command + // to update the name of the device in TIF + mService.sendCecCommand( + HdmiCecMessageBuilder.buildGiveOsdNameCommand(mAddress, address)); + return true; + } + + Slog.w(TAG, "Device info exists. Not updating on Physical Address."); + return true; + } + + @Override + protected boolean handleReportPowerStatus(HdmiCecMessage command) { + int newStatus = command.getParams()[0] & 0xFF; + updateDevicePowerStatus(command.getSource(), newStatus); + return true; + } + + @Override + @ServiceThreadOnly + protected boolean handleSetOsdName(HdmiCecMessage message) { + int source = message.getSource(); + String osdName; + HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source); + // If the device is not in device list, ignore it. + if (deviceInfo == null) { + Slog.i(TAG, "No source device info for <Set Osd Name>." + message); + return true; + } + try { + osdName = new String(message.getParams(), "US-ASCII"); + } catch (UnsupportedEncodingException e) { + Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e); + return true; + } + + if (deviceInfo.getDisplayName().equals(osdName)) { + Slog.d(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message); + return true; + } + + Slog.d(TAG, "Updating device OSD name from " + + deviceInfo.getDisplayName() + + " to " + osdName); + updateCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(), + deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(), + deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName)); + return true; + } + + @Override + @ServiceThreadOnly protected boolean handleReportAudioStatus(HdmiCecMessage message) { assertRunOnServiceThread(); // TODO(amyjojo): implement report audio status handler @@ -407,8 +617,8 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { HdmiLogger.debug( "System Audio Mode change[old:%b new:%b]", mSystemAudioActivated, newSystemAudioMode); - // Wake up device if System Audio Control is turned on but device is still on standby - if (newSystemAudioMode && mService.isPowerStandbyOrTransient()) { + // Wake up device if System Audio Control is turned on + if (newSystemAudioMode) { mService.wakeUp(); } setSystemAudioMode(newSystemAudioMode); @@ -494,19 +704,20 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); return; } - getActiveSource().invalidate(); if (!mService.isControlEnabled()) { + setRoutingPort(portId); setLocalActivePort(portId); invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); return; } - int oldPath = getLocalActivePort() != Constants.CEC_SWITCH_HOME - ? getActivePathOnSwitchFromActivePortId(getLocalActivePort()) + int oldPath = getRoutingPort() != Constants.CEC_SWITCH_HOME + ? mService.portIdToPath(getRoutingPort()) : getDeviceInfo().getPhysicalAddress(); - int newPath = getActivePathOnSwitchFromActivePortId(portId); + int newPath = mService.portIdToPath(portId); if (oldPath == newPath) { return; } + setRoutingPort(portId); setLocalActivePort(portId); HdmiCecMessage routingChange = HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath); @@ -575,10 +786,8 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); return; } - // Wake up device if it is still on standby - if (mService.isPowerStandbyOrTransient()) { - mService.wakeUp(); - } + // Wake up device + mService.wakeUp(); // Check if TV supports System Audio Control. // Handle broadcasting setSystemAudioMode on or aborting message on callback. queryTvSystemAudioModeSupport(new TvSystemAudioModeSupportedCallback() { @@ -635,9 +844,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { return; } // Wake up if the current device if ready to route. - if (mService.isPowerStandbyOrTransient()) { - mService.wakeUp(); - } + mService.wakeUp(); if (portId == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) { switchToHomeTvInput(); } else if (portId == Constants.CEC_SWITCH_ARC) { @@ -725,4 +932,50 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { mAddress, routingInformationPath)); routeToInputFromPortId(getRoutingPort()); } + + protected void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) { + HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress); + if (info == null) { + Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress); + return; + } + + if (info.getDevicePowerStatus() == newPowerStatus) { + return; + } + + HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus); + // addDeviceInfo replaces old device info with new one if exists. + addDeviceInfo(newInfo); + + invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE); + } + + @ServiceThreadOnly + private void launchDeviceDiscovery() { + assertRunOnServiceThread(); + DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, + new DeviceDiscoveryCallback() { + @Override + public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) { + for (HdmiDeviceInfo info : deviceInfos) { + addCecDevice(info); + } + } + }); + addAndStartAction(action); + } + + // Clear all device info. + @ServiceThreadOnly + private void clearDeviceInfoList() { + assertRunOnServiceThread(); + for (HdmiDeviceInfo info : HdmiUtils.sparseArrayToList(mDeviceInfos)) { + if (info.getPhysicalAddress() == mService.getPhysicalAddress()) { + continue; + } + invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); + } + mDeviceInfos.clear(); + } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java index 6532e16cffe5..a95f7f1f3f3d 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java @@ -16,7 +16,10 @@ package com.android.server.hdmi; +import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH; + import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiPortInfo; import android.hardware.hdmi.IHdmiControlCallback; import android.os.SystemProperties; import android.util.Slog; @@ -42,7 +45,7 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { // Device has cec switch functionality or not. // Default is false. protected boolean mIsSwitchDevice = SystemProperties.getBoolean( - Constants.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false); + PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false); // Routing port number used for Routing Control. // This records the default routing port or the previous valid routing port. @@ -71,9 +74,11 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { @ServiceThreadOnly void onHotplug(int portId, boolean connected) { assertRunOnServiceThread(); - mCecMessageCache.flushAll(); + if (mService.getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) { + mCecMessageCache.flushAll(); + } // We'll not clear mIsActiveSource on the hotplug event to pass CETC 11.2.2-2 ~ 3. - if (mService.isPowerStandbyOrTransient()) { + if (connected) { mService.wakeUp(); } } @@ -117,6 +122,7 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { setActiveSource(activeSource); } setIsActiveSource(physicalAddress == mService.getPhysicalAddress()); + updateDevicePowerStatus(logicalAddress, HdmiControlManager.POWER_STATUS_ON); switchInputOnReceivingNewActivePath(physicalAddress); return true; } @@ -185,6 +191,13 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { // do nothing } + // Update the power status of the devices connected to the current device. + // This only works if the current device is a switch and keeps tracking the device info + // of the device connected to it. + protected void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) { + // do nothing + } + // Active source claiming needs to be handled in Service // since service can decide who will be the active source when the device supports // multiple device types in this method. @@ -204,10 +217,8 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { if (!mIsActiveSource) { return; } - // Wake up the device if the power is in standby mode - if (mService.isPowerStandbyOrTransient()) { - mService.wakeUp(); - } + // Wake up the device + mService.wakeUp(); return; } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index b91d8c637c79..a8c435086e8e 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -346,10 +346,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } } - int getPortId(int physicalAddress) { - return mService.pathToPortId(physicalAddress); - } - /** * Returns the previous port id kept to handle input switching on <Inactive Source>. */ diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 833091df5f1c..2d6e76294e4a 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -19,6 +19,7 @@ package com.android.server.hdmi; import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE; import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE; +import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH; import static com.android.server.hdmi.Constants.DISABLED; import static com.android.server.hdmi.Constants.ENABLED; import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE; @@ -754,6 +755,9 @@ public class HdmiControlService extends SystemService { mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap); mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap); + if (mMhlController == null) { + return; + } HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos(); ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length); for (HdmiPortInfo info : mhlPortInfo) { @@ -808,13 +812,31 @@ public class HdmiControlService extends SystemService { } /** - * Returns the id of HDMI port located at the top of the hierarchy of - * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance, - * the port id to be returned is the ID associated with the port address - * 0x1000 (1.0.0.0) which is the topmost path of the given routing path. + * Returns the id of HDMI port located at the current device that runs this method. + * + * For TV with physical address 0x0000, target device 0x1120, we want port physical address + * 0x1000 to get the correct port id from {@link #mPortIdMap}. For device with Physical Address + * 0x2000, target device 0x2420, we want port address 0x24000 to get the port id. + * + * <p>Return {@link Constants#INVALID_PORT_ID} if target device does not connect to. + * + * @param path the target device's physical address. + * @return the id of the port that the target device eventually connects to + * on the current device. */ int pathToPortId(int path) { - int portAddress = path & Constants.ROUTING_PATH_TOP_MASK; + int mask = 0xF000; + int finalMask = 0xF000; + int physicalAddress = getPhysicalAddress(); + int maskedAddress = physicalAddress; + + while (maskedAddress != 0) { + maskedAddress = physicalAddress & mask; + finalMask |= mask; + mask >>= 4; + } + + int portAddress = path & finalMask; return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID); } @@ -1007,8 +1029,9 @@ public class HdmiControlService extends SystemService { void onHotplug(int portId, boolean connected) { assertRunOnServiceThread(); - if (connected && !isTvDevice()) { - if (getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT && isSwitchDevice()) { + if (connected && !isTvDevice() + && getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) { + if (isSwitchDevice()) { initPortInfo(); HdmiLogger.debug("initPortInfo for switch device when onHotplug from tx."); } @@ -2130,7 +2153,7 @@ public class HdmiControlService extends SystemService { boolean isSwitchDevice() { return SystemProperties.getBoolean( - Constants.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false); + PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false); } boolean isTvDeviceEnabled() { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java index 4255e37aac2d..8607ec66a5ba 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java @@ -50,6 +50,7 @@ final class FakeNativeWrapper implements NativeWrapper { private final List<HdmiCecMessage> mResultMessages = new ArrayList<>(); private int mMyPhysicalAddress = 0; + private HdmiPortInfo[] mHdmiPortInfo = null; @Override public long nativeInit(HdmiCecController handler, MessageQueue messageQueue) { @@ -92,9 +93,11 @@ final class FakeNativeWrapper implements NativeWrapper { @Override public HdmiPortInfo[] nativeGetPortInfos(long controllerPtr) { - HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[1]; - hdmiPortInfo[0] = new HdmiPortInfo(1, 1, 0x1000, true, true, true); - return hdmiPortInfo; + if (mHdmiPortInfo == null) { + mHdmiPortInfo = new HdmiPortInfo[1]; + mHdmiPortInfo[0] = new HdmiPortInfo(1, 1, 0x1000, true, true, true); + } + return mHdmiPortInfo; } @Override @@ -131,4 +134,10 @@ final class FakeNativeWrapper implements NativeWrapper { protected void setPhysicalAddress(int physicalAddress) { mMyPhysicalAddress = physicalAddress; } + + @VisibleForTesting + protected void setPortInfo(HdmiPortInfo[] hdmiPortInfo) { + mHdmiPortInfo = new HdmiPortInfo[hdmiPortInfo.length]; + System.arraycopy(hdmiPortInfo, 0, mHdmiPortInfo, 0, hdmiPortInfo.length); + } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java index b47f269bc721..3b51a2a70f83 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -15,6 +15,9 @@ */ package com.android.server.hdmi; +import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE; +import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE; + import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; import static com.android.server.hdmi.Constants.ADDR_BROADCAST; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; @@ -25,6 +28,7 @@ import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF; import static com.google.common.truth.Truth.assertThat; +import android.hardware.hdmi.HdmiDeviceInfo; import android.media.AudioManager; import android.os.Looper; import android.os.SystemProperties; @@ -64,6 +68,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { private int mMusicMaxVolume; private boolean mMusicMute; private int mAvrPhysicalAddress; + private int mInvokeDeviceEventState; + private HdmiDeviceInfo mDeviceInfo; @Before public void setUp() { @@ -127,6 +133,18 @@ public class HdmiCecLocalDeviceAudioSystemTest { @Override void wakeUp() {} + + @Override + void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) { + mDeviceInfo = device; + mInvokeDeviceEventState = status; + } + + @Override + int pathToPortId(int path) { + // port id is not useful for the test right now + return 1; + } }; mMyLooper = mTestLooper.getLooper(); @@ -157,6 +175,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { mNativeWrapper.setPhysicalAddress(mAvrPhysicalAddress); SystemProperties.set(Constants.PROPERTY_ARC_SUPPORT, "true"); SystemProperties.set(Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE, "true"); + mInvokeDeviceEventState = 0; + mDeviceInfo = null; } @Test @@ -611,4 +631,73 @@ public class HdmiCecLocalDeviceAudioSystemTest { mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); } + + @Test + public void updateCecDevice_deviceNotExists_addDevice() { + assertThat(mInvokeDeviceEventState).isNotEqualTo(DEVICE_EVENT_ADD_DEVICE); + HdmiDeviceInfo newDevice = new HdmiDeviceInfo( + ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK, + Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1)); + + mHdmiCecLocalDeviceAudioSystem.updateCecDevice(newDevice); + assertThat(mDeviceInfo).isEqualTo(newDevice); + assertThat(mHdmiCecLocalDeviceAudioSystem + .getCecDeviceInfo(newDevice.getLogicalAddress())).isEqualTo(newDevice); + assertThat(mInvokeDeviceEventState).isEqualTo(DEVICE_EVENT_ADD_DEVICE); + } + + @Test + public void updateCecDevice_deviceExists_doNothing() { + mInvokeDeviceEventState = 0; + HdmiDeviceInfo oldDevice = new HdmiDeviceInfo( + ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK, + Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1)); + mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice); + + mHdmiCecLocalDeviceAudioSystem.updateCecDevice(oldDevice); + assertThat(mInvokeDeviceEventState).isEqualTo(0); + } + + @Test + public void updateCecDevice_deviceInfoDifferent_updateDevice() { + assertThat(mInvokeDeviceEventState).isNotEqualTo(DEVICE_EVENT_UPDATE_DEVICE); + HdmiDeviceInfo oldDevice = new HdmiDeviceInfo( + ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK, + Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1)); + mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice); + + HdmiDeviceInfo differentDevice = new HdmiDeviceInfo( + ADDR_PLAYBACK_1, 0x2100, 4, HdmiDeviceInfo.DEVICE_PLAYBACK, + Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1)); + + mHdmiCecLocalDeviceAudioSystem.updateCecDevice(differentDevice); + assertThat(mDeviceInfo).isEqualTo(differentDevice); + assertThat(mHdmiCecLocalDeviceAudioSystem + .getCecDeviceInfo(differentDevice.getLogicalAddress())).isEqualTo(differentDevice); + assertThat(mInvokeDeviceEventState).isEqualTo(DEVICE_EVENT_UPDATE_DEVICE); + } + + @Test + public void handleReportPhysicalAddress_differentPath_addDevice() { + assertThat(mInvokeDeviceEventState).isNotEqualTo(DEVICE_EVENT_ADD_DEVICE); + HdmiDeviceInfo oldDevice = new HdmiDeviceInfo( + ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK, + Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1)); + mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice); + + HdmiDeviceInfo differentDevice = new HdmiDeviceInfo( + ADDR_PLAYBACK_1, 0x2200, 1, HdmiDeviceInfo.DEVICE_PLAYBACK, + Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1)); + HdmiCecMessage reportPhysicalAddress = HdmiCecMessageBuilder + .buildReportPhysicalAddressCommand( + ADDR_PLAYBACK_1, 0x2200, HdmiDeviceInfo.DEVICE_PLAYBACK); + mHdmiCecLocalDeviceAudioSystem.handleReportPhysicalAddress(reportPhysicalAddress); + + mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice); + mTestLooper.dispatchAll(); + assertThat(mDeviceInfo).isEqualTo(differentDevice); + assertThat(mHdmiCecLocalDeviceAudioSystem + .getCecDeviceInfo(differentDevice.getLogicalAddress())).isEqualTo(differentDevice); + assertThat(mInvokeDeviceEventState).isEqualTo(DEVICE_EVENT_ADD_DEVICE); + } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index 18c9a653dc7d..67ce13fdef72 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -20,13 +20,18 @@ import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_PLAYBACK; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import android.hardware.hdmi.HdmiPortInfo; import android.os.Looper; import android.os.test.TestLooper; + import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -102,6 +107,7 @@ public class HdmiControlServiceTest { private TestLooper mTestLooper = new TestLooper(); private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>(); private boolean mStandbyMessageReceived; + private HdmiPortInfo[] mHdmiPortInfo; @Before public void SetUp() { @@ -131,6 +137,16 @@ public class HdmiControlServiceTest { mLocalDevices.add(mMyAudioSystemDevice); mLocalDevices.add(mMyPlaybackDevice); + mHdmiPortInfo = new HdmiPortInfo[4]; + mHdmiPortInfo[0] = + new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false); + mHdmiPortInfo[1] = + new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2200, true, false, false); + mHdmiPortInfo[2] = + new HdmiPortInfo(3, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false); + mHdmiPortInfo[3] = + new HdmiPortInfo(4, HdmiPortInfo.PORT_INPUT, 0x3000, true, false, false); + mNativeWrapper.setPortInfo(mHdmiPortInfo); mHdmiControlService.initPortInfo(); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); @@ -159,4 +175,24 @@ public class HdmiControlServiceTest { assertTrue(mMyPlaybackDevice.isDisabled()); assertTrue(mMyAudioSystemDevice.isDisabled()); } + + @Test + public void pathToPort_pathExists_weAreNonTv() { + mNativeWrapper.setPhysicalAddress(0x2000); + assertThat(mHdmiControlService.pathToPortId(0x2120)).isEqualTo(1); + assertThat(mHdmiControlService.pathToPortId(0x2234)).isEqualTo(2); + } + + @Test + public void pathToPort_pathExists_weAreTv() { + mNativeWrapper.setPhysicalAddress(0x0000); + assertThat(mHdmiControlService.pathToPortId(0x2120)).isEqualTo(3); + assertThat(mHdmiControlService.pathToPortId(0x3234)).isEqualTo(4); + } + + @Test + public void pathToPort_pathInvalid() { + mNativeWrapper.setPhysicalAddress(0x2000); + assertThat(mHdmiControlService.pathToPortId(0x1000)).isEqualTo(Constants.INVALID_PORT_ID); + } } |