diff options
| author | 2020-10-26 13:43:07 +0000 | |
|---|---|---|
| committer | 2020-10-26 13:43:07 +0000 | |
| commit | 9d2aa0281d9bddca0d941978e26b59ba363909ff (patch) | |
| tree | 534fa04d6fc387300ca573217397f0f9ef74dba9 | |
| parent | 90d0b580026ac40fe75eb0da0784c6603321180b (diff) | |
| parent | 59841e039772887e2e31eede27141522127f8607 (diff) | |
Merge "Track HDMI CEC Network"
22 files changed, 1482 insertions, 1131 deletions
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java index a9e8719890ef..8980de12a31f 100755 --- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java +++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java @@ -339,7 +339,8 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction { // This is to manager CEC device separately in case they don't have address. if (mIsTvDevice) { - tv().updateCecSwitchInfo(current.mLogicalAddress, current.mDeviceType, + localDevice().mService.getHdmiCecNetwork().updateCecSwitchInfo(current.mLogicalAddress, + current.mDeviceType, current.mPhysicalAddress); } increaseProcessedDeviceCount(); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index 96679c3ab767..efe730231d36 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -32,7 +32,6 @@ import android.os.Looper; import android.os.RemoteException; import android.stats.hdmi.HdmiStatsEnums; import android.util.Slog; -import android.util.SparseArray; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.IndentingPrintWriter; @@ -97,7 +96,7 @@ final class HdmiCecController { private final Predicate<Integer> mRemoteDeviceAddressPredicate = new Predicate<Integer>() { @Override public boolean test(Integer address) { - return !isAllocatedLocalDeviceAddress(address); + return !mService.getHdmiCecNetwork().isAllocatedLocalDeviceAddress(address); } }; @@ -118,9 +117,6 @@ final class HdmiCecController { private final HdmiControlService mService; - // Stores the local CEC devices in the system. Device type is used for key. - private final SparseArray<HdmiCecLocalDevice> mLocalDevices = new SparseArray<>(); - // Stores recent CEC messages and HDMI Hotplug event history for debugging purpose. private final ArrayBlockingQueue<Dumpable> mMessageHistory = new ArrayBlockingQueue<>(MAX_HDMI_MESSAGE_HISTORY); @@ -173,12 +169,6 @@ final class HdmiCecController { nativeWrapper.setCallback(new HdmiCecCallback()); } - @ServiceThreadOnly - void addLocalDevice(int deviceType, HdmiCecLocalDevice device) { - assertRunOnServiceThread(); - mLocalDevices.put(deviceType, device); - } - /** * Allocate a new logical address of the given device type. Allocated * address will be reported through {@link AllocateAddressCallback}. @@ -269,17 +259,6 @@ final class HdmiCecController { } /** - * Return the locally hosted logical device of a given type. - * - * @param deviceType logical device type - * @return {@link HdmiCecLocalDevice} instance if the instance of the type is available; - * otherwise null. - */ - HdmiCecLocalDevice getLocalDevice(int deviceType) { - return mLocalDevices.get(deviceType); - } - - /** * Add a new logical address to the device. Device's HW should be notified * when a new logical address is assigned to a device, so that it can accept * a command having available destinations. @@ -307,18 +286,9 @@ final class HdmiCecController { @ServiceThreadOnly void clearLogicalAddress() { assertRunOnServiceThread(); - for (int i = 0; i < mLocalDevices.size(); ++i) { - mLocalDevices.valueAt(i).clearAddress(); - } mNativeWrapperImpl.nativeClearLogicalAddress(); } - @ServiceThreadOnly - void clearLocalDevices() { - assertRunOnServiceThread(); - mLocalDevices.clear(); - } - /** * Return the physical address of the device. * @@ -428,17 +398,6 @@ final class HdmiCecController { runDevicePolling(sourceAddress, pollingCandidates, retryCount, callback, allocated); } - /** - * Return a list of all {@link HdmiCecLocalDevice}s. - * - * <p>Declared as package-private. accessed by {@link HdmiControlService} only. - */ - @ServiceThreadOnly - List<HdmiCecLocalDevice> getLocalDeviceList() { - assertRunOnServiceThread(); - return HdmiUtils.sparseArrayToList(mLocalDevices); - } - private List<Integer> pickPollCandidates(int pickStrategy) { int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK; Predicate<Integer> pickPredicate = null; @@ -475,17 +434,6 @@ final class HdmiCecController { } @ServiceThreadOnly - private boolean isAllocatedLocalDeviceAddress(int address) { - assertRunOnServiceThread(); - for (int i = 0; i < mLocalDevices.size(); ++i) { - if (mLocalDevices.valueAt(i).isAddressOf(address)) { - return true; - } - } - return false; - } - - @ServiceThreadOnly private void runDevicePolling(final int sourceAddress, final List<Integer> candidates, final int retryCount, final DevicePollingCallback callback, final List<Integer> allocated) { @@ -578,7 +526,7 @@ final class HdmiCecController { if (address == Constants.ADDR_BROADCAST) { return true; } - return isAllocatedLocalDeviceAddress(address); + return mService.getHdmiCecNetwork().isAllocatedLocalDeviceAddress(address); } @ServiceThreadOnly @@ -682,7 +630,7 @@ final class HdmiCecController { private int incomingMessageDirection(int srcAddress, int dstAddress) { boolean sourceIsLocal = false; boolean destinationIsLocal = false; - for (HdmiCecLocalDevice localDevice : getLocalDeviceList()) { + for (HdmiCecLocalDevice localDevice : mService.getHdmiCecNetwork().getLocalDeviceList()) { int logicalAddress = localDevice.getDeviceInfo().getLogicalAddress(); if (logicalAddress == srcAddress) { sourceIsLocal = true; @@ -731,24 +679,9 @@ final class HdmiCecController { } void dump(final IndentingPrintWriter pw) { - final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - for (int i = 0; i < mLocalDevices.size(); ++i) { - pw.println("HdmiCecLocalDevice #" + mLocalDevices.keyAt(i) + ":"); - pw.increaseIndent(); - mLocalDevices.valueAt(i).dump(pw); - - pw.println("Active Source history:"); - pw.increaseIndent(); - for (Dumpable activeSourceEvent : mLocalDevices.valueAt(i).getActiveSourceHistory()) { - activeSourceEvent.dump(pw, sdf); - } - pw.decreaseIndent(); - pw.decreaseIndent(); - } - pw.println("CEC message history:"); pw.increaseIndent(); + final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); for (Dumpable record : mMessageHistory) { record.dump(pw, sdf); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 946fb0d00d60..62a67b6243d7 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -16,6 +16,7 @@ package com.android.server.hdmi; +import android.annotation.CallSuper; import android.annotation.Nullable; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; @@ -471,8 +472,27 @@ abstract class HdmiCecLocalDevice { return false; } + @CallSuper protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { - return false; + // <Report Physical Address> is also handled in HdmiCecNetwork to update the local network + // state + + int address = message.getSource(); + + // 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; + } + + HdmiDeviceInfo cecDeviceInfo = mService.getHdmiCecNetwork().getCecDeviceInfo(address); + // If no non-default display name is available for the device, request the devices OSD name. + if (cecDeviceInfo.getDisplayName().equals(HdmiUtils.getDefaultDeviceName(address))) { + mService.sendCecCommand( + HdmiCecMessageBuilder.buildGiveOsdNameCommand(mAddress, address)); + } + + return true; } protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { @@ -708,7 +728,7 @@ abstract class HdmiCecLocalDevice { } protected boolean handleSetOsdName(HdmiCecMessage message) { - // The default behavior of <Set Osd Name> is doing nothing. + // <Set OSD name> is also handled in HdmiCecNetwork to update the local network state return true; } @@ -724,7 +744,8 @@ abstract class HdmiCecLocalDevice { } protected boolean handleReportPowerStatus(HdmiCecMessage message) { - return false; + // <Report Power Status> is also handled in HdmiCecNetwork to update the local network state + return true; } protected boolean handleTimerStatus(HdmiCecMessage message) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java index 29bdd6cb40c3..fe4fd3805994 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java @@ -37,7 +37,6 @@ import android.os.SystemProperties; import android.provider.Settings.Global; import android.sysprop.HdmiProperties; import android.util.Slog; -import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -54,15 +53,12 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.stream.Collectors; - /** * Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android * system. @@ -104,14 +100,6 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { @GuardedBy("mLock") private final HashMap<String, HdmiDeviceInfo> mTvInputsToDeviceInfo = new HashMap<>(); - // Copy of mDeviceInfos to guarantee thread-safety. - @GuardedBy("mLock") - private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList(); - - // Map-like container of all cec devices. - // device id is used as key of container. - private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>(); - // Message buffer used to buffer selected messages to process later. <Active Source> // from a source device, for instance, needs to be buffered if the device is not // discovered yet. The buffered commands are taken out and when they are ready to @@ -187,135 +175,6 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { return info != null; } - /** - * 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(); - mService.checkLogicalAddressConflictAndReallocate(deviceInfo.getLogicalAddress()); - HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress()); - if (oldDeviceInfo != null) { - removeDeviceInfo(deviceInfo.getId()); - } - mDeviceInfos.append(deviceInfo.getId(), deviceInfo); - updateSafeDeviceInfoList(); - 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); - } - updateSafeDeviceInfoList(); - 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)); - } - - @ServiceThreadOnly - private void updateSafeDeviceInfoList() { - assertRunOnServiceThread(); - List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos); - synchronized (mLock) { - mSafeAllDeviceInfos = copiedDevices; - } - } - - @GuardedBy("mLock") - List<HdmiDeviceInfo> getSafeCecDevicesLocked() { - ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>(); - for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { - infoList.add(info); - } - return infoList; - } - - private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) { - mService.invokeDeviceEventListeners(info, status); - } - @Override @ServiceThreadOnly void onHotplug(int portId, boolean connected) { @@ -342,7 +201,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { } // Update with TIF on the device removal. TIF callback will update // mPortIdToTvInputs and mPortIdToTvInputs. - removeCecDevice(info.getLogicalAddress()); + mService.getHdmiCecNetwork().removeCecDevice(this, info.getLogicalAddress()); } } @@ -399,7 +258,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { boolean lastSystemAudioControlStatus = SystemProperties.getBoolean(Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL, true); systemAudioControlOnPowerOn(systemAudioControlOnPowerOnProp, lastSystemAudioControlStatus); - clearDeviceInfoList(); + mService.getHdmiCecNetwork().clearDeviceList(); launchDeviceDiscovery(); startQueuedActions(); } @@ -458,7 +317,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { // If the new Active Source is under the current device, check if the device info and the TV // input is ready to switch to the new Active Source. If not ready, buffer the cec command // to handle later when the device is ready. - HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress); + HdmiDeviceInfo info = mService.getHdmiCecNetwork().getCecDeviceInfo(logicalAddress); if (info == null) { HdmiLogger.debug("Device info %X not found; buffering the command", logicalAddress); mDelayedMessageBuffer.add(message); @@ -474,79 +333,6 @@ 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, "")); - // 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() != null - && 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 handleInitiateArc(HdmiCecMessage message) { assertRunOnServiceThread(); // TODO(amyjojo): implement initiate arc handler @@ -864,14 +650,9 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { != HdmiUtils.TARGET_NOT_UNDER_LOCAL_DEVICE) { return true; } - boolean isDeviceInCecDeviceList = false; - for (HdmiDeviceInfo info : HdmiUtils.sparseArrayToList(mDeviceInfos)) { - if (info.getPhysicalAddress() == sourcePhysicalAddress) { - isDeviceInCecDeviceList = true; - break; - } - } - if (!isDeviceInCecDeviceList) { + HdmiDeviceInfo safeDeviceInfoByPath = + mService.getHdmiCecNetwork().getSafeDeviceInfoByPath(sourcePhysicalAddress); + if (safeDeviceInfoByPath == null) { switchInputOnReceivingNewActivePath(sourcePhysicalAddress); } } @@ -1345,24 +1126,6 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { 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(); @@ -1375,27 +1138,13 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { @Override public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) { for (HdmiDeviceInfo info : deviceInfos) { - addCecDevice(info); + mService.getHdmiCecNetwork().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(); - updateSafeDeviceInfoList(); - } - @Override protected void dump(IndentingPrintWriter pw) { pw.println("HdmiCecLocalDeviceAudioSystem:"); @@ -1409,7 +1158,6 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { pw.println("mLocalActivePort: " + getLocalActivePort()); HdmiUtils.dumpMap(pw, "mPortIdToTvInputs:", mPortIdToTvInputs); HdmiUtils.dumpMap(pw, "mTvInputsToDeviceInfo:", mTvInputsToDeviceInfo); - HdmiUtils.dumpSparseArray(pw, "mDeviceInfos:", mDeviceInfos); pw.decreaseIndent(); super.dump(pw); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 0325ad929849..93cdca2d0c83 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -42,9 +42,7 @@ import android.media.AudioSystem; import android.media.tv.TvInputInfo; import android.media.tv.TvInputManager.TvInputCallback; import android.provider.Settings.Global; -import android.util.ArraySet; import android.util.Slog; -import android.util.SparseArray; import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; @@ -53,13 +51,8 @@ import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; import com.android.server.hdmi.HdmiControlService.SendMessageCallback; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; import java.util.HashMap; -import java.util.Iterator; import java.util.List; /** @@ -95,37 +88,18 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @GuardedBy("mLock") private boolean mSystemAudioMute = false; - // Copy of mDeviceInfos to guarantee thread-safety. - @GuardedBy("mLock") - private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList(); - // All external cec input(source) devices. Does not include system audio device. - @GuardedBy("mLock") - private List<HdmiDeviceInfo> mSafeExternalInputs = Collections.emptyList(); - - // Map-like container of all cec devices including local ones. - // device id is used as key of container. - // This is not thread-safe. For external purpose use mSafeDeviceInfos. - private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>(); - // If true, TV going to standby mode puts other devices also to standby. private boolean mAutoDeviceOff; // If true, TV wakes itself up when receiving <Text/Image View On>. private boolean mAutoWakeup; - // List of the logical address of local CEC devices. Unmodifiable, thread-safe. - private List<Integer> mLocalDeviceAddresses; - private final HdmiCecStandbyModeHandler mStandbyHandler; // If true, do not do routing control/send active source for internal source. // Set to true when the device was woken up by <Text/Image View On>. private boolean mSkipRoutingControl; - // Set of physical addresses of CEC switches on the CEC bus. Managed independently from - // other CEC devices since they might not have logical address. - private final ArraySet<Integer> mCecSwitches = new ArraySet<Integer>(); - // Message buffer used to buffer selected messages to process later. <Active Source> // from a source device, for instance, needs to be buffered if the device is not // discovered yet. The buffered commands are taken out and when they are ready to @@ -205,12 +179,12 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mAddress, mService.getPhysicalAddress(), mDeviceType)); mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand( mAddress, mService.getVendorId())); - mCecSwitches.add(mService.getPhysicalAddress()); // TV is a CEC switch too. + mService.getHdmiCecNetwork().addCecSwitch( + mService.getHdmiCecNetwork().getPhysicalAddress()); // TV is a CEC switch too. mTvInputs.clear(); mSkipRoutingControl = (reason == HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE); launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC && reason != HdmiControlService.INITIATED_BY_BOOT_UP); - mLocalDeviceAddresses = initLocalDeviceAddresses(); resetSelectRequestBuffer(); launchDeviceDiscovery(); startQueuedActions(); @@ -220,17 +194,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } @ServiceThreadOnly - private List<Integer> initLocalDeviceAddresses() { - assertRunOnServiceThread(); - List<Integer> addresses = new ArrayList<>(); - for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) { - addresses.add(device.getDeviceInfo().getLogicalAddress()); - } - return Collections.unmodifiableList(addresses); - } - - - @ServiceThreadOnly public void setSelectRequestBuffer(SelectRequestBuffer requestBuffer) { assertRunOnServiceThread(); mSelectRequestBuffer = requestBuffer; @@ -272,7 +235,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @ServiceThreadOnly void deviceSelect(int id, IHdmiControlCallback callback) { assertRunOnServiceThread(); - HdmiDeviceInfo targetDevice = mDeviceInfos.get(id); + HdmiDeviceInfo targetDevice = mService.getHdmiCecNetwork().getDeviceInfo(id); if (targetDevice == null) { invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); return; @@ -335,7 +298,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } setActiveSource(newActive, caller); int logicalAddress = newActive.logicalAddress; - if (getCecDeviceInfo(logicalAddress) != null && logicalAddress != mAddress) { + if (mService.getHdmiCecNetwork().getCecDeviceInfo(logicalAddress) != null + && logicalAddress != mAddress) { if (mService.pathToPortId(newActive.physicalAddress) == getActivePortId()) { setPrevPortId(getActivePortId()); } @@ -374,7 +338,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // Show OSD port change banner if (notifyInputChange) { ActiveSource activeSource = getActiveSource(); - HdmiDeviceInfo info = getCecDeviceInfo(activeSource.logicalAddress); + HdmiDeviceInfo info = mService.getHdmiCecNetwork().getCecDeviceInfo( + activeSource.logicalAddress); if (info == null) { info = mService.getDeviceInfoByPort(getActivePortId()); if (info == null) { @@ -442,7 +407,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (getActiveSource().isValid()) { return getActiveSource().logicalAddress; } - HdmiDeviceInfo info = getDeviceInfoByPath(getActivePath()); + HdmiDeviceInfo info = mService.getHdmiCecNetwork().getDeviceInfoByPath(getActivePath()); if (info != null) { return info.getLogicalAddress(); } @@ -455,7 +420,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { assertRunOnServiceThread(); int logicalAddress = message.getSource(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); - HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress); + HdmiDeviceInfo info = mService.getHdmiCecNetwork().getCecDeviceInfo(logicalAddress); if (info == null) { if (!handleNewDeviceAtTheTailOfActivePath(physicalAddress)) { HdmiLogger.debug("Device info %X not found; buffering the command", logicalAddress); @@ -463,7 +428,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } } else if (isInputReady(info.getId()) || info.getDeviceType() == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) { - updateDevicePowerStatus(logicalAddress, HdmiControlManager.POWER_STATUS_ON); + mService.getHdmiCecNetwork().updateDevicePowerStatus(logicalAddress, + HdmiControlManager.POWER_STATUS_ON); ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress); ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType()); } else { @@ -490,7 +456,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (portId != Constants.INVALID_PORT_ID) { // TODO: Do this only if TV is not showing multiview like PIP/PAP. - HdmiDeviceInfo inactiveSource = getCecDeviceInfo(message.getSource()); + HdmiDeviceInfo inactiveSource = mService.getHdmiCecNetwork().getCecDeviceInfo( + message.getSource()); if (inactiveSource == null) { return true; } @@ -546,42 +513,20 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } @Override - @ServiceThreadOnly protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { - assertRunOnServiceThread(); + super.handleReportPhysicalAddress(message); int path = HdmiUtils.twoBytesToInt(message.getParams()); int address = message.getSource(); int type = message.getParams()[2]; - if (updateCecSwitchInfo(address, type, path)) return true; - - // 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; - } - - if (!isInDeviceList(address, path)) { + if (!mService.getHdmiCecNetwork().isInDeviceList(address, path)) { handleNewDeviceAtTheTailOfActivePath(path); } - - // Add the device ahead with default information to handle <Active Source> - // promptly, rather than waiting till the new device action is finished. - HdmiDeviceInfo deviceInfo = new HdmiDeviceInfo(address, path, getPortId(path), type, - Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(address)); - addCecDevice(deviceInfo); startNewDeviceAction(ActiveSource.of(address, path), type); return true; } @Override - protected boolean handleReportPowerStatus(HdmiCecMessage command) { - int newStatus = command.getParams()[0] & 0xFF; - updateDevicePowerStatus(command.getSource(), newStatus); - return true; - } - - @Override protected boolean handleTimerStatus(HdmiCecMessage message) { // Do nothing. return true; @@ -593,19 +538,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return true; } - boolean updateCecSwitchInfo(int address, int type, int path) { - if (address == Constants.ADDR_UNREGISTERED - && type == HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH) { - mCecSwitches.add(path); - updateSafeDeviceInfoList(); - return true; // Pure switch does not need further processing. Return here. - } - if (type == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) { - mCecSwitches.add(path); - } - return false; - } - void startNewDeviceAction(ActiveSource activeSource, int deviceType) { for (NewDeviceAction action : getActions(NewDeviceAction.class)) { // If there is new device action which has the same logical address and path @@ -719,35 +651,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return handleTextViewOn(message); } - @Override - @ServiceThreadOnly - protected boolean handleSetOsdName(HdmiCecMessage message) { - int source = message.getSource(); - HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source); - // If the device is not in device list, ignore it. - if (deviceInfo == null) { - Slog.e(TAG, "No source device info for <Set Osd Name>." + message); - return true; - } - String osdName = null; - 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.i(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message); - return true; - } - - addCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(), - deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(), - deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName)); - return true; - } - @ServiceThreadOnly private void launchDeviceDiscovery() { assertRunOnServiceThread(); @@ -757,14 +660,14 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @Override public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) { for (HdmiDeviceInfo info : deviceInfos) { - addCecDevice(info); + mService.getHdmiCecNetwork().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()) { - addCecDevice(device.getDeviceInfo()); + mService.getHdmiCecNetwork().addCecDevice(device.getDeviceInfo()); } mSelectRequestBuffer.process(); @@ -798,11 +701,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @ServiceThreadOnly private void clearDeviceInfoList() { assertRunOnServiceThread(); - for (HdmiDeviceInfo info : mSafeExternalInputs) { - invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); - } - mDeviceInfos.clear(); - updateSafeDeviceInfoList(); + mService.getHdmiCecNetwork().clearDeviceList(); } @ServiceThreadOnly @@ -1224,170 +1123,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { && getAvrDeviceInfo() != null; } - /** - * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same - * logical address as new device info's. - * - * <p>Declared as package-private. accessed by {@link HdmiControlService} only. - * - * @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 - private HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) { - assertRunOnServiceThread(); - HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress()); - if (oldDeviceInfo != null) { - removeDeviceInfo(deviceInfo.getId()); - } - mDeviceInfos.append(deviceInfo.getId(), deviceInfo); - updateSafeDeviceInfoList(); - return oldDeviceInfo; - } - - /** - * Remove a device info corresponding to the given {@code logicalAddress}. - * It returns removed {@link HdmiDeviceInfo} if exists. - * - * <p>Declared as package-private. accessed by {@link HdmiControlService} only. - * - * @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); - } - updateSafeDeviceInfoList(); - return deviceInfo; - } - - /** - * Return a list of all {@link HdmiDeviceInfo}. - * - * <p>Declared as package-private. accessed by {@link HdmiControlService} only. - * This is not thread-safe. For thread safety, call {@link #getSafeExternalInputsLocked} which - * does not include local device. - */ - @ServiceThreadOnly - List<HdmiDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) { - assertRunOnServiceThread(); - if (includeLocalDevice) { - return HdmiUtils.sparseArrayToList(mDeviceInfos); - } else { - ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>(); - for (int i = 0; i < mDeviceInfos.size(); ++i) { - HdmiDeviceInfo info = mDeviceInfos.valueAt(i); - if (!isLocalDeviceAddress(info.getLogicalAddress())) { - infoList.add(info); - } - } - return infoList; - } - } - - /** - * Return external input devices. - */ - @GuardedBy("mLock") - List<HdmiDeviceInfo> getSafeExternalInputsLocked() { - return mSafeExternalInputs; - } - - @ServiceThreadOnly - private void updateSafeDeviceInfoList() { - assertRunOnServiceThread(); - List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos); - List<HdmiDeviceInfo> externalInputs = getInputDevices(); - synchronized (mLock) { - mSafeAllDeviceInfos = copiedDevices; - mSafeExternalInputs = externalInputs; - } - } - - /** - * Return a list of external cec input (source) devices. - * - * <p>Note that this effectively excludes non-source devices like system audio, - * secondary TV. - */ - private List<HdmiDeviceInfo> getInputDevices() { - ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>(); - for (int i = 0; i < mDeviceInfos.size(); ++i) { - HdmiDeviceInfo info = mDeviceInfos.valueAt(i); - if (isLocalDeviceAddress(info.getLogicalAddress())) { - continue; - } - if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) { - infoList.add(info); - } - } - return infoList; - } - - // Check if we are hiding CEC devices connected to a legacy (non-CEC) switch. - // Returns true if the policy is set to true, and the device to check does not have - // a parent CEC device (which should be the CEC-enabled switch) in the list. - private boolean hideDevicesBehindLegacySwitch(HdmiDeviceInfo info) { - return HdmiConfig.HIDE_DEVICES_BEHIND_LEGACY_SWITCH - && !isConnectedToCecSwitch(info.getPhysicalAddress(), mCecSwitches); - } - - private static boolean isConnectedToCecSwitch(int path, Collection<Integer> switches) { - for (int switchPath : switches) { - if (isParentPath(switchPath, path)) { - return true; - } - } - return false; - } - - private static boolean isParentPath(int parentPath, int childPath) { - // (A000, AB00) (AB00, ABC0), (ABC0, ABCD) - // If child's last non-zero nibble is removed, the result equals to the parent. - for (int i = 0; i <= 12; i += 4) { - int nibble = (childPath >> i) & 0xF; - if (nibble != 0) { - int parentNibble = (parentPath >> i) & 0xF; - return parentNibble == 0 && (childPath >> i+4) == (parentPath >> i+4); - } - } - return false; - } - - private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) { - if (!hideDevicesBehindLegacySwitch(info)) { - mService.invokeDeviceEventListeners(info, status); - } - } - - private boolean isLocalDeviceAddress(int address) { - return mLocalDeviceAddresses.contains(address); - } - @ServiceThreadOnly HdmiDeviceInfo getAvrDeviceInfo() { assertRunOnServiceThread(); - return getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); - } - - /** - * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}. - * - * This is not thread-safe. For thread safety, call {@link #getSafeCecDeviceInfo(int)}. - * - * @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)); + return mService.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); } boolean hasSystemAudioDevice() { @@ -1395,74 +1134,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } HdmiDeviceInfo getSafeAvrDeviceInfo() { - return getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); - } - - /** - * Thread safe version of {@link #getCecDeviceInfo(int)}. - * - * @param logicalAddress logical address to be retrieved - * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}. - * Returns null if no logical address matched - */ - HdmiDeviceInfo getSafeCecDeviceInfo(int logicalAddress) { - synchronized (mLock) { - for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { - if (info.isCecDevice() && info.getLogicalAddress() == logicalAddress) { - return info; - } - } - return null; - } - } - - @GuardedBy("mLock") - List<HdmiDeviceInfo> getSafeCecDevicesLocked() { - ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>(); - for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { - if (isLocalDeviceAddress(info.getLogicalAddress())) { - continue; - } - infoList.add(info); - } - return infoList; - } - - /** - * Called when a device is newly added or a new device is detected or - * 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.getLogicalAddress() == mAddress) { - // The addition of TV device itself should not be notified. - 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); - } + return mService.getHdmiCecNetwork().getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); } - /** - * 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); - } @ServiceThreadOnly void handleRemoveActiveRoutingPath(int path) { @@ -1501,72 +1175,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } } - /** - * Returns the {@link HdmiDeviceInfo} instance whose physical address matches - * the given routing path. CEC devices use routing path for its physical address to - * describe the hierarchy of the devices in the network. - * - * @param path routing path or physical address - * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null - */ - @ServiceThreadOnly - final HdmiDeviceInfo getDeviceInfoByPath(int path) { - assertRunOnServiceThread(); - for (HdmiDeviceInfo info : getDeviceInfoList(false)) { - if (info.getPhysicalAddress() == path) { - return info; - } - } - return null; - } - - /** - * Returns the {@link HdmiDeviceInfo} instance whose physical address matches - * the given routing path. This is the version accessible safely from threads - * other than service thread. - * - * @param path routing path or physical address - * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null - */ - HdmiDeviceInfo getSafeDeviceInfoByPath(int path) { - synchronized (mLock) { - for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { - if (info.getPhysicalAddress() == path) { - return info; - } - } - return null; - } - } - - /** - * 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 logicalAddress logical address of a device to be searched - * @param physicalAddress physical address of a device to be searched - * @return true if exist; otherwise false - */ - @ServiceThreadOnly - boolean isInDeviceList(int logicalAddress, int physicalAddress) { - assertRunOnServiceThread(); - HdmiDeviceInfo device = getCecDeviceInfo(logicalAddress); - if (device == null) { - return false; - } - return device.getPhysicalAddress() == physicalAddress; - } - @Override @ServiceThreadOnly void onHotplug(int portId, boolean connected) { assertRunOnServiceThread(); - - if (!connected) { - removeCecSwitches(portId); - } - // Turning System Audio Mode off when the AVR is unlugged or standby. // When the device is not unplugged but reawaken from standby, we check if the System // Audio Control Feature is enabled or not then decide if turning SAM on/off accordingly. @@ -1588,16 +1200,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } } - private void removeCecSwitches(int portId) { - Iterator<Integer> it = mCecSwitches.iterator(); - while (!it.hasNext()) { - int path = it.next(); - if (pathToPortId(path) == portId) { - it.remove(); - } - } - } - @Override @ServiceThreadOnly void setAutoDeviceOff(boolean enabled) { @@ -1765,7 +1367,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } private boolean checkRecorder(int recorderAddress) { - HdmiDeviceInfo device = getCecDeviceInfo(recorderAddress); + HdmiDeviceInfo device = mService.getHdmiCecNetwork().getCecDeviceInfo(recorderAddress); return (device != null) && (HdmiUtils.getTypeFromAddress(recorderAddress) == HdmiDeviceInfo.DEVICE_RECORDER); @@ -1871,24 +1473,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { }); } - 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); - } - @Override protected boolean handleMenuStatus(HdmiCecMessage message) { // Do nothing and just return true not to prevent from responding <Feature Abort>. @@ -1897,7 +1481,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @Override protected void sendStandby(int deviceId) { - HdmiDeviceInfo targetDevice = mDeviceInfos.get(deviceId); + HdmiDeviceInfo targetDevice = mService.getHdmiCecNetwork().getDeviceInfo(deviceId); if (targetDevice == null) { return; } @@ -1934,11 +1518,5 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { pw.println("mAutoWakeup: " + mAutoWakeup); pw.println("mSkipRoutingControl: " + mSkipRoutingControl); pw.println("mPrevPortId: " + mPrevPortId); - pw.println("CEC devices:"); - pw.increaseIndent(); - for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { - pw.println(info); - } - pw.decreaseIndent(); } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java new file mode 100644 index 000000000000..416302419a71 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java @@ -0,0 +1,822 @@ +/* + * Copyright (C) 2020 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 com.android.server.hdmi; + +import static com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; + +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; +import android.hardware.hdmi.HdmiPortInfo; +import android.os.Handler; +import android.os.Looper; +import android.util.ArraySet; +import android.util.Slog; +import android.util.SparseArray; +import android.util.SparseIntArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.IndentingPrintWriter; + +import java.io.UnsupportedEncodingException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; + +/** + * Holds information about the current state of the HDMI CEC network. It is the sole source of + * truth for device information in the CEC network. + * + * This information includes: + * - All local devices + * - All HDMI ports, their capabilities and status + * - All devices connected to the CEC bus + * + * This class receives all incoming CEC messages and passively listens to device updates to fill + * out the above information. + * This class should not take any active action in sending CEC messages. + * + * Note that the information cached in this class is not guaranteed to be up-to-date, especially OSD + * names, power states can be outdated. + */ +class HdmiCecNetwork { + private static final String TAG = "HdmiCecNetwork"; + + protected final Object mLock; + private final HdmiControlService mHdmiControlService; + private final HdmiCecController mHdmiCecController; + private final HdmiMhlControllerStub mHdmiMhlController; + private final Handler mHandler; + // Stores the local CEC devices in the system. Device type is used for key. + private final SparseArray<HdmiCecLocalDevice> mLocalDevices = new SparseArray<>(); + + // Map-like container of all cec devices including local ones. + // device id is used as key of container. + // This is not thread-safe. For external purpose use mSafeDeviceInfos. + private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>(); + // Set of physical addresses of CEC switches on the CEC bus. Managed independently from + // other CEC devices since they might not have logical address. + private final ArraySet<Integer> mCecSwitches = new ArraySet<>(); + // Copy of mDeviceInfos to guarantee thread-safety. + @GuardedBy("mLock") + private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList(); + // All external cec input(source) devices. Does not include system audio device. + @GuardedBy("mLock") + private List<HdmiDeviceInfo> mSafeExternalInputs = Collections.emptyList(); + // HDMI port information. Stored in the unmodifiable list to keep the static information + // from being modified. + @GuardedBy("mLock") + private List<HdmiPortInfo> mPortInfo = Collections.emptyList(); + + // Map from path(physical address) to port ID. + private UnmodifiableSparseIntArray mPortIdMap; + + // Map from port ID to HdmiPortInfo. + private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap; + + // Map from port ID to HdmiDeviceInfo. + private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap; + + HdmiCecNetwork(HdmiControlService hdmiControlService, + HdmiCecController hdmiCecController, + HdmiMhlControllerStub hdmiMhlController) { + mHdmiControlService = hdmiControlService; + mHdmiCecController = hdmiCecController; + mHdmiMhlController = hdmiMhlController; + mHandler = new Handler(mHdmiControlService.getServiceLooper()); + mLock = mHdmiControlService.getServiceLock(); + } + + private static boolean isConnectedToCecSwitch(int path, Collection<Integer> switches) { + for (int switchPath : switches) { + if (isParentPath(switchPath, path)) { + return true; + } + } + return false; + } + + private static boolean isParentPath(int parentPath, int childPath) { + // (A000, AB00) (AB00, ABC0), (ABC0, ABCD) + // If child's last non-zero nibble is removed, the result equals to the parent. + for (int i = 0; i <= 12; i += 4) { + int nibble = (childPath >> i) & 0xF; + if (nibble != 0) { + int parentNibble = (parentPath >> i) & 0xF; + return parentNibble == 0 && (childPath >> i + 4) == (parentPath >> i + 4); + } + } + return false; + } + + public void addLocalDevice(int deviceType, HdmiCecLocalDevice device) { + mLocalDevices.put(deviceType, device); + } + + /** + * Return the locally hosted logical device of a given type. + * + * @param deviceType logical device type + * @return {@link HdmiCecLocalDevice} instance if the instance of the type is available; + * otherwise null. + */ + HdmiCecLocalDevice getLocalDevice(int deviceType) { + return mLocalDevices.get(deviceType); + } + + /** + * Return a list of all {@link HdmiCecLocalDevice}s. + * + * <p>Declared as package-private. accessed by {@link HdmiControlService} only. + */ + @ServiceThreadOnly + List<HdmiCecLocalDevice> getLocalDeviceList() { + assertRunOnServiceThread(); + return HdmiUtils.sparseArrayToList(mLocalDevices); + } + + @ServiceThreadOnly + boolean isAllocatedLocalDeviceAddress(int address) { + assertRunOnServiceThread(); + for (int i = 0; i < mLocalDevices.size(); ++i) { + if (mLocalDevices.valueAt(i).isAddressOf(address)) { + return true; + } + } + return false; + } + + /** + * Clear all logical addresses registered in the device. + * + * <p>Declared as package-private. accessed by {@link HdmiControlService} only. + */ + @ServiceThreadOnly + void clearLogicalAddress() { + assertRunOnServiceThread(); + for (int i = 0; i < mLocalDevices.size(); ++i) { + mLocalDevices.valueAt(i).clearAddress(); + } + } + + @ServiceThreadOnly + void clearLocalDevices() { + assertRunOnServiceThread(); + mLocalDevices.clear(); + } + + public HdmiDeviceInfo getDeviceInfo(int id) { + return mDeviceInfos.get(id); + } + + /** + * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same + * logical address as new device info's. + * + * <p>Declared as package-private. accessed by {@link HdmiControlService} only. + * + * @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 + private HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) { + assertRunOnServiceThread(); + HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress()); + mHdmiControlService.checkLogicalAddressConflictAndReallocate( + deviceInfo.getLogicalAddress(), deviceInfo.getPhysicalAddress()); + if (oldDeviceInfo != null) { + removeDeviceInfo(deviceInfo.getId()); + } + mDeviceInfos.append(deviceInfo.getId(), deviceInfo); + updateSafeDeviceInfoList(); + return oldDeviceInfo; + } + + /** + * Remove a device info corresponding to the given {@code logicalAddress}. + * It returns removed {@link HdmiDeviceInfo} if exists. + * + * <p>Declared as package-private. accessed by {@link HdmiControlService} only. + * + * @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); + } + updateSafeDeviceInfoList(); + return deviceInfo; + } + + /** + * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}. + * + * This is not thread-safe. For thread safety, call {@link #getSafeCecDeviceInfo(int)}. + * + * @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)); + } + + /** + * Called when a device is newly added or a new device is detected or + * existing device is updated. + * + * @param info device info of a new device. + */ + @ServiceThreadOnly + final void addCecDevice(HdmiDeviceInfo info) { + assertRunOnServiceThread(); + HdmiDeviceInfo old = addDeviceInfo(info); + if (isLocalDeviceAddress(info.getLogicalAddress())) { + // The addition of a local device should not notify listeners + 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); + } + } + + private void invokeDeviceEventListener(HdmiDeviceInfo info, int event) { + if (!hideDevicesBehindLegacySwitch(info)) { + mHdmiControlService.invokeDeviceEventListeners(info, event); + } + } + + /** + * 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); + } + } + + @ServiceThreadOnly + private void updateSafeDeviceInfoList() { + assertRunOnServiceThread(); + List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos); + List<HdmiDeviceInfo> externalInputs = getInputDevices(); + mSafeAllDeviceInfos = copiedDevices; + mSafeExternalInputs = externalInputs; + } + + /** + * Return a list of all {@link HdmiDeviceInfo}. + * + * <p>Declared as package-private. accessed by {@link HdmiControlService} only. + * This is not thread-safe. For thread safety, call {@link #getSafeExternalInputsLocked} which + * does not include local device. + */ + @ServiceThreadOnly + List<HdmiDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) { + assertRunOnServiceThread(); + if (includeLocalDevice) { + return HdmiUtils.sparseArrayToList(mDeviceInfos); + } else { + ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>(); + for (int i = 0; i < mDeviceInfos.size(); ++i) { + HdmiDeviceInfo info = mDeviceInfos.valueAt(i); + if (!isLocalDeviceAddress(info.getLogicalAddress())) { + infoList.add(info); + } + } + return infoList; + } + } + + /** + * Return external input devices. + */ + @GuardedBy("mLock") + List<HdmiDeviceInfo> getSafeExternalInputsLocked() { + return mSafeExternalInputs; + } + + /** + * Return a list of external cec input (source) devices. + * + * <p>Note that this effectively excludes non-source devices like system audio, + * secondary TV. + */ + private List<HdmiDeviceInfo> getInputDevices() { + ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>(); + for (int i = 0; i < mDeviceInfos.size(); ++i) { + HdmiDeviceInfo info = mDeviceInfos.valueAt(i); + if (isLocalDeviceAddress(info.getLogicalAddress())) { + continue; + } + if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) { + infoList.add(info); + } + } + return infoList; + } + + // Check if we are hiding CEC devices connected to a legacy (non-CEC) switch. + // This only applies to TV devices. + // Returns true if the policy is set to true, and the device to check does not have + // a parent CEC device (which should be the CEC-enabled switch) in the list. + private boolean hideDevicesBehindLegacySwitch(HdmiDeviceInfo info) { + return isLocalDeviceAddress(Constants.ADDR_TV) + && HdmiConfig.HIDE_DEVICES_BEHIND_LEGACY_SWITCH + && !isConnectedToCecSwitch(info.getPhysicalAddress(), getCecSwitches()); + } + + /** + * 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(HdmiCecLocalDevice localDevice, int address) { + assertRunOnServiceThread(); + HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address)); + + localDevice.mCecMessageCache.flushMessagesFrom(address); + invokeDeviceEventListener(info, + HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); + } + + public 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; + } + + updateCecDevice(HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus)); + } + + /** + * Whether a device of the specified physical address is connected to ARC enabled port. + */ + boolean isConnectedToArcPort(int physicalAddress) { + int portId = physicalAddressToPortId(physicalAddress); + if (portId != Constants.INVALID_PORT_ID) { + return mPortInfoMap.get(portId).isArcSupported(); + } + return false; + } + + + // Initialize HDMI port information. Combine the information from CEC and MHL HAL and + // keep them in one place. + @ServiceThreadOnly + @VisibleForTesting + public void initPortInfo() { + assertRunOnServiceThread(); + HdmiPortInfo[] cecPortInfo = null; + // CEC HAL provides majority of the info while MHL does only MHL support flag for + // each port. Return empty array if CEC HAL didn't provide the info. + if (mHdmiCecController != null) { + cecPortInfo = mHdmiCecController.getPortInfos(); + } + if (cecPortInfo == null) { + return; + } + + SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>(); + SparseIntArray portIdMap = new SparseIntArray(); + SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>(); + for (HdmiPortInfo info : cecPortInfo) { + portIdMap.put(info.getAddress(), info.getId()); + portInfoMap.put(info.getId(), info); + portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId())); + } + mPortIdMap = new UnmodifiableSparseIntArray(portIdMap); + mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap); + mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap); + + if (mHdmiMhlController == null) { + return; + } + HdmiPortInfo[] mhlPortInfo = mHdmiMhlController.getPortInfos(); + ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length); + for (HdmiPortInfo info : mhlPortInfo) { + if (info.isMhlSupported()) { + mhlSupportedPorts.add(info.getId()); + } + } + + // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use + // cec port info if we do not have have port that supports MHL. + if (mhlSupportedPorts.isEmpty()) { + setPortInfo(Collections.unmodifiableList(Arrays.asList(cecPortInfo))); + return; + } + ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length); + for (HdmiPortInfo info : cecPortInfo) { + if (mhlSupportedPorts.contains(info.getId())) { + result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(), + info.isCecSupported(), true, info.isArcSupported())); + } else { + result.add(info); + } + } + setPortInfo(Collections.unmodifiableList(result)); + } + + HdmiDeviceInfo getDeviceForPortId(int portId) { + return mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE); + } + + /** + * 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 logicalAddress logical address of a device to be searched + * @param physicalAddress physical address of a device to be searched + * @return true if exist; otherwise false + */ + @ServiceThreadOnly + boolean isInDeviceList(int logicalAddress, int physicalAddress) { + assertRunOnServiceThread(); + HdmiDeviceInfo device = getCecDeviceInfo(logicalAddress); + if (device == null) { + return false; + } + return device.getPhysicalAddress() == physicalAddress; + } + + /** + * Passively listen to incoming CEC messages. + * + * This shall not result in any CEC messages being sent. + */ + @ServiceThreadOnly + public void handleCecMessage(HdmiCecMessage message) { + assertRunOnServiceThread(); + // Add device by logical address if it's not already known + int sourceAddress = message.getSource(); + if (getCecDeviceInfo(sourceAddress) == null) { + HdmiDeviceInfo newDevice = new HdmiDeviceInfo(sourceAddress, + HdmiDeviceInfo.PATH_INVALID, HdmiDeviceInfo.PORT_INVALID, + HdmiDeviceInfo.DEVICE_RESERVED, Constants.UNKNOWN_VENDOR_ID, + HdmiUtils.getDefaultDeviceName(sourceAddress)); + addCecDevice(newDevice); + } + + switch (message.getOpcode()) { + case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS: + handleReportPhysicalAddress(message); + break; + case Constants.MESSAGE_REPORT_POWER_STATUS: + handleReportPowerStatus(message); + break; + case Constants.MESSAGE_SET_OSD_NAME: + handleSetOsdName(message); + break; + } + } + + @ServiceThreadOnly + private void handleReportPhysicalAddress(HdmiCecMessage message) { + assertRunOnServiceThread(); + int logicalAddress = message.getSource(); + int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); + int type = message.getParams()[2]; + + if (updateCecSwitchInfo(logicalAddress, type, physicalAddress)) return; + + HdmiDeviceInfo deviceInfo = getCecDeviceInfo(logicalAddress); + if (deviceInfo == null) { + Slog.i(TAG, "Unknown source device info for <Report Physical Address> " + message); + } else { + HdmiDeviceInfo updatedDeviceInfo = new HdmiDeviceInfo(deviceInfo.getLogicalAddress(), + physicalAddress, + physicalAddressToPortId(physicalAddress), type, deviceInfo.getVendorId(), + deviceInfo.getDisplayName(), deviceInfo.getDevicePowerStatus()); + updateCecDevice(updatedDeviceInfo); + } + } + + @ServiceThreadOnly + private void handleReportPowerStatus(HdmiCecMessage message) { + assertRunOnServiceThread(); + // Update power status of device + int newStatus = message.getParams()[0] & 0xFF; + updateDevicePowerStatus(message.getSource(), newStatus); + } + + @ServiceThreadOnly + private void handleSetOsdName(HdmiCecMessage message) { + assertRunOnServiceThread(); + int logicalAddress = message.getSource(); + String osdName; + HdmiDeviceInfo deviceInfo = getCecDeviceInfo(logicalAddress); + // 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; + } + try { + osdName = new String(message.getParams(), "US-ASCII"); + } catch (UnsupportedEncodingException e) { + Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e); + return; + } + + if (deviceInfo.getDisplayName() != null + && deviceInfo.getDisplayName().equals(osdName)) { + Slog.d(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message); + return; + } + + 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, + deviceInfo.getDevicePowerStatus())); + } + + void addCecSwitch(int physicalAddress) { + mCecSwitches.add(physicalAddress); + } + + public ArraySet<Integer> getCecSwitches() { + return mCecSwitches; + } + + void removeDevicesConnectedToPort(int portId) { + Iterator<Integer> it = mCecSwitches.iterator(); + while (it.hasNext()) { + int path = it.next(); + int devicePortId = physicalAddressToPortId(path); + if (devicePortId == portId || devicePortId == Constants.INVALID_PORT_ID) { + it.remove(); + } + } + List<Integer> toRemove = new ArrayList<>(); + for (int i = 0; i < mDeviceInfos.size(); i++) { + int key = mDeviceInfos.keyAt(i); + int physicalAddress = mDeviceInfos.get(key).getPhysicalAddress(); + int devicePortId = physicalAddressToPortId(physicalAddress); + if (devicePortId == portId || devicePortId == Constants.INVALID_PORT_ID) { + toRemove.add(key); + } + } + for (Integer key : toRemove) { + removeDeviceInfo(key); + } + } + + boolean updateCecSwitchInfo(int address, int type, int path) { + if (address == Constants.ADDR_UNREGISTERED + && type == HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH) { + mCecSwitches.add(path); + updateSafeDeviceInfoList(); + return true; // Pure switch does not need further processing. Return here. + } + if (type == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) { + mCecSwitches.add(path); + } + return false; + } + + @GuardedBy("mLock") + List<HdmiDeviceInfo> getSafeCecDevicesLocked() { + ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>(); + for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { + if (isLocalDeviceAddress(info.getLogicalAddress())) { + continue; + } + infoList.add(info); + } + return infoList; + } + + /** + * Thread safe version of {@link #getCecDeviceInfo(int)}. + * + * @param logicalAddress logical address to be retrieved + * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}. + * Returns null if no logical address matched + */ + HdmiDeviceInfo getSafeCecDeviceInfo(int logicalAddress) { + for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { + if (info.isCecDevice() && info.getLogicalAddress() == logicalAddress) { + return info; + } + } + return null; + } + + /** + * Returns the {@link HdmiDeviceInfo} instance whose physical address matches + * + * + * + * qq * the given routing path. CEC devices use routing path for its physical address to + * describe the hierarchy of the devices in the network. + * + * @param path routing path or physical address + * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null + */ + @ServiceThreadOnly + final HdmiDeviceInfo getDeviceInfoByPath(int path) { + assertRunOnServiceThread(); + for (HdmiDeviceInfo info : getDeviceInfoList(false)) { + if (info.getPhysicalAddress() == path) { + return info; + } + } + return null; + } + + /** + * Returns the {@link HdmiDeviceInfo} instance whose physical address matches + * the given routing path. This is the version accessible safely from threads + * other than service thread. + * + * @param path routing path or physical address + * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null + */ + HdmiDeviceInfo getSafeDeviceInfoByPath(int path) { + for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { + if (info.getPhysicalAddress() == path) { + return info; + } + } + return null; + } + + public int getPhysicalAddress() { + return mHdmiCecController.getPhysicalAddress(); + } + + @ServiceThreadOnly + public void clear() { + assertRunOnServiceThread(); + initPortInfo(); + clearDeviceList(); + clearLocalDevices(); + } + + @ServiceThreadOnly + public void clearDeviceList() { + assertRunOnServiceThread(); + for (HdmiDeviceInfo info : HdmiUtils.sparseArrayToList(mDeviceInfos)) { + if (info.getPhysicalAddress() == getPhysicalAddress()) { + continue; + } + invokeDeviceEventListener(info, + HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); + } + mDeviceInfos.clear(); + updateSafeDeviceInfoList(); + } + + /** + * Returns HDMI port information for the given port id. + * + * @param portId HDMI port id + * @return {@link HdmiPortInfo} for the given port + */ + HdmiPortInfo getPortInfo(int portId) { + return mPortInfoMap.get(portId, null); + } + + /** + * Returns the routing path (physical address) of the HDMI port for the given + * port id. + */ + int portIdToPath(int portId) { + HdmiPortInfo portInfo = getPortInfo(portId); + if (portInfo == null) { + Slog.e(TAG, "Cannot find the port info: " + portId); + return Constants.INVALID_PHYSICAL_ADDRESS; + } + return portInfo.getAddress(); + } + + /** + * 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 physicalAddressToPortId(int path) { + int mask = 0xF000; + int finalMask = 0xF000; + int physicalAddress; + 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); + } + + List<HdmiPortInfo> getPortInfo() { + return mPortInfo; + } + + void setPortInfo(List<HdmiPortInfo> portInfo) { + mPortInfo = portInfo; + } + + private boolean isLocalDeviceAddress(int address) { + for (int i = 0; i < mLocalDevices.size(); i++) { + int key = mLocalDevices.keyAt(i); + if (mLocalDevices.get(key).mAddress == address) { + return true; + } + } + return false; + } + + private void assertRunOnServiceThread() { + if (Looper.myLooper() != mHandler.getLooper()) { + throw new IllegalStateException("Should run on service thread."); + } + } + + protected void dump(IndentingPrintWriter pw) { + pw.println("HDMI CEC Network"); + pw.increaseIndent(); + HdmiUtils.dumpIterable(pw, "mPortInfo:", mPortInfo); + for (int i = 0; i < mLocalDevices.size(); ++i) { + pw.println("HdmiCecLocalDevice #" + mLocalDevices.keyAt(i) + ":"); + pw.increaseIndent(); + mLocalDevices.valueAt(i).dump(pw); + + pw.println("Active Source history:"); + pw.increaseIndent(); + final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + ArrayBlockingQueue<HdmiCecController.Dumpable> activeSourceHistory = + mLocalDevices.valueAt(i).getActiveSourceHistory(); + for (HdmiCecController.Dumpable activeSourceEvent : activeSourceHistory) { + activeSourceEvent.dump(pw, sdf); + } + pw.decreaseIndent(); + pw.decreaseIndent(); + } + HdmiUtils.dumpIterable(pw, "mDeviceInfos:", mSafeAllDeviceInfos); + pw.decreaseIndent(); + } +} diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 8bb89da5726f..ee86593916ae 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -71,10 +71,8 @@ import android.os.UserHandle; import android.provider.Settings.Global; import android.sysprop.HdmiProperties; import android.text.TextUtils; -import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; -import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -91,7 +89,6 @@ import libcore.util.EmptyArray; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -177,6 +174,8 @@ public class HdmiControlService extends SystemService { static final int STANDBY_SCREEN_OFF = 0; static final int STANDBY_SHUTDOWN = 1; + private HdmiCecNetwork mHdmiCecNetwork; + // Logical address of the active source. @GuardedBy("mLock") protected final ActiveSource mActiveSource = new ActiveSource(); @@ -333,21 +332,6 @@ public class HdmiControlService extends SystemService { @Nullable private HdmiCecController mCecController; - // HDMI port information. Stored in the unmodifiable list to keep the static information - // from being modified. - // This variable is null if the current device does not have hdmi input. - @GuardedBy("mLock") - private List<HdmiPortInfo> mPortInfo = null; - - // Map from path(physical address) to port ID. - private UnmodifiableSparseIntArray mPortIdMap; - - // Map from port ID to HdmiPortInfo. - private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap; - - // Map from port ID to HdmiDeviceInfo. - private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap; - private HdmiCecMessageValidator mMessageValidator; @ServiceThreadOnly @@ -389,10 +373,6 @@ public class HdmiControlService extends SystemService { @Nullable private Looper mIoLooper; - // Thread safe physical address - @GuardedBy("mLock") - private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS; - // Last input port before switching to the MHL port. Should switch back to this port // when the mobile device sends the request one touch play with off. // Gets invalidated if we go to other port/input. @@ -507,6 +487,26 @@ public class HdmiControlService extends SystemService { @Override public void onStart() { + initService(); + publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); + + if (mCecController != null) { + // Register broadcast receiver for power state change. + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(Intent.ACTION_SCREEN_ON); + filter.addAction(Intent.ACTION_SHUTDOWN); + filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); + getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter); + + // Register ContentObserver to monitor the settings change. + registerContentObserver(); + } + mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED); + } + + @VisibleForTesting + void initService() { if (mIoLooper == null) { mIoThread.start(); mIoLooper = mIoThread.getLooper(); @@ -521,13 +521,7 @@ public class HdmiControlService extends SystemService { if (mCecController == null) { mCecController = HdmiCecController.create(this, getAtomWriter()); } - if (mCecController != null) { - if (mHdmiControlEnabled) { - initializeCec(INITIATED_BY_BOOT_UP); - } else { - mCecController.setOption(OptionKey.ENABLE_CEC, false); - } - } else { + if (mCecController == null) { Slog.i(TAG, "Device does not support HDMI-CEC."); return; } @@ -537,27 +531,18 @@ public class HdmiControlService extends SystemService { if (!mMhlController.isReady()) { Slog.i(TAG, "Device does not support MHL-control."); } + mHdmiCecNetwork = new HdmiCecNetwork(this, mCecController, mMhlController); + if (mHdmiControlEnabled) { + initializeCec(INITIATED_BY_BOOT_UP); + } else { + mCecController.setOption(OptionKey.ENABLE_CEC, false); + } mMhlDevices = Collections.emptyList(); - initPortInfo(); + mHdmiCecNetwork.initPortInfo(); if (mMessageValidator == null) { mMessageValidator = new HdmiCecMessageValidator(this); } - publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); - - if (mCecController != null) { - // Register broadcast receiver for power state change. - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_SCREEN_OFF); - filter.addAction(Intent.ACTION_SCREEN_ON); - filter.addAction(Intent.ACTION_SHUTDOWN); - filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); - getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter); - - // Register ContentObserver to monitor the settings change. - registerContentObserver(); - } - mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED); } private void bootCompleted() { @@ -588,6 +573,15 @@ public class HdmiControlService extends SystemService { } @VisibleForTesting + void setHdmiCecNetwork(HdmiCecNetwork hdmiCecNetwork) { + mHdmiCecNetwork = hdmiCecNetwork; + } + + public HdmiCecNetwork getHdmiCecNetwork() { + return mHdmiCecNetwork; + } + + @VisibleForTesting void setHdmiMhlController(HdmiMhlControllerStub hdmiMhlController) { mMhlController = hdmiMhlController; } @@ -705,7 +699,7 @@ public class HdmiControlService extends SystemService { break; case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED: for (int type : mLocalDevices) { - HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type); + HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(type); if (localDevice != null) { localDevice.setAutoDeviceOff(enabled); } @@ -800,7 +794,7 @@ public class HdmiControlService extends SystemService { // A container for [Device type, Local device info]. ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>(); for (int type : mLocalDevices) { - HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type); + HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(type); if (localDevice == null) { localDevice = HdmiCecLocalDevice.create(this, type); } @@ -832,43 +826,48 @@ public class HdmiControlService extends SystemService { for (final HdmiCecLocalDevice localDevice : allocatingDevices) { mCecController.allocateLogicalAddress(localDevice.getType(), localDevice.getPreferredAddress(), new AllocateAddressCallback() { - @Override - public void onAllocated(int deviceType, int logicalAddress) { - if (logicalAddress == Constants.ADDR_UNREGISTERED) { - Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]"); - } else { - // Set POWER_STATUS_ON to all local devices because they share lifetime - // with system. - HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType, - HdmiControlManager.POWER_STATUS_ON); - localDevice.setDeviceInfo(deviceInfo); - mCecController.addLocalDevice(deviceType, localDevice); - mCecController.addLogicalAddress(logicalAddress); - allocatedDevices.add(localDevice); - } + @Override + public void onAllocated(int deviceType, int logicalAddress) { + if (logicalAddress == Constants.ADDR_UNREGISTERED) { + Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + + "]"); + } else { + // Set POWER_STATUS_ON to all local devices because they share + // lifetime + // with system. + HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, + deviceType, + HdmiControlManager.POWER_STATUS_ON); + localDevice.setDeviceInfo(deviceInfo); + mHdmiCecNetwork.addLocalDevice(deviceType, localDevice); + mCecController.addLogicalAddress(logicalAddress); + allocatedDevices.add(localDevice); + } - // Address allocation completed for all devices. Notify each device. - if (allocatingDevices.size() == ++finished[0]) { - mAddressAllocated = true; - if (initiatedBy != INITIATED_BY_HOTPLUG) { - // In case of the hotplug we don't call onInitializeCecComplete() - // since we reallocate the logical address only. - onInitializeCecComplete(initiatedBy); - } - notifyAddressAllocated(allocatedDevices, initiatedBy); - // Reinvoke the saved display status callback once the local device is ready. - if (mDisplayStatusCallback != null) { - queryDisplayStatus(mDisplayStatusCallback); - mDisplayStatusCallback = null; - } - if (mOtpCallbackPendingAddressAllocation != null) { - oneTouchPlay(mOtpCallbackPendingAddressAllocation); - mOtpCallbackPendingAddressAllocation = null; + // Address allocation completed for all devices. Notify each device. + if (allocatingDevices.size() == ++finished[0]) { + mAddressAllocated = true; + if (initiatedBy != INITIATED_BY_HOTPLUG) { + // In case of the hotplug we don't call + // onInitializeCecComplete() + // since we reallocate the logical address only. + onInitializeCecComplete(initiatedBy); + } + notifyAddressAllocated(allocatedDevices, initiatedBy); + // Reinvoke the saved display status callback once the local + // device is ready. + if (mDisplayStatusCallback != null) { + queryDisplayStatus(mDisplayStatusCallback); + mDisplayStatusCallback = null; + } + if (mOtpCallbackPendingAddressAllocation != null) { + oneTouchPlay(mOtpCallbackPendingAddressAllocation); + mOtpCallbackPendingAddressAllocation = null; + } + mCecMessageBuffer.processMessages(); + } } - mCecMessageBuffer.processMessages(); - } - } - }); + }); } } @@ -888,88 +887,14 @@ public class HdmiControlService extends SystemService { return mAddressAllocated; } - // Initialize HDMI port information. Combine the information from CEC and MHL HAL and - // keep them in one place. - @ServiceThreadOnly - @VisibleForTesting - protected void initPortInfo() { - assertRunOnServiceThread(); - HdmiPortInfo[] cecPortInfo = null; - - synchronized (mLock) { - mPhysicalAddress = getPhysicalAddress(); - } - - // CEC HAL provides majority of the info while MHL does only MHL support flag for - // each port. Return empty array if CEC HAL didn't provide the info. - if (mCecController != null) { - cecPortInfo = mCecController.getPortInfos(); - } - if (cecPortInfo == null) { - return; - } - - SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>(); - SparseIntArray portIdMap = new SparseIntArray(); - SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>(); - for (HdmiPortInfo info : cecPortInfo) { - portIdMap.put(info.getAddress(), info.getId()); - portInfoMap.put(info.getId(), info); - portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId())); - } - mPortIdMap = new UnmodifiableSparseIntArray(portIdMap); - 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) { - if (info.isMhlSupported()) { - mhlSupportedPorts.add(info.getId()); - } - } - - // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use - // cec port info if we do not have have port that supports MHL. - if (mhlSupportedPorts.isEmpty()) { - setPortInfo(Collections.unmodifiableList(Arrays.asList(cecPortInfo))); - return; - } - ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length); - for (HdmiPortInfo info : cecPortInfo) { - if (mhlSupportedPorts.contains(info.getId())) { - result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(), - info.isCecSupported(), true, info.isArcSupported())); - } else { - result.add(info); - } - } - setPortInfo(Collections.unmodifiableList(result)); - } - List<HdmiPortInfo> getPortInfo() { synchronized (mLock) { - return mPortInfo; - } - } - - void setPortInfo(List<HdmiPortInfo> portInfo) { - synchronized (mLock) { - mPortInfo = portInfo; + return mHdmiCecNetwork.getPortInfo(); } } - /** - * Returns HDMI port information for the given port id. - * - * @param portId HDMI port id - * @return {@link HdmiPortInfo} for the given port - */ HdmiPortInfo getPortInfo(int portId) { - return mPortInfoMap.get(portId, null); + return mHdmiCecNetwork.getPortInfo(portId); } /** @@ -977,12 +902,7 @@ public class HdmiControlService extends SystemService { * port id. */ int portIdToPath(int portId) { - HdmiPortInfo portInfo = getPortInfo(portId); - if (portInfo == null) { - Slog.e(TAG, "Cannot find the port info: " + portId); - return Constants.INVALID_PHYSICAL_ADDRESS; - } - return portInfo.getAddress(); + return mHdmiCecNetwork.portIdToPath(portId); } /** @@ -999,26 +919,11 @@ public class HdmiControlService extends SystemService { * on the current device. */ int pathToPortId(int path) { - int mask = 0xF000; - int finalMask = 0xF000; - int physicalAddress; - synchronized (mLock) { - physicalAddress = mPhysicalAddress; - } - 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); + return mHdmiCecNetwork.physicalAddressToPortId(path); } boolean isValidPortId(int portId) { - return getPortInfo(portId) != null; + return mHdmiCecNetwork.getPortInfo(portId) != null; } /** @@ -1068,7 +973,7 @@ public class HdmiControlService extends SystemService { @ServiceThreadOnly HdmiDeviceInfo getDeviceInfo(int logicalAddress) { assertRunOnServiceThread(); - return tv() == null ? null : tv().getCecDeviceInfo(logicalAddress); + return mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); } @ServiceThreadOnly @@ -1092,11 +997,7 @@ public class HdmiControlService extends SystemService { * Whether a device of the specified physical address is connected to ARC enabled port. */ boolean isConnectedToArcPort(int physicalAddress) { - int portId = pathToPortId(physicalAddress); - if (portId != Constants.INVALID_PORT_ID) { - return mPortInfoMap.get(portId).isArcSupported(); - } - return false; + return mHdmiCecNetwork.isConnectedToArcPort(physicalAddress); } @ServiceThreadOnly @@ -1168,7 +1069,7 @@ public class HdmiControlService extends SystemService { } return true; } - + getHdmiCecNetwork().handleCecMessage(message); if (dispatchMessageToLocalDevice(message)) { return true; } @@ -1183,7 +1084,7 @@ public class HdmiControlService extends SystemService { @ServiceThreadOnly private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) { assertRunOnServiceThread(); - for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { + for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) { if (device.dispatchMessage(message) && message.getDestination() != Constants.ADDR_BROADCAST) { return true; @@ -1209,12 +1110,12 @@ public class HdmiControlService extends SystemService { if (connected && !isTvDevice() && getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) { if (isSwitchDevice()) { - initPortInfo(); + mHdmiCecNetwork.initPortInfo(); HdmiLogger.debug("initPortInfo for switch device when onHotplug from tx."); } ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>(); for (int type : mLocalDevices) { - HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type); + HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(type); if (localDevice == null) { localDevice = HdmiCecLocalDevice.create(this, type); localDevice.init(); @@ -1224,9 +1125,14 @@ public class HdmiControlService extends SystemService { allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG); } - for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { + for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) { device.onHotplug(portId, connected); } + + if (!connected) { + mHdmiCecNetwork.removeDevicesConnectedToPort(portId); + } + announceHotplugEvent(portId, connected); } @@ -1262,7 +1168,7 @@ public class HdmiControlService extends SystemService { List<HdmiCecLocalDevice> getAllLocalDevices() { assertRunOnServiceThread(); - return mCecController.getLocalDeviceList(); + return mHdmiCecNetwork.getLocalDeviceList(); } /** @@ -1275,8 +1181,14 @@ public class HdmiControlService extends SystemService { * * @param logicalAddress logical address of the remote device that might have the same logical * address as the current device. + * @param physicalAddress physical address of the given device. */ - protected void checkLogicalAddressConflictAndReallocate(int logicalAddress) { + protected void checkLogicalAddressConflictAndReallocate(int logicalAddress, + int physicalAddress) { + // The given device is a local device. No logical address conflict. + if (physicalAddress == getPhysicalAddress()) { + return; + } for (HdmiCecLocalDevice device : getAllLocalDevices()) { if (device.getDeviceInfo().getLogicalAddress() == logicalAddress) { HdmiLogger.debug("allocate logical address for " + device.getDeviceInfo()); @@ -1616,8 +1528,7 @@ public class HdmiControlService extends SystemService { return null; } if (audioSystem() != null) { - HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem(); - for (HdmiDeviceInfo info : audioSystem.getSafeCecDevicesLocked()) { + for (HdmiDeviceInfo info : mHdmiCecNetwork.getSafeCecDevicesLocked()) { if (info.getPhysicalAddress() == activeSource.physicalAddress) { return info; } @@ -1649,7 +1560,7 @@ public class HdmiControlService extends SystemService { } int activePath = tv.getActivePath(); if (activePath != HdmiDeviceInfo.PATH_INVALID) { - HdmiDeviceInfo info = tv.getSafeDeviceInfoByPath(activePath); + HdmiDeviceInfo info = mHdmiCecNetwork.getSafeDeviceInfoByPath(activePath); return (info != null) ? info : new HdmiDeviceInfo(activePath, tv.getActivePortId()); } return null; @@ -1752,7 +1663,7 @@ public class HdmiControlService extends SystemService { return; } if (mCecController != null) { - HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType); + HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(deviceType); if (localDevice == null) { Slog.w(TAG, "Local device not available to send key event."); return; @@ -1774,7 +1685,7 @@ public class HdmiControlService extends SystemService { Slog.w(TAG, "CEC controller not available to send volume key event."); return; } - HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType); + HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(deviceType); if (localDevice == null) { Slog.w(TAG, "Local device " + deviceType + " not available to send volume key event."); @@ -1888,7 +1799,7 @@ public class HdmiControlService extends SystemService { public int getPhysicalAddress() { enforceAccessPermission(); synchronized (mLock) { - return mPhysicalAddress; + return mHdmiCecNetwork.getPhysicalAddress(); } } @@ -1934,13 +1845,8 @@ public class HdmiControlService extends SystemService { enforceAccessPermission(); // No need to hold the lock for obtaining TV device as the local device instance // is preserved while the HDMI control is enabled. - HdmiCecLocalDeviceTv tv = tv(); - synchronized (mLock) { - List<HdmiDeviceInfo> cecDevices = (tv == null) - ? Collections.<HdmiDeviceInfo>emptyList() - : tv.getSafeExternalInputsLocked(); - return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked()); - } + return HdmiUtils.mergeToUnmodifiableList(mHdmiCecNetwork.getSafeExternalInputsLocked(), + getMhlDevicesLocked()); } // Returns all the CEC devices on the bus including system audio, switch, @@ -1948,19 +1854,7 @@ public class HdmiControlService extends SystemService { @Override public List<HdmiDeviceInfo> getDeviceList() { enforceAccessPermission(); - HdmiCecLocalDeviceTv tv = tv(); - if (tv != null) { - synchronized (mLock) { - return tv.getSafeCecDevicesLocked(); - } - } else { - HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem(); - synchronized (mLock) { - return (audioSystem == null) - ? Collections.<HdmiDeviceInfo>emptyList() - : audioSystem.getSafeCecDevicesLocked(); - } - } + return mHdmiCecNetwork.getSafeCecDevicesLocked(); } @Override @@ -2089,7 +1983,7 @@ public class HdmiControlService extends SystemService { runOnServiceThread(new Runnable() { @Override public void run() { - HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType); + HdmiCecLocalDevice device = mHdmiCecNetwork.getLocalDevice(deviceType); if (device == null) { Slog.w(TAG, "Local device not available"); return; @@ -2117,7 +2011,7 @@ public class HdmiControlService extends SystemService { mhlDevice.sendStandby(); return; } - HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType); + HdmiCecLocalDevice device = mHdmiCecNetwork.getLocalDevice(deviceType); if (device == null) { device = audioSystem(); } @@ -2262,7 +2156,7 @@ public class HdmiControlService extends SystemService { runOnServiceThread(new Runnable() { @Override public void run() { - HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType); + HdmiCecLocalDevice device = mHdmiCecNetwork.getLocalDevice(deviceType); if (device == null) { Slog.w(TAG, "Local device not available"); return; @@ -2339,8 +2233,7 @@ public class HdmiControlService extends SystemService { pw.increaseIndent(); mMhlController.dump(pw); pw.decreaseIndent(); - - HdmiUtils.dumpIterable(pw, "mPortInfo:", mPortInfo); + mHdmiCecNetwork.dump(pw); if (mCecController != null) { pw.println("mCecController: "); pw.increaseIndent(); @@ -2832,7 +2725,7 @@ public class HdmiControlService extends SystemService { } public HdmiCecLocalDeviceTv tv() { - return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV); + return (HdmiCecLocalDeviceTv) mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_TV); } boolean isTvDevice() { @@ -2857,11 +2750,11 @@ public class HdmiControlService extends SystemService { protected HdmiCecLocalDevicePlayback playback() { return (HdmiCecLocalDevicePlayback) - mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK); + mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK); } public HdmiCecLocalDeviceAudioSystem audioSystem() { - return (HdmiCecLocalDeviceAudioSystem) mCecController.getLocalDevice( + return (HdmiCecLocalDeviceAudioSystem) mHdmiCecNetwork.getLocalDevice( HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); } @@ -2991,7 +2884,7 @@ public class HdmiControlService extends SystemService { } private boolean canGoToStandby() { - for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { + for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) { if (!device.canGoToStandby()) return false; } return true; @@ -3025,7 +2918,7 @@ public class HdmiControlService extends SystemService { private void disableDevices(PendingActionClearedCallback callback) { if (mCecController != null) { - for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { + for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) { device.disableDevice(mStandbyMessageReceived, callback); } } @@ -3039,7 +2932,8 @@ public class HdmiControlService extends SystemService { return; } mCecController.clearLogicalAddress(); - mCecController.clearLocalDevices(); + mHdmiCecNetwork.clearLogicalAddress(); + mHdmiCecNetwork.clearLocalDevices(); } @ServiceThreadOnly @@ -3051,7 +2945,7 @@ public class HdmiControlService extends SystemService { return; } mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; - for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { + for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) { device.onStandby(mStandbyMessageReceived, standbyAction); } mStandbyMessageReceived = false; @@ -3411,7 +3305,7 @@ public class HdmiControlService extends SystemService { // input change listener should be the one describing the corresponding HDMI port. HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); HdmiDeviceInfo info = (device != null) ? device.getInfo() - : mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE); + : mHdmiCecNetwork.getDeviceForPortId(portId); invokeInputChangeListener(info); } diff --git a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java index 7670dccf9c0a..ece78bfa2769 100644 --- a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java +++ b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java @@ -148,7 +148,8 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction { } private void checkHotplug(List<Integer> ackedAddress, boolean audioOnly) { - BitSet currentInfos = infoListToBitSet(tv().getDeviceInfoList(false), audioOnly); + BitSet currentInfos = infoListToBitSet( + localDevice().mService.getHdmiCecNetwork().getDeviceInfoList(false), audioOnly); BitSet polledResult = addressListToBitSet(ackedAddress); // At first, check removed devices. @@ -225,11 +226,11 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction { mayCancelOneTouchRecord(removedAddress); mayDisableSystemAudioAndARC(removedAddress); - tv().removeCecDevice(removedAddress); + localDevice().mService.getHdmiCecNetwork().removeCecDevice(localDevice(), removedAddress); } private void mayChangeRoutingPath(int address) { - HdmiDeviceInfo info = tv().getCecDeviceInfo(address); + HdmiDeviceInfo info = localDevice().mService.getHdmiCecNetwork().getCecDeviceInfo(address); if (info != null) { tv().handleRemoveActiveRoutingPath(info.getPhysicalAddress()); } diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java index 6753368911b9..edc7bd95a017 100644 --- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java +++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java @@ -19,6 +19,7 @@ import android.hardware.hdmi.HdmiDeviceInfo; import android.util.Slog; import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource; + import java.io.UnsupportedEncodingException; /** @@ -164,7 +165,8 @@ final class NewDeviceAction extends HdmiCecFeatureAction { private void addDeviceInfo() { // The device should be in the device list with default information. - if (!tv().isInDeviceList(mDeviceLogicalAddress, mDevicePhysicalAddress)) { + if (!localDevice().mService.getHdmiCecNetwork().isInDeviceList(mDeviceLogicalAddress, + mDevicePhysicalAddress)) { Slog.w(TAG, String.format("Device not found (%02x, %04x)", mDeviceLogicalAddress, mDevicePhysicalAddress)); return; @@ -176,7 +178,7 @@ final class NewDeviceAction extends HdmiCecFeatureAction { mDeviceLogicalAddress, mDevicePhysicalAddress, tv().getPortId(mDevicePhysicalAddress), mDeviceType, mVendorId, mDisplayName); - tv().addCecDevice(deviceInfo); + localDevice().mService.getHdmiCecNetwork().addCecDevice(deviceInfo); // Consume CEC messages we already got for this newly found device. tv().processDelayedMessages(mDeviceLogicalAddress); diff --git a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java index a62d0b63221c..909fcda26c39 100644 --- a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java +++ b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java @@ -20,7 +20,9 @@ import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_UNKNOWN; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.util.SparseIntArray; + import com.android.server.hdmi.HdmiControlService.SendMessageCallback; + import java.util.List; /** @@ -111,7 +113,8 @@ public class PowerStatusMonitorAction extends HdmiCecFeatureAction { } private void queryPowerStatus() { - List<HdmiDeviceInfo> deviceInfos = tv().getDeviceInfoList(false); + List<HdmiDeviceInfo> deviceInfos = + localDevice().mService.getHdmiCecNetwork().getDeviceInfoList(false); resetPowerStatus(deviceInfos); for (HdmiDeviceInfo info : deviceInfos) { final int logicalAddress = info.getLogicalAddress(); @@ -137,7 +140,8 @@ public class PowerStatusMonitorAction extends HdmiCecFeatureAction { } private void updatePowerStatus(int logicalAddress, int newStatus, boolean remove) { - tv().updateDevicePowerStatus(logicalAddress, newStatus); + localDevice().mService.getHdmiCecNetwork().updateDevicePowerStatus(logicalAddress, + newStatus); if (remove) { mPowerStatus.delete(logicalAddress); diff --git a/services/core/java/com/android/server/hdmi/RoutingControlAction.java b/services/core/java/com/android/server/hdmi/RoutingControlAction.java index 6c8694ea74ad..6c147ed5e6d6 100644 --- a/services/core/java/com/android/server/hdmi/RoutingControlAction.java +++ b/services/core/java/com/android/server/hdmi/RoutingControlAction.java @@ -17,8 +17,8 @@ package com.android.server.hdmi; import android.annotation.Nullable; -import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; import android.os.RemoteException; import android.util.Slog; @@ -160,7 +160,9 @@ final class RoutingControlAction extends HdmiCecFeatureAction { } switch (timeoutState) { case STATE_WAIT_FOR_ROUTING_INFORMATION: - HdmiDeviceInfo device = tv().getDeviceInfoByPath(mCurrentRoutingPath); + HdmiDeviceInfo device = + localDevice().mService.getHdmiCecNetwork().getDeviceInfoByPath( + mCurrentRoutingPath); if (device != null && mQueryDevicePowerStatus) { int deviceLogicalAddress = device.getLogicalAddress(); queryDevicePowerStatus(deviceLogicalAddress, new SendMessageCallback() { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java index 2a9c3942211c..8af7332e24ed 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java @@ -114,7 +114,7 @@ public class ActiveSourceActionTest { mHdmiControlService.setCecController(hdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); - mHdmiControlService.initPortInfo(); + mHdmiControlService.initService(); mPhysicalAddress = 0x2000; mNativeWrapper.setPhysicalAddress(mPhysicalAddress); mTestLooper.dispatchAll(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java index 1385376b740d..37a75e3822aa 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java @@ -123,7 +123,7 @@ public class ArcInitiationActionFromAvrTest { hdmiControlService.setCecController(hdmiCecController); hdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(hdmiControlService)); hdmiControlService.setMessageValidator(new HdmiCecMessageValidator(hdmiControlService)); - hdmiControlService.initPortInfo(); + hdmiControlService.initService(); mAction = new ArcInitiationActionFromAvr(mHdmiCecLocalDeviceAudioSystem); mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java index 169f885a7253..6027c3e4eeab 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java @@ -117,7 +117,7 @@ public class ArcTerminationActionFromAvrTest { hdmiControlService.setCecController(hdmiCecController); hdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(hdmiControlService)); hdmiControlService.setMessageValidator(new HdmiCecMessageValidator(hdmiControlService)); - hdmiControlService.initPortInfo(); + hdmiControlService.initService(); mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(hdmiControlService) { @Override 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 2c42791fabce..bb57a69d6f51 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java @@ -30,6 +30,7 @@ import com.google.common.collect.Iterables; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; /** Fake {@link NativeWrapper} useful for testing. */ final class FakeNativeWrapper implements NativeWrapper { @@ -55,6 +56,7 @@ final class FakeNativeWrapper implements NativeWrapper { }; private final List<HdmiCecMessage> mResultMessages = new ArrayList<>(); + private final Map<Integer, Boolean> mPortConnectionStatus = new HashMap<>(); private final HashMap<Integer, Integer> mMessageSendResult = new HashMap<>(); private int mMyPhysicalAddress = 0; private HdmiPortInfo[] mHdmiPortInfo = null; @@ -125,7 +127,12 @@ final class FakeNativeWrapper implements NativeWrapper { @Override public boolean nativeIsConnected(int port) { - return false; + Boolean isConnected = mPortConnectionStatus.get(port); + return isConnected == null ? false : isConnected; + } + + public void setPortConnectionStatus(int port, boolean connected) { + mPortConnectionStatus.put(port, connected); } public void onCecMessage(HdmiCecMessage hdmiCecMessage) { 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 74fd6830de61..c436cc846564 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -15,13 +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; -import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2; import static com.android.server.hdmi.Constants.ADDR_TUNER_1; import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.Constants.MESSAGE_GIVE_AUDIO_STATUS; @@ -226,7 +222,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { new HdmiPortInfo( 4, HdmiPortInfo.PORT_INPUT, HDMI_3_PHYSICAL_ADDRESS, true, false, false); mNativeWrapper.setPortInfo(mHdmiPortInfo); - mHdmiControlService.initPortInfo(); + mHdmiControlService.initService(); // No TV device interacts with AVR so system audio control won't be turned on here mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); @@ -654,75 +650,6 @@ public class HdmiCecLocalDeviceAudioSystemTest { } @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, 0x2300, 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 - @Ignore("b/120845532") - 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_2, 0x2200, 1, HdmiDeviceInfo.DEVICE_PLAYBACK, - Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_2)); - HdmiCecMessage reportPhysicalAddress = HdmiCecMessageBuilder - .buildReportPhysicalAddressCommand( - ADDR_PLAYBACK_2, 0x2200, HdmiDeviceInfo.DEVICE_PLAYBACK); - mHdmiCecLocalDeviceAudioSystem.handleReportPhysicalAddress(reportPhysicalAddress); - - mTestLooper.dispatchAll(); - assertThat(mDeviceInfo).isEqualTo(differentDevice); - assertThat(mHdmiCecLocalDeviceAudioSystem - .getCecDeviceInfo(differentDevice.getLogicalAddress())).isEqualTo(differentDevice); - assertThat(mInvokeDeviceEventState).isEqualTo(DEVICE_EVENT_ADD_DEVICE); - } - - @Test public void doNotWakeUpOnHotPlug_PlugIn() { mWokenUp = false; mHdmiCecLocalDeviceAudioSystem.onHotplug(0, true); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java index ef98b98ba7e1..1c04d98327c9 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java @@ -25,6 +25,7 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiPortInfo; import android.os.Handler; import android.os.IPowerManager; import android.os.IThermalService; @@ -125,7 +126,12 @@ public class HdmiCecLocalDevicePlaybackTest { mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDevicePlayback); - mHdmiControlService.initPortInfo(); + HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1]; + hdmiPortInfos[0] = + new HdmiPortInfo(1, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false); + mNativeWrapper.setPortInfo(hdmiPortInfos); + mNativeWrapper.setPortConnectionStatus(1, true); + mHdmiControlService.initService(); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mPlaybackPhysicalAddress = 0x2000; mNativeWrapper.setPhysicalAddress(mPlaybackPhysicalAddress); @@ -892,6 +898,7 @@ public class HdmiCecLocalDevicePlaybackTest { public void handleSetStreamPath_afterHotplug_broadcastsActiveSource() { mNativeWrapper.onHotplugEvent(1, false); mNativeWrapper.onHotplugEvent(1, true); + mTestLooper.dispatchAll(); HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, mPlaybackPhysicalAddress); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java index ce1cdf369076..bf4851b927b1 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiPortInfo; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Handler; import android.os.IPowerManager; @@ -103,7 +104,11 @@ public class HdmiCecLocalDeviceTvTest { mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDeviceTv); - mHdmiControlService.initPortInfo(); + HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1]; + hdmiPortInfos[0] = + new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false); + mNativeWrapper.setPortInfo(hdmiPortInfos); + mHdmiControlService.initService(); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTvPhysicalAddress = 0x0000; mNativeWrapper.setPhysicalAddress(mTvPhysicalAddress); @@ -119,8 +124,9 @@ public class HdmiCecLocalDeviceTvTest { @Test public void onAddressAllocated_invokesDeviceDiscovery() { + mHdmiControlService.getHdmiCecNetwork().clearLocalDevices(); mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); - mHdmiCecLocalDeviceTv.onAddressAllocated(0, HdmiControlService.INITIATED_BY_BOOT_UP); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java new file mode 100644 index 000000000000..4f18582bf83f --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2020 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 com.android.server.hdmi; + + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; +import android.hardware.hdmi.HdmiPortInfo; +import android.os.Looper; +import android.os.test.TestLooper; +import android.platform.test.annotations.Presubmit; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tests for {@link HdmiCecNetwork} class. + */ +@SmallTest +@Presubmit +@RunWith(JUnit4.class) +public class HdmiCecNetworkTest { + + private HdmiCecNetwork mHdmiCecNetwork; + + private Context mContext; + + private HdmiControlService mHdmiControlService; + private HdmiMhlControllerStub mHdmiMhlControllerStub; + + private HdmiCecController mHdmiCecController; + private FakeNativeWrapper mNativeWrapper; + private Looper mMyLooper; + private TestLooper mTestLooper = new TestLooper(); + private HdmiPortInfo[] mHdmiPortInfo; + private List<Integer> mDeviceEventListenerStatuses = new ArrayList<>(); + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getTargetContext(); + mHdmiControlService = new HdmiControlService(mContext) { + @Override + void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) { + mDeviceEventListenerStatuses.add(status); + } + }; + + mMyLooper = mTestLooper.getLooper(); + mHdmiControlService.setIoLooper(mMyLooper); + + mNativeWrapper = new FakeNativeWrapper(); + mHdmiCecController = HdmiCecController.createWithNativeWrapper(mHdmiControlService, + mNativeWrapper, mHdmiControlService.getAtomWriter()); + mHdmiMhlControllerStub = HdmiMhlControllerStub.create(mHdmiControlService); + mHdmiControlService.setCecController(mHdmiCecController); + mHdmiControlService.setHdmiMhlController(mHdmiMhlControllerStub); + mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); + + mHdmiCecNetwork = new HdmiCecNetwork(mHdmiControlService, + mHdmiCecController, mHdmiMhlControllerStub); + + mHdmiControlService.setHdmiCecNetwork(mHdmiCecNetwork); + + mHdmiPortInfo = new HdmiPortInfo[5]; + 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); + mHdmiPortInfo[4] = + new HdmiPortInfo(5, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false); + mNativeWrapper.setPortInfo(mHdmiPortInfo); + mHdmiCecNetwork.initPortInfo(); + } + + @Test + public void initializeNetwork_verifyPortInfo() { + mHdmiCecNetwork.initPortInfo(); + assertThat(mHdmiCecNetwork.getPortInfo()).hasSize(mHdmiPortInfo.length); + } + + @Test + public void physicalAddressToPort_pathExists_weAreNonTv() { + mNativeWrapper.setPhysicalAddress(0x2000); + mHdmiCecNetwork.initPortInfo(); + assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x2120)).isEqualTo(1); + assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x2234)).isEqualTo(2); + } + + @Test + public void physicalAddressToPort_pathExists_weAreSourceDevice() { + mNativeWrapper.setPhysicalAddress(0x2000); + mHdmiCecNetwork.initPortInfo(); + assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x0000)).isEqualTo(5); + } + + @Test + public void physicalAddressToPort_pathExists_weAreTv() { + mNativeWrapper.setPhysicalAddress(0x0000); + mHdmiCecNetwork.initPortInfo(); + assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x2120)).isEqualTo(3); + assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x3234)).isEqualTo(4); + } + + @Test + public void physicalAddressToPort_pathInvalid() { + mNativeWrapper.setPhysicalAddress(0x2000); + mHdmiCecNetwork.initPortInfo(); + assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x1000)).isEqualTo( + Constants.INVALID_PORT_ID); + } + + @Test + public void localDevices_verifyOne_tv() { + mHdmiCecNetwork.addLocalDevice(HdmiDeviceInfo.DEVICE_TV, + new HdmiCecLocalDeviceTv(mHdmiControlService)); + + assertThat(mHdmiCecNetwork.getLocalDeviceList()).hasSize(1); + assertThat(mHdmiCecNetwork.getLocalDeviceList().get(0)).isInstanceOf( + HdmiCecLocalDeviceTv.class); + assertThat(mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_TV)).isNotNull(); + assertThat(mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK)).isNull(); + } + + @Test + public void localDevices_verifyOne_playback() { + mHdmiCecNetwork.addLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK, + new HdmiCecLocalDevicePlayback(mHdmiControlService)); + + assertThat(mHdmiCecNetwork.getLocalDeviceList()).hasSize(1); + assertThat(mHdmiCecNetwork.getLocalDeviceList().get(0)).isInstanceOf( + HdmiCecLocalDevicePlayback.class); + assertThat(mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK)).isNotNull(); + assertThat(mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_TV)).isNull(); + } + + @Test + public void cecDevices_tracking_logicalAddressOnly() throws Exception { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildActiveSource(logicalAddress, 0x1000)); + + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + + HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); + assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress); + assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo( + Constants.INVALID_PHYSICAL_ADDRESS); + assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED); + assertThat(cecDeviceInfo.getDisplayName()).isEqualTo( + HdmiUtils.getDefaultDeviceName(logicalAddress)); + assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID); + assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo( + HdmiControlManager.POWER_STATUS_UNKNOWN); + + assertThat(mDeviceEventListenerStatuses).containsExactly( + HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); + } + + @Test + public void cecDevices_tracking_logicalAddressOnly_doesntNotifyAgain() throws Exception { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildActiveSource(logicalAddress, 0x1000)); + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildActiveSource(logicalAddress, 0x1000)); + + assertThat(mDeviceEventListenerStatuses).containsExactly( + HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); + } + + @Test + public void cecDevices_tracking_reportPhysicalAddress() { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + int physicalAddress = 0x1000; + int type = HdmiDeviceInfo.DEVICE_PLAYBACK; + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress, + physicalAddress, type)); + + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + + HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); + assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress); + assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo( + physicalAddress); + assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(type); + assertThat(cecDeviceInfo.getDisplayName()).isEqualTo( + HdmiUtils.getDefaultDeviceName(logicalAddress)); + assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID); + assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo( + HdmiControlManager.POWER_STATUS_UNKNOWN); + } + + @Test + public void cecDevices_tracking_updateDeviceInfo_sameDoesntNotify() { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + int physicalAddress = 0x1000; + int type = HdmiDeviceInfo.DEVICE_PLAYBACK; + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildActiveSource(logicalAddress, 0x1000)); + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress, + physicalAddress, type)); + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress, + physicalAddress, type)); + + + // ADD for logical address first detected + // UPDATE for updating device with physical address + assertThat(mDeviceEventListenerStatuses).containsExactly( + HdmiControlManager.DEVICE_EVENT_ADD_DEVICE, + HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE); + } + + @Test + public void cecDevices_tracking_reportPowerStatus() { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + int powerStatus = HdmiControlManager.POWER_STATUS_ON; + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildReportPowerStatus(logicalAddress, + Constants.ADDR_BROADCAST, powerStatus)); + + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + + HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); + assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress); + assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo( + Constants.INVALID_PHYSICAL_ADDRESS); + assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED); + assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID); + assertThat(cecDeviceInfo.getDisplayName()).isEqualTo( + HdmiUtils.getDefaultDeviceName(logicalAddress)); + assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(powerStatus); + } + + @Test + public void cecDevices_tracking_reportOsdName() { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + String osdName = "Test Device"; + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildSetOsdNameCommand(logicalAddress, + Constants.ADDR_BROADCAST, osdName)); + + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + + HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); + assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress); + assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo( + Constants.INVALID_PHYSICAL_ADDRESS); + assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED); + assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID); + assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(osdName); + assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo( + HdmiControlManager.POWER_STATUS_UNKNOWN); + } + + @Test + public void cecDevices_tracking_updatesDeviceInfo() { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + int physicalAddress = 0x1000; + int type = HdmiDeviceInfo.DEVICE_PLAYBACK; + int powerStatus = HdmiControlManager.POWER_STATUS_ON; + String osdName = "Test Device"; + + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress, + physicalAddress, type)); + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildReportPowerStatus(logicalAddress, + Constants.ADDR_BROADCAST, powerStatus)); + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildSetOsdNameCommand(logicalAddress, + Constants.ADDR_BROADCAST, osdName)); + + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + + HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); + assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress); + assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(physicalAddress); + assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(type); + assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(osdName); + assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(powerStatus); + } + + @Test + public void cecDevices_tracking_updatesPhysicalAddress() { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + int initialPhysicalAddress = 0x1000; + int updatedPhysicalAddress = 0x2000; + int type = HdmiDeviceInfo.DEVICE_PLAYBACK; + + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress, + initialPhysicalAddress, type)); + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress, + updatedPhysicalAddress, type)); + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + + HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); + assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress); + assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(updatedPhysicalAddress); + assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(type); + + // ADD for logical address first detected + // UPDATE for updating device with physical address + // UPDATE for updating device with new physical address + assertThat(mDeviceEventListenerStatuses).containsExactly( + HdmiControlManager.DEVICE_EVENT_ADD_DEVICE, + HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE, + HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE); + } + + @Test + public void cecDevices_tracking_updatesPowerStatus() { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + int powerStatus = HdmiControlManager.POWER_STATUS_ON; + int updatedPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; + + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildReportPowerStatus(logicalAddress, + Constants.ADDR_BROADCAST, powerStatus)); + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildReportPowerStatus(logicalAddress, + Constants.ADDR_BROADCAST, updatedPowerStatus)); + + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + + HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); + assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress); + assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(updatedPowerStatus); + } + + @Test + public void cecDevices_tracking_updatesOsdName() { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + String osdName = "Test Device"; + String updatedOsdName = "Different"; + + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildSetOsdNameCommand(logicalAddress, + Constants.ADDR_BROADCAST, osdName)); + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildSetOsdNameCommand(logicalAddress, + Constants.ADDR_BROADCAST, updatedOsdName)); + + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + + HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); + assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress); + assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(updatedOsdName); + } + + @Test + public void cecDevices_tracking_clearDevices() { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildActiveSource(logicalAddress, 0x1000)); + + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + + mHdmiCecNetwork.clearDeviceList(); + + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).isEmpty(); + + assertThat(mDeviceEventListenerStatuses).containsExactly( + HdmiControlManager.DEVICE_EVENT_ADD_DEVICE, + HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java index c4068d34c00d..74a00521d7f4 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java @@ -21,11 +21,13 @@ import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; +import android.content.Context; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiPortInfo; import android.hardware.hdmi.IHdmiControlCallback; import android.os.Looper; import android.os.test.TestLooper; +import android.provider.Settings; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -44,6 +46,8 @@ import java.util.ArrayList; @RunWith(JUnit4.class) public class HdmiControlServiceBinderAPITest { + private Context mContext; + private class HdmiCecLocalDeviceMyDevice extends HdmiCecLocalDevice { private boolean mCanGoToStandby; @@ -111,8 +115,12 @@ public class HdmiControlServiceBinderAPITest { @Before public void SetUp() { + mContext = InstrumentationRegistry.getTargetContext(); + // Some tests expect no logical addresses being allocated at the beginning of the test. + setHdmiControlEnabled(false); + mHdmiControlService = - new HdmiControlService(InstrumentationRegistry.getTargetContext()) { + new HdmiControlService(mContext) { @Override void sendCecCommand(HdmiCecMessage command) { switch (command.getOpcode()) { @@ -164,7 +172,7 @@ public class HdmiControlServiceBinderAPITest { mHdmiPortInfo[0] = new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false); mNativeWrapper.setPortInfo(mHdmiPortInfo); - mHdmiControlService.initPortInfo(); + mHdmiControlService.initService(); mResult = -1; mPowerStatus = HdmiControlManager.POWER_STATUS_ON; @@ -183,6 +191,7 @@ public class HdmiControlServiceBinderAPITest { assertEquals(mResult, -1); assertThat(mPlaybackDevice.isActiveSource()).isFalse(); + setHdmiControlEnabled(true); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); assertThat(mHdmiControlService.isAddressAllocated()).isTrue(); @@ -192,6 +201,8 @@ public class HdmiControlServiceBinderAPITest { @Test public void oneTouchPlay_addressAllocated() { + setHdmiControlEnabled(true); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); assertThat(mHdmiControlService.isAddressAllocated()).isTrue(); @@ -204,4 +215,10 @@ public class HdmiControlServiceBinderAPITest { assertEquals(mResult, HdmiControlManager.RESULT_SUCCESS); assertThat(mPlaybackDevice.isActiveSource()).isTrue(); } + + private void setHdmiControlEnabled(boolean enabled) { + int value = enabled ? 1 : 0; + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.HDMI_CONTROL_ENABLED, + value); + } } 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 2f48b5ee4c70..8f631a307f15 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -176,7 +176,7 @@ public class HdmiControlServiceTest { mHdmiPortInfo[3] = new HdmiPortInfo(4, HdmiPortInfo.PORT_INPUT, 0x3000, true, false, false); mNativeWrapper.setPortInfo(mHdmiPortInfo); - mHdmiControlService.initPortInfo(); + mHdmiControlService.initService(); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); @@ -206,29 +206,6 @@ public class HdmiControlServiceTest { } @Test - public void pathToPort_pathExists_weAreNonTv() { - mNativeWrapper.setPhysicalAddress(0x2000); - mHdmiControlService.initPortInfo(); - assertThat(mHdmiControlService.pathToPortId(0x2120)).isEqualTo(1); - assertThat(mHdmiControlService.pathToPortId(0x2234)).isEqualTo(2); - } - - @Test - public void pathToPort_pathExists_weAreTv() { - mNativeWrapper.setPhysicalAddress(0x0000); - mHdmiControlService.initPortInfo(); - assertThat(mHdmiControlService.pathToPortId(0x2120)).isEqualTo(3); - assertThat(mHdmiControlService.pathToPortId(0x3234)).isEqualTo(4); - } - - @Test - public void pathToPort_pathInvalid() { - mNativeWrapper.setPhysicalAddress(0x2000); - mHdmiControlService.initPortInfo(); - assertThat(mHdmiControlService.pathToPortId(0x1000)).isEqualTo(Constants.INVALID_PORT_ID); - } - - @Test public void initialPowerStatus_normalBoot_isTransientToStandby() { assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo( HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java index 6be28d9a13be..0e4bfab0d94c 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java @@ -90,8 +90,6 @@ public class SystemAudioInitiationActionFromAvrTest { break; case Constants.MESSAGE_INITIATE_ARC: break; - default: - throw new IllegalArgumentException("Unexpected message"); } } @@ -159,6 +157,14 @@ public class SystemAudioInitiationActionFromAvrTest { return -1; } }; + + Looper looper = mTestLooper.getLooper(); + hdmiControlService.setIoLooper(looper); + HdmiCecController.NativeWrapper nativeWrapper = new FakeNativeWrapper(); + HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper( + hdmiControlService, nativeWrapper, hdmiControlService.getAtomWriter()); + hdmiControlService.setCecController(hdmiCecController); + hdmiControlService.initService(); mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(hdmiControlService) { @Override @@ -181,8 +187,6 @@ public class SystemAudioInitiationActionFromAvrTest { } }; mHdmiCecLocalDeviceAudioSystem.init(); - Looper looper = mTestLooper.getLooper(); - hdmiControlService.setIoLooper(looper); } @Test |