Merge "Source device to toggle and follow TV power"
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index f1534d9..eef4089 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -832,6 +832,22 @@
}
/**
+ * For CEC source devices (OTT/STB/Audio system): toggle the power status of the HDMI-connected
+ * display and follow the display's new power status.
+ * For all other devices: no functionality.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+ public void toggleAndFollowTvPower() {
+ try {
+ mService.toggleAndFollowTvPower();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Controls whether volume control commands via HDMI CEC are enabled.
*
* <p>When disabled:
diff --git a/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
index fab56b8..202e090 100644
--- a/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
+++ b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
@@ -67,6 +67,11 @@
}
@Override
+ public void toggleAndFollowTvPower() {
+ HdmiControlServiceWrapper.this.toggleAndFollowTvPower();
+ }
+
+ @Override
public void queryDisplayStatus(IHdmiControlCallback callback) {
HdmiControlServiceWrapper.this.queryDisplayStatus(callback);
}
@@ -360,6 +365,9 @@
public void oneTouchPlay(IHdmiControlCallback callback) {}
/** @hide */
+ public void toggleAndFollowTvPower() {}
+
+ /** @hide */
public void queryDisplayStatus(IHdmiControlCallback callback) {}
/** @hide */
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index af9d3ac..6d0c688 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -42,6 +42,7 @@
int[] getSupportedTypes();
HdmiDeviceInfo getActiveSource();
void oneTouchPlay(IHdmiControlCallback callback);
+ void toggleAndFollowTvPower();
void queryDisplayStatus(IHdmiControlCallback callback);
void addHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener);
void removeHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener);
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
index 4094f83..306388f 100644
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
@@ -191,6 +191,10 @@
}
@Override
+ public void toggleAndFollowTvPower() {
+ }
+
+ @Override
public void queryDisplayStatus(final IHdmiControlCallback callback) {
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index 62b7d8f..4b58925 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -87,10 +87,14 @@
@ServiceThreadOnly
protected void sendStandby(int deviceId) {
assertRunOnServiceThread();
-
- // Send standby to TV only for now
- int targetAddress = Constants.ADDR_TV;
- mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
+ String sendStandbyOnSleep = mService.getHdmiCecConfig().getStringValue(
+ HdmiControlManager.CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP);
+ if (sendStandbyOnSleep.equals(HdmiControlManager.SEND_STANDBY_ON_SLEEP_BROADCAST)) {
+ mService.sendCecCommand(
+ HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_BROADCAST));
+ return;
+ }
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_TV));
}
@ServiceThreadOnly
@@ -113,6 +117,44 @@
}
@ServiceThreadOnly
+ void toggleAndFollowTvPower() {
+ assertRunOnServiceThread();
+ // Wake up Android framework to take over CEC control from the microprocessor.
+ mService.wakeUp();
+ mService.queryDisplayStatus(new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int status) {
+ if (status == HdmiControlManager.POWER_STATUS_UNKNOWN) {
+ Slog.i(TAG, "TV power toggle: TV power status unknown");
+ sendUserControlPressedAndReleased(Constants.ADDR_TV,
+ HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
+ // Source device remains awake.
+ } else if (status == HdmiControlManager.POWER_STATUS_ON
+ || status == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
+ Slog.i(TAG, "TV power toggle: turning off TV");
+ sendStandby(0 /*unused */);
+ // Source device goes to standby, to follow the toggled TV power state.
+ mService.standby();
+ } else if (status == HdmiControlManager.POWER_STATUS_STANDBY
+ || status == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
+ Slog.i(TAG, "TV power toggle: turning on TV");
+ oneTouchPlay(new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ if (result != HdmiControlManager.RESULT_SUCCESS) {
+ Slog.w(TAG, "Failed to complete One Touch Play. result=" + result);
+ sendUserControlPressedAndReleased(Constants.ADDR_TV,
+ HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
+ }
+ }
+ });
+ // Source device remains awake, to follow the toggled TV power state.
+ }
+ }
+ });
+ }
+
+ @ServiceThreadOnly
protected void onActiveSourceLost() {
// Nothing to do.
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index dcd6c02..1ff8803e 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1686,7 +1686,7 @@
public void oneTouchPlay(final IHdmiControlCallback callback) {
enforceAccessPermission();
int pid = Binder.getCallingPid();
- Slog.d(TAG, "Proccess pid: " + pid + " is calling oneTouchPlay.");
+ Slog.d(TAG, "Process pid: " + pid + " is calling oneTouchPlay.");
runOnServiceThread(new Runnable() {
@Override
public void run() {
@@ -1696,6 +1696,19 @@
}
@Override
+ public void toggleAndFollowTvPower() {
+ enforceAccessPermission();
+ int pid = Binder.getCallingPid();
+ Slog.d(TAG, "Process pid: " + pid + " is calling toggleAndFollowTvPower.");
+ runOnServiceThread(new Runnable() {
+ @Override
+ public void run() {
+ HdmiControlService.this.toggleAndFollowTvPower();
+ }
+ });
+ }
+
+ @Override
public void queryDisplayStatus(final IHdmiControlCallback callback) {
enforceAccessPermission();
runOnServiceThread(new Runnable() {
@@ -2362,7 +2375,23 @@
}
@ServiceThreadOnly
- private void queryDisplayStatus(final IHdmiControlCallback callback) {
+ @VisibleForTesting
+ protected void toggleAndFollowTvPower() {
+ assertRunOnServiceThread();
+ HdmiCecLocalDeviceSource source = playback();
+ if (source == null) {
+ source = audioSystem();
+ }
+
+ if (source == null) {
+ Slog.w(TAG, "Local source device not available");
+ return;
+ }
+ source.toggleAndFollowTvPower();
+ }
+
+ @ServiceThreadOnly
+ protected void queryDisplayStatus(final IHdmiControlCallback callback) {
assertRunOnServiceThread();
if (!mAddressAllocated) {
mDisplayStatusCallback = callback;
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 ce3b8d6..f0d87cb 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -1248,4 +1248,77 @@
assertThat(mHdmiControlService.getActiveSource().getPhysicalAddress()).isEqualTo(
externalDevice.getPhysicalAddress());
}
+
+ @Test
+ public void toggleAndFollowTvPower_ToTv_TvStatusOn() {
+ mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+ HdmiControlManager.CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP,
+ HdmiControlManager.SEND_STANDBY_ON_SLEEP_TO_TV);
+ mStandby = false;
+ mHdmiControlService.toggleAndFollowTvPower();
+ HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV,
+ mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_ON);
+ assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildStandby(
+ mPlaybackLogicalAddress, ADDR_TV);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
+ assertThat(mStandby).isTrue();
+ }
+
+ @Test
+ public void toggleAndFollowTvPower_Broadcast_TvStatusOn() {
+ mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+ HdmiControlManager.CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP,
+ HdmiControlManager.SEND_STANDBY_ON_SLEEP_BROADCAST);
+ mStandby = false;
+ mHdmiControlService.toggleAndFollowTvPower();
+ HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV,
+ mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_ON);
+ assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildStandby(
+ mPlaybackLogicalAddress, ADDR_BROADCAST);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
+ assertThat(mStandby).isTrue();
+ }
+
+ @Test
+ public void toggleAndFollowTvPower_TvStatusStandby() {
+ mStandby = false;
+ mHdmiControlService.toggleAndFollowTvPower();
+ HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV,
+ mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_STANDBY);
+ assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(mPlaybackLogicalAddress,
+ ADDR_TV);
+ HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
+ mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
+ assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
+ assertThat(mNativeWrapper.getResultMessages()).contains(activeSource);
+ assertThat(mStandby).isFalse();
+ }
+
+ @Test
+ public void toggleAndFollowTvPower_TvStatusUnknown() {
+ mStandby = false;
+ mHdmiControlService.toggleAndFollowTvPower();
+ HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV,
+ mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_UNKNOWN);
+ assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed(
+ mPlaybackLogicalAddress, Constants.ADDR_TV,
+ HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
+ HdmiCecMessage userControlReleased = HdmiCecMessageBuilder.buildUserControlReleased(
+ mPlaybackLogicalAddress, Constants.ADDR_TV);
+ assertThat(mNativeWrapper.getResultMessages()).contains(userControlPressed);
+ assertThat(mNativeWrapper.getResultMessages()).contains(userControlReleased);
+ assertThat(mStandby).isFalse();
+ }
}