diff options
author | 2019-01-14 18:17:43 +0000 | |
---|---|---|
committer | 2019-01-14 18:17:43 +0000 | |
commit | 726a71c0db4f4c842394e9251c90093402f9c3fd (patch) | |
tree | dc95b91010ecfd2c8cc24c26a6ce5b379833c90d | |
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.
13 files changed, 646 insertions, 48 deletions
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java index b520d2c14edc..f5d288e6a233 100644 --- a/core/java/android/hardware/hdmi/HdmiControlManager.java +++ b/core/java/android/hardware/hdmi/HdmiControlManager.java @@ -16,6 +16,8 @@ package android.hardware.hdmi; +import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH; + import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; @@ -27,6 +29,7 @@ import android.annotation.SystemService; import android.content.Context; import android.content.pm.PackageManager; import android.os.RemoteException; +import android.os.SystemProperties; import android.util.ArrayMap; import android.util.Log; @@ -264,6 +267,10 @@ public final class HdmiControlManager { private final boolean mHasTvDevice; // True if we have a logical device of type audio system hosted in the system. private final boolean mHasAudioSystemDevice; + // True if we have a logical device of type audio system hosted in the system. + private final boolean mHasSwitchDevice; + // True if it's a switch device. + private final boolean mIsSwitchDevice; /** * {@hide} - hide this constructor because it has a parameter of type IHdmiControlService, @@ -283,6 +290,9 @@ public final class HdmiControlManager { mHasTvDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_TV); mHasPlaybackDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_PLAYBACK); mHasAudioSystemDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mHasSwitchDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH); + mIsSwitchDevice = SystemProperties.getBoolean( + PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false); } private static boolean hasDeviceType(int[] types, int type) { @@ -319,6 +329,9 @@ public final class HdmiControlManager { return mHasPlaybackDevice ? new HdmiPlaybackClient(mService) : null; case HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM: return mHasAudioSystemDevice ? new HdmiAudioSystemClient(mService) : null; + case HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH: + return (mHasSwitchDevice || mIsSwitchDevice) + ? new HdmiSwitchClient(mService) : null; default: return null; } @@ -373,6 +386,24 @@ public final class HdmiControlManager { } /** + * Gets an object that represents an HDMI-CEC logical device of type switch on the system. + * + * <p>Used to send HDMI control messages to other devices like TV through HDMI bus. It is also + * possible to communicate with other logical devices hosted in the same system if the system is + * configured to host more than one type of HDMI-CEC logical devices. + * + * @return {@link HdmiSwitchClient} instance. {@code null} on failure. + * + * TODO(b/110094868): unhide for Q + * @hide + */ + @Nullable + @SuppressLint("Doclava125") + public HdmiSwitchClient getSwitchClient() { + return (HdmiSwitchClient) getClient(HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH); + } + + /** * Controls standby mode of the system. It will also try to turn on/off the connected devices if * necessary. * diff --git a/core/java/android/hardware/hdmi/HdmiSwitchClient.java b/core/java/android/hardware/hdmi/HdmiSwitchClient.java new file mode 100644 index 000000000000..1ac29736f964 --- /dev/null +++ b/core/java/android/hardware/hdmi/HdmiSwitchClient.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.hdmi; + +import android.annotation.NonNull; +import android.os.RemoteException; +import android.util.Log; + +import java.util.Collections; +import java.util.List; + +/** + * HdmiSwitchClient represents HDMI-CEC logical device of type Switch in the Android system which + * acts as switch. + * + * <p>HdmiSwitchClient has a CEC device type of HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH, + * but it is used by all Android TV devices that have multiple HDMI inputs, + * even if it is not a "pure" swicth and has another device type like TV or Player. + * + * @hide + * TODO(b/110094868): unhide and add @SystemApi for Q + */ +public class HdmiSwitchClient extends HdmiClient { + + private static final String TAG = "HdmiSwitchClient"; + + /* package */ HdmiSwitchClient(IHdmiControlService service) { + super(service); + } + + private static IHdmiControlCallback getCallbackWrapper(final SelectCallback callback) { + return new IHdmiControlCallback.Stub() { + @Override + public void onComplete(int result) { + callback.onComplete(result); + } + }; + } + + /** @hide */ + // TODO(b/110094868): unhide for Q + @Override + public int getDeviceType() { + return HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH; + } + + /** + * Selects a CEC logical device to be a new active source. + * + * @param logicalAddress logical address of the device to select + * @param callback callback to get the result with + * @throws {@link IllegalArgumentException} if the {@code callback} is null + * + * @hide + * TODO(b/110094868): unhide and add @SystemApi for Q + */ + public void deviceSelect(int logicalAddress, @NonNull SelectCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback must not be null."); + } + try { + mService.deviceSelect(logicalAddress, getCallbackWrapper(callback)); + } catch (RemoteException e) { + Log.e(TAG, "failed to select device: ", e); + } + } + + /** + * Selects a HDMI port to be a new route path. + * + * @param portId HDMI port to select + * @param callback callback to get the result with + * @throws {@link IllegalArgumentException} if the {@code callback} is null + * + * @hide + * TODO(b/110094868): unhide and add @SystemApi for Q + */ + public void portSelect(int portId, @NonNull SelectCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("Callback must not be null"); + } + try { + mService.portSelect(portId, getCallbackWrapper(callback)); + } catch (RemoteException e) { + Log.e(TAG, "failed to select port: ", e); + } + } + + /** + * Returns all the CEC devices connected to the device. + * + * <p>This only applies to device with multiple HDMI inputs + * + * @return list of {@link HdmiDeviceInfo} for connected CEC devices. Empty list is returned if + * there is none. + * + * @hide + * TODO(b/110094868): unhide and add @SystemApi for Q + */ + public List<HdmiDeviceInfo> getDeviceList() { + try { + return mService.getDeviceList(); + } catch (RemoteException e) { + Log.e("TAG", "Failed to call getDeviceList():", e); + return Collections.<HdmiDeviceInfo>emptyList(); + } + } + + /** + * Callback interface used to get the result of {@link #deviceSelect} or {@link #portSelect}. + * + * @hide + * TODO(b/110094868): unhide and add @SystemApi for Q + */ + public interface SelectCallback { + + /** + * Called when the operation is finished. + * + * @param result the result value of {@link #deviceSelect} or {@link #portSelect}. + */ + void onComplete(int result); + } +} diff --git a/core/java/com/android/internal/os/RoSystemProperties.java b/core/java/com/android/internal/os/RoSystemProperties.java index a319d83b077d..b529bbe51e93 100644 --- a/core/java/com/android/internal/os/RoSystemProperties.java +++ b/core/java/com/android/internal/os/RoSystemProperties.java @@ -30,6 +30,7 @@ public class RoSystemProperties { public static final String CONTROL_PRIVAPP_PERMISSIONS = SystemProperties.get("ro.control_privapp_permissions"); + // ------ ro.hdmi.* -------- // /** * Property to indicate if a CEC audio device should forward volume keys when system audio * mode is off. @@ -38,6 +39,14 @@ public class RoSystemProperties { SystemProperties.getBoolean( "ro.hdmi.cec_audio_device_forward_volume_keys_system_audio_mode_off", false); + /** + * Property to indicate if the current device is a cec switch device. + * + * <p> Default is false. + */ + public static final String PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH = + "ro.hdmi.property_is_device_hdmi_cec_switch"; + // ------ ro.config.* -------- // public static final boolean CONFIG_AVOID_GFX_ACCEL = SystemProperties.getBoolean("ro.config.avoid_gfx_accel", false); 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); + } } |