From 123ec40f7dffcd24a8eddc1d9fc42dc2ee484725 Mon Sep 17 00:00:00 2001 From: Amy Date: Tue, 25 Sep 2018 10:56:31 -0700 Subject: [PATCH] Refactor the Active Source updating logic in CEC devices. ag/5108161 Move local Active Source to control service since each device only needs one local Active Source. Easier to update. But each device needs multiple mIsActiveSource for each device type since only one of them should be the Active Source. When Active Source can be handled only by one device type, like OneTouchPlay, call setAndBroadcastActiveSourceFromOneDeviceType in service to update all the device types. When receiving broadcast messages, all of the device types can handle Active Source updating in their own logic by calling setAndBroadcastActiveSource in service. Test: atest com.android.server.hdmi Change-Id: I88ce68719ae02c7a847dabb6c558abdb6840b3fa --- .../server/hdmi/HdmiCecLocalDevice.java | 14 +--- .../hdmi/HdmiCecLocalDeviceAudioSystem.java | 8 +- .../hdmi/HdmiCecLocalDevicePlayback.java | 49 ------------ .../server/hdmi/HdmiCecLocalDeviceSource.java | 31 ++------ .../server/hdmi/HdmiCecLocalDeviceTv.java | 12 +-- .../server/hdmi/HdmiControlService.java | 75 +++++++++++++++++++ .../server/hdmi/OneTouchPlayAction.java | 5 +- .../SystemAudioInitiationActionFromAvr.java | 8 +- 8 files changed, 99 insertions(+), 103 deletions(-) diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 528e0a4e4940f..c1ac1af339efb 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -133,9 +133,6 @@ abstract class HdmiCecLocalDevice { return s.toString(); } } - // Logical address of the active source. - @GuardedBy("mLock") - protected final ActiveSource mActiveSource = new ActiveSource(); // Active routing path. Physical address of the active source but not all the time, such as // when the new active source does not claim itself to be one. Note that we don't keep @@ -867,9 +864,7 @@ abstract class HdmiCecLocalDevice { } ActiveSource getActiveSource() { - synchronized (mLock) { - return mActiveSource; - } + return mService.getActiveSource(); } void setActiveSource(ActiveSource newActive) { @@ -881,10 +876,7 @@ abstract class HdmiCecLocalDevice { } void setActiveSource(int logicalAddress, int physicalAddress) { - synchronized (mLock) { - mActiveSource.logicalAddress = logicalAddress; - mActiveSource.physicalAddress = physicalAddress; - } + mService.setActiveSource(logicalAddress, physicalAddress); mService.setLastInputForMhl(Constants.INVALID_PORT_ID); } @@ -1042,7 +1034,7 @@ abstract class HdmiCecLocalDevice { pw.println("mAddress: " + mAddress); pw.println("mPreferredAddress: " + mPreferredAddress); pw.println("mDeviceInfo: " + mDeviceInfo); - pw.println("mActiveSource: " + mActiveSource); + pw.println("mActiveSource: " + getActiveSource()); pw.println(String.format("mActiveRoutingPath: 0x%04x", mActiveRoutingPath)); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java index 73e0b63676551..086a27a0a7acd 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java @@ -676,12 +676,8 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { } if (getLocalActivePort() == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) { routeToInputFromPortId(Constants.CEC_SWITCH_HOME); - if (mService.playback() != null) { - mService.playback().setAndBroadcastActiveSource( - message, mService.getPhysicalAddress()); - } else { - setAndBroadcastActiveSource(message, mService.getPhysicalAddress()); - } + mService.setAndBroadcastActiveSourceFromOneDeviceType( + message.getSource(), mService.getPhysicalAddress()); return; } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index 593cf5908bbe2..9bb95384fedcd 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -48,8 +48,6 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { private static final boolean SET_MENU_LANGUAGE = SystemProperties.getBoolean(Constants.PROPERTY_SET_MENU_LANGUAGE, false); - private boolean mIsActiveSource = false; - // Used to keep the device awake while it is the active source. For devices that // cannot wake up via CEC commands, this address the inconvenience of having to // turn them on. True by default, and can be disabled (i.e. device can go to sleep @@ -220,53 +218,6 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { return super.handleUserControlPressed(message); } - @Override - @ServiceThreadOnly - protected boolean handleSetStreamPath(HdmiCecMessage message) { - assertRunOnServiceThread(); - int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); - // If current device is the target path, set to Active Source. - // If the path is under the current device, should switch - int port = getLocalPortFromPhysicalAddress(physicalAddress); - if (port == 0) { - setIsActiveSource(true); - maySendActiveSource(message.getSource()); - wakeUpIfActiveSource(); - } else if (port > 0) { - // Wake up the device if the power is in standby mode for routing - if (mService.isPowerStandbyOrTransient()) { - mService.wakeUp(); - } - routeToPort(port); - } - return true; - } - - // Samsung model we tested sends and - // in a row, and then changes the input to the internal source if there is no - // in response. To handle this, we'll set ActiveSource aggressively. - @Override - @ServiceThreadOnly - protected boolean handleRoutingChange(HdmiCecMessage message) { - assertRunOnServiceThread(); - int newPath = HdmiUtils.twoBytesToInt(message.getParams(), 2); - maySetActiveSource(newPath); - return true; // Broadcast message. - } - - @Override - @ServiceThreadOnly - protected boolean handleRoutingInformation(HdmiCecMessage message) { - assertRunOnServiceThread(); - int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); - maySetActiveSource(physicalAddress); - return true; // Broadcast message. - } - - private void maySetActiveSource(int physicalAddress) { - setIsActiveSource(physicalAddress == mService.getPhysicalAddress()); - } - @Override protected void wakeUpIfActiveSource() { if (!mIsActiveSource) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java index 8d55299243b2a..8dfab486aa9fb 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java @@ -37,7 +37,8 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { private static final String TAG = "HdmiCecLocalDeviceSource"; // Indicate if current device is Active Source or not - private boolean mIsActiveSource = false; + @VisibleForTesting + protected boolean mIsActiveSource = false; // Device has cec switch functionality or not. // Default is false. @@ -113,7 +114,7 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { int logicalAddress = message.getSource(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress); - if (!mActiveSource.equals(activeSource)) { + if (!getActiveSource().equals(activeSource)) { setActiveSource(activeSource); } setIsActiveSource(physicalAddress == mService.getPhysicalAddress()); @@ -185,24 +186,13 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { // do nothing } - // Active source claiming needs to be handled in the parent class - // since we decide who will be the active source when the device supports + // Active source claiming needs to be handled in Service + // since service can decide who will be the active source when the device supports // multiple device types in this method. // This method should only be called when the device can be the active source. protected void setAndBroadcastActiveSource(HdmiCecMessage message, int physicalAddress) { - // If the device has both playback and audio system logical addresses, - // playback will claim active source. Otherwise audio system will. - HdmiCecLocalDevice deviceToBeActiveSource = mService.playback(); - if (deviceToBeActiveSource == null) { - deviceToBeActiveSource = mService.audioSystem(); - } - if (this == deviceToBeActiveSource) { - ActiveSource activeSource = ActiveSource.of(mAddress, physicalAddress); - setIsActiveSource(true); - setActiveSource(activeSource); - wakeUpIfActiveSource(); - maySendActiveSource(message.getSource()); - } + mService.setAndBroadcastActiveSource( + message, physicalAddress, getDeviceInfo().getDeviceType()); } @ServiceThreadOnly @@ -211,13 +201,6 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { mIsActiveSource = on; } - @ServiceThreadOnly - // Check if current device is the Active Source - boolean isActiveSource() { - assertRunOnServiceThread(); - return mIsActiveSource; - } - protected void wakeUpIfActiveSource() { if (!mIsActiveSource) { return; diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 25ca27836aa7d..9e1250e1843d9 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -47,11 +47,13 @@ import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; + import com.android.internal.annotations.GuardedBy; import com.android.internal.util.IndentingPrintWriter; 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; @@ -307,7 +309,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { private void handleSelectInternalSource() { assertRunOnServiceThread(); // Seq #18 - if (mService.isControlEnabled() && mActiveSource.logicalAddress != mAddress) { + if (mService.isControlEnabled() && getActiveSource().logicalAddress != mAddress) { updateActiveSource(mAddress, mService.getPhysicalAddress()); if (mSkipRoutingControl) { mSkipRoutingControl = false; @@ -329,7 +331,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { void updateActiveSource(ActiveSource newActive) { assertRunOnServiceThread(); // Seq #14 - if (mActiveSource.equals(newActive)) { + if (getActiveSource().equals(newActive)) { return; } setActiveSource(newActive); @@ -402,7 +404,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); return; } - mActiveSource.invalidate(); + getActiveSource().invalidate(); if (!mService.isControlEnabled()) { setActivePortId(portId); invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); @@ -518,7 +520,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } else { // No HDMI port to switch to was found. Notify the input change listers to // switch to the lastly shown internal input. - mActiveSource.invalidate(); + getActiveSource().invalidate(); setActivePath(Constants.INVALID_PHYSICAL_ADDRESS); mService.invokeInputChangeListener(HdmiDeviceInfo.INACTIVE_DEVICE); } @@ -685,7 +687,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { byte[] params = message.getParams(); int currentPath = HdmiUtils.twoBytesToInt(params); if (HdmiUtils.isAffectingActiveRoutingPath(getActivePath(), currentPath)) { - mActiveSource.invalidate(); + getActiveSource().invalidate(); removeAction(RoutingControlAction.class); int newPath = HdmiUtils.twoBytesToInt(params, 2); addAndStartAction(new RoutingControlAction(this, newPath, true, null)); diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index bb96a519c949f..53d84a86a95ad 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -140,6 +140,10 @@ public class HdmiControlService extends SystemService { static final int STANDBY_SCREEN_OFF = 0; static final int STANDBY_SHUTDOWN = 1; + // Logical address of the active source. + @GuardedBy("mLock") + protected final ActiveSource mActiveSource = new ActiveSource(); + private static final boolean isHdmiCecNeverClaimPlaybackLogicAddr = SystemProperties.getBoolean( Constants.PROPERTY_HDMI_CEC_NEVER_CLAIM_PLAYBACK_LOGICAL_ADDRESS, false); @@ -2488,6 +2492,77 @@ public class HdmiControlService extends SystemService { setLastInputForMhl(Constants.INVALID_PORT_ID); } + ActiveSource getActiveSource() { + synchronized (mLock) { + return mActiveSource; + } + } + + void setActiveSource(int logicalAddress, int physicalAddress) { + synchronized (mLock) { + mActiveSource.logicalAddress = logicalAddress; + mActiveSource.physicalAddress = physicalAddress; + } + } + + // This method should only be called when the device can be the active source + // and all the device types call into this method. + // For example, when receiving broadcast messages, all the device types will call this + // method but only one of them will be the Active Source. + protected void setAndBroadcastActiveSource( + HdmiCecMessage message, int physicalAddress, int deviceType) { + // If the device has both playback and audio system logical addresses, + // playback will claim active source. Otherwise audio system will. + if (deviceType == HdmiDeviceInfo.DEVICE_PLAYBACK) { + HdmiCecLocalDevicePlayback playback = playback(); + playback.setIsActiveSource(true); + playback.wakeUpIfActiveSource(); + playback.maySendActiveSource(message.getSource()); + setActiveSource(playback.mAddress, physicalAddress); + } + + if (deviceType == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) { + HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem(); + if (playback() != null) { + audioSystem.setIsActiveSource(false); + } else { + audioSystem.setIsActiveSource(true); + audioSystem.wakeUpIfActiveSource(); + audioSystem.maySendActiveSource(message.getSource()); + setActiveSource(audioSystem.mAddress, physicalAddress); + } + } + } + + // This method should only be called when the device can be the active source + // and only one of the device types calls into this method. + // For example, when receiving One Touch Play, only playback device handles it + // and this method updates Active Source in all the device types sharing the same + // Physical Address. + protected void setAndBroadcastActiveSourceFromOneDeviceType( + int sourceAddress, int physicalAddress) { + // If the device has both playback and audio system logical addresses, + // playback will claim active source. Otherwise audio system will. + HdmiCecLocalDevicePlayback playback = playback(); + HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem(); + if (playback != null) { + playback.setIsActiveSource(true); + playback.wakeUpIfActiveSource(); + playback.maySendActiveSource(sourceAddress); + if (audioSystem != null) { + audioSystem.setIsActiveSource(false); + } + setActiveSource(playback.mAddress, physicalAddress); + } else { + if (audioSystem != null) { + audioSystem.setIsActiveSource(true); + audioSystem.wakeUpIfActiveSource(); + audioSystem.maySendActiveSource(sourceAddress); + setActiveSource(audioSystem.mAddress, physicalAddress); + } + } + } + @ServiceThreadOnly void setLastInputForMhl(int portId) { assertRunOnServiceThread(); diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java index 46611dd1b66ea..1908d69d4e6d0 100644 --- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java +++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java @@ -83,11 +83,10 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { } private void broadcastActiveSource() { - sendCommand(HdmiCecMessageBuilder.buildActiveSource(getSourceAddress(), getSourcePath())); // Because only source device can create this action, it's safe to cast. HdmiCecLocalDeviceSource source = source(); - source.setIsActiveSource(true); - source.setActiveSource(getSourceAddress(), getSourcePath()); + source.mService.setAndBroadcastActiveSourceFromOneDeviceType( + mTargetAddress, getSourcePath()); // Set local active port to HOME when One Touch Play. source.setLocalActivePort(Constants.CEC_SWITCH_HOME); } diff --git a/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java index a6e69656a9567..7e0890fe34a2e 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java @@ -41,7 +41,7 @@ public class SystemAudioInitiationActionFromAvr extends HdmiCecFeatureAction { @Override boolean start() { - if (audioSystem().mActiveSource.physicalAddress == Constants.INVALID_PHYSICAL_ADDRESS) { + if (audioSystem().getActiveSource().physicalAddress == Constants.INVALID_PHYSICAL_ADDRESS) { mState = STATE_WAITING_FOR_ACTIVE_SOURCE; addTimer(mState, HdmiConfig.TIMEOUT_MS); sendRequestActiveSource(); @@ -60,10 +60,8 @@ public class SystemAudioInitiationActionFromAvr extends HdmiCecFeatureAction { return false; } mActionTimer.clearTimerMessage(); - int physicalAddress = HdmiUtils.twoBytesToInt(cmd.getParams()); - if (physicalAddress != getSourcePath()) { - audioSystem().setActiveSource(cmd.getSource(), physicalAddress); - } + // Broadcast message is also handled by other device types + audioSystem().handleActiveSource(cmd); mState = STATE_WAITING_FOR_TV_SUPPORT; queryTvSystemAudioModeSupport(); return true;