Merge "Handle the power state change."
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 87cabc6..ee02883 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -155,6 +155,18 @@
return handleSystemAudioModeStatus(message);
case HdmiCec.MESSAGE_REPORT_AUDIO_STATUS:
return handleReportAudioStatus(message);
+ case HdmiCec.MESSAGE_STANDBY:
+ return handleStandby(message);
+ case HdmiCec.MESSAGE_TEXT_VIEW_ON:
+ return handleTextViewOn(message);
+ case HdmiCec.MESSAGE_IMAGE_VIEW_ON:
+ return handleImageViewOn(message);
+ case HdmiCec.MESSAGE_USER_CONTROL_PRESSED:
+ return handleUserControlPressed(message);
+ case HdmiCec.MESSAGE_SET_STREAM_PATH:
+ return handleSetStreamPath(message);
+ case HdmiCec.MESSAGE_GIVE_DEVICE_POWER_STATUS:
+ return handleGiveDevicePowerStatus(message);
default:
return false;
}
@@ -276,6 +288,67 @@
}
@ServiceThreadOnly
+ protected boolean handleStandby(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ // Seq #12
+ if (mService.isControlEnabled() && !isInPresetInstallationMode()
+ && mService.isPowerOnOrTransient()) {
+ mService.standby();
+ return true;
+ }
+ return false;
+ }
+
+ @ServiceThreadOnly
+ protected boolean handleUserControlPressed(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ final int opCode = message.getOpcode();
+ final byte[] params = message.getParams();
+ if (mService.isPowerOnOrTransient() && isPowerOffOrToggleCommand(message)) {
+ mService.standby();
+ return true;
+ } else if (mService.isPowerStandbyOrTransient() && isPowerOnOrToggleCommand(message)) {
+ mService.wakeUp();
+ return true;
+ }
+ return false;
+ }
+
+ private static boolean isPowerOnOrToggleCommand(HdmiCecMessage message) {
+ byte[] params = message.getParams();
+ return message.getOpcode() == HdmiCec.MESSAGE_USER_CONTROL_PRESSED && params.length == 1
+ && (params[0] == HdmiConstants.UI_COMMAND_POWER
+ || params[0] == HdmiConstants.UI_COMMAND_POWER_ON_FUNCTION
+ || params[0] == HdmiConstants.UI_COMMAND_POWER_TOGGLE_FUNCTION);
+ }
+
+ private static boolean isPowerOffOrToggleCommand(HdmiCecMessage message) {
+ byte[] params = message.getParams();
+ return message.getOpcode() == HdmiCec.MESSAGE_USER_CONTROL_PRESSED && params.length == 1
+ && (params[0] == HdmiConstants.UI_COMMAND_POWER
+ || params[0] == HdmiConstants.UI_COMMAND_POWER_OFF_FUNCTION
+ || params[0] == HdmiConstants.UI_COMMAND_POWER_TOGGLE_FUNCTION);
+ }
+
+ protected boolean handleTextViewOn(HdmiCecMessage message) {
+ return false;
+ }
+
+ protected boolean handleImageViewOn(HdmiCecMessage message) {
+ return false;
+ }
+
+ protected boolean handleSetStreamPath(HdmiCecMessage message) {
+ return false;
+ }
+
+ protected boolean handleGiveDevicePowerStatus(HdmiCecMessage message) {
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPowerStatus(
+ mAddress, message.getSource(), mService.getPowerStatus()));
+ return true;
+ }
+
+ @ServiceThreadOnly
final void handleAddressAllocated(int logicalAddress) {
assertRunOnServiceThread();
mAddress = mPreferredAddress = logicalAddress;
@@ -323,6 +396,10 @@
@ServiceThreadOnly
void addAndStartAction(final FeatureAction action) {
assertRunOnServiceThread();
+ if (mService.isPowerStandbyOrTransient()) {
+ Slog.w(TAG, "Skip the action during Standby: " + action);
+ return;
+ }
mActions.add(action);
action.start();
}
@@ -361,6 +438,7 @@
void removeAction(final FeatureAction action) {
assertRunOnServiceThread();
mActions.remove(action);
+ checkIfPendingActionsCleared();
}
// Remove all actions matched with the given Class type.
@@ -383,8 +461,14 @@
mActions.remove(action);
}
}
+ checkIfPendingActionsCleared();
}
+ protected void checkIfPendingActionsCleared() {
+ if (mActions.isEmpty()) {
+ mService.onPendingActionsCleared();
+ }
+ }
protected void assertRunOnServiceThread() {
if (Looper.myLooper() != mService.getServiceLooper()) {
throw new IllegalStateException("Should run on service thread.");
@@ -488,4 +572,23 @@
assertRunOnServiceThread();
return mService.pathToPortId(newPath);
}
+
+ /**
+ * Called when the system started transition to standby mode.
+ *
+ * @param initiatedByCec true if this power sequence is initiated
+ * by the reception the CEC messages like <StandBy>
+ */
+ protected void onTransitionToStandby(boolean initiatedByCec) {
+ // If there are no outstanding actions, we'll go to STANDBY state.
+ checkIfPendingActionsCleared();
+ }
+
+ /**
+ * Called when the system goes to standby mode.
+ *
+ * @param initiatedByCec true if this power sequence is initiated
+ * by the reception the CEC messages like <StandBy>
+ */
+ protected void onStandBy(boolean initiatedByCec) {}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index d7e36b3..a631172 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -17,6 +17,7 @@
package com.android.server.hdmi;
import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecMessage;
import android.hardware.hdmi.IHdmiControlCallback;
import android.os.RemoteException;
import android.util.Slog;
@@ -29,6 +30,8 @@
final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice {
private static final String TAG = "HdmiCecLocalDevicePlayback";
+ private boolean mIsActiveSource = false;
+
HdmiCecLocalDevicePlayback(HdmiControlService service) {
super(service, HdmiCec.DEVICE_PLAYBACK);
}
@@ -93,7 +96,57 @@
@ServiceThreadOnly
void onHotplug(int portId, boolean connected) {
assertRunOnServiceThread();
- // TODO: clear devices connected to the given port id.
mCecMessageCache.flushAll();
+ mIsActiveSource = false;
+ if (connected && mService.isPowerStandbyOrTransient()) {
+ mService.wakeUp();
+ }
+ }
+
+ @ServiceThreadOnly
+ void markActiveSource() {
+ assertRunOnServiceThread();
+ mIsActiveSource = true;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleActiveSource(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
+ if (physicalAddress != mService.getPhysicalAddress()) {
+ mIsActiveSource = false;
+ if (mService.isPowerOnOrTransient()) {
+ mService.standby();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleSetStreamPath(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
+ if (physicalAddress == mService.getPhysicalAddress()) {
+ if (mService.isPowerStandbyOrTransient()) {
+ mService.wakeUp();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected void onTransitionToStandby(boolean initiatedByCec) {
+ assertRunOnServiceThread();
+ if (!initiatedByCec && mIsActiveSource) {
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildInactiveSource(
+ mAddress, mService.getPhysicalAddress()));
+ }
+ mIsActiveSource = false;
+ checkIfPendingActionsCleared();
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 72d44fa..868571e 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -441,6 +441,26 @@
return true;
}
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleTextViewOn(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ if (mService.isPowerStandbyOrTransient()) {
+ mService.wakeUp();
+ }
+ // TODO: Connect to Hardware input manager to invoke TV App with the appropriate channel
+ // that represents the source device.
+ return true;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleImageViewOn(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ // Currently, it's the same as <Text View On>.
+ return handleTextViewOn(message);
+ }
+
@ServiceThreadOnly
private void launchDeviceDiscovery() {
assertRunOnServiceThread();
@@ -985,4 +1005,42 @@
assertRunOnServiceThread();
mAutoDeviceOff = enabled;
}
+
+ @Override
+ @ServiceThreadOnly
+ protected void onTransitionToStandby(boolean initiatedByCec) {
+ assertRunOnServiceThread();
+ // Remove any repeated working actions.
+ // HotplugDetectionAction will be reinstated during the wake up process.
+ // HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
+ // LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery().
+ removeAction(HotplugDetectionAction.class);
+ checkIfPendingActionsCleared();
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected void onStandBy(boolean initiatedByCec) {
+ assertRunOnServiceThread();
+ // Seq #11
+ if (!mService.isControlEnabled()) {
+ return;
+ }
+ if (!initiatedByCec) {
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(
+ mAddress, HdmiCec.ADDR_BROADCAST));
+ }
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleStandby(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ // Seq #12
+ // Tv accepts directly addressed <Standby> only.
+ if (message.getDestination() == mAddress) {
+ super.handleStandby(message);
+ }
+ return false;
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index 361a063..b1ea134 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -270,6 +270,18 @@
}
/**
+ * Build <Inactive Source> command.
+ *
+ * @param src source address of command
+ * @param physicalAddress physical address of the device to become inactive
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildInactiveSource(int src, int physicalAddress) {
+ return buildCommand(src, HdmiCec.ADDR_BROADCAST, HdmiCec.MESSAGE_INACTIVE_SOURCE,
+ physicalAddressToParam(physicalAddress));
+ }
+
+ /**
* Build <Set Stream Path> command.
*
* <p>This is a broadcast message sent to all devices on the bus.
@@ -313,6 +325,21 @@
}
/**
+ * Build <Report Power Status> command.
+ *
+ * @param src source address of command
+ * @param dest destination address of command
+ * @param powerStatus power status of the device
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildReportPowerStatus(int src, int dest, int powerStatus) {
+ byte[] param = new byte[] {
+ (byte) (powerStatus)
+ };
+ return buildCommand(src, dest, HdmiCec.MESSAGE_REPORT_POWER_STATUS, param);
+ }
+
+ /**
* Build <System Audio Mode Request> command.
*
* @param src source address of command
@@ -388,6 +415,17 @@
return buildCommand(src, dest, HdmiCec.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS);
}
+ /**
+ * Build <Standby> command.
+ *
+ * @param src source address of command
+ * @param dest destination address of command
+ * @return newly created {@link HdmiCecMessage}
+ */
+ public static HdmiCecMessage buildStandby(int src, int dest) {
+ return buildCommand(src, dest, HdmiCec.MESSAGE_STANDBY);
+ }
+
/***** Please ADD new buildXXX() methods above. ******/
/**
diff --git a/services/core/java/com/android/server/hdmi/HdmiConstants.java b/services/core/java/com/android/server/hdmi/HdmiConstants.java
index ab5b8d8..c0accc3 100644
--- a/services/core/java/com/android/server/hdmi/HdmiConstants.java
+++ b/services/core/java/com/android/server/hdmi/HdmiConstants.java
@@ -45,6 +45,8 @@
static final int UI_COMMAND_MUTE = 0x43;
static final int UI_COMMAND_MUTE_FUNCTION = 0x65;
static final int UI_COMMAND_RESTORE_VOLUME_FUNCTION = 0x66;
+ static final int UI_COMMAND_POWER_TOGGLE_FUNCTION = 0x6B;
+ static final int UI_COMMAND_POWER_OFF_FUNCTION = 0x6C;
static final int UI_COMMAND_POWER_ON_FUNCTION = 0x6D;
// Bit mask used to get the routing path of the top level device.
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 63df9a2..95d8333 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -17,7 +17,10 @@
package com.android.server.hdmi;
import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.hardware.hdmi.HdmiCec;
import android.hardware.hdmi.HdmiCecDeviceInfo;
import android.hardware.hdmi.HdmiCecMessage;
@@ -34,8 +37,9 @@
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
+import android.os.PowerManager;
import android.os.RemoteException;
-import android.util.Log;
+import android.os.SystemClock;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -86,6 +90,24 @@
void onPollingFinished(List<Integer> ackedAddress);
}
+ private class PowerStateReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getAction()) {
+ case Intent.ACTION_SCREEN_OFF:
+ if (isPowerOnOrTransient()) {
+ onStandby();
+ }
+ break;
+ case Intent.ACTION_SCREEN_ON:
+ if (isPowerStandbyOrTransient()) {
+ onWakeUp();
+ }
+ break;
+ }
+ }
+ }
+
// A thread to handle synchronous IO of CEC and MHL control service.
// Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
// and sparse call it shares a thread to handle IO operations.
@@ -143,6 +165,14 @@
// from being modified.
private List<HdmiPortInfo> mPortInfo;
+ private final PowerStateReceiver mPowerStateReceiver = new PowerStateReceiver();
+
+ @ServiceThreadOnly
+ private int mPowerStatus = HdmiCec.POWER_STATUS_STANDBY;
+
+ @ServiceThreadOnly
+ private boolean mStandbyMessageReceived = false;
+
public HdmiControlService(Context context) {
super(context);
mLocalDevices = HdmiUtils.asImmutableList(getContext().getResources().getIntArray(
@@ -152,9 +182,11 @@
@Override
public void onStart() {
mIoThread.start();
+ mPowerStatus = HdmiCec.POWER_STATUS_TRANSIENT_TO_ON;
mCecController = HdmiCecController.create(this);
if (mCecController != null) {
+ mCecController.setOption(HdmiCec.OPTION_CEC_SERVICE_CONTROL, HdmiCec.DISABLED);
initializeLocalDevices(mLocalDevices);
} else {
Slog.i(TAG, "Device does not support HDMI-CEC.");
@@ -167,6 +199,14 @@
mPortInfo = initPortInfo();
publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
+ // Register broadcast receiver for power state change.
+ if (mCecController != null || mMhlController != null) {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(Intent.ACTION_SCREEN_ON);
+ getContext().registerReceiver(mPowerStateReceiver, filter);
+ }
+
// TODO: Read the preference for SystemAudioMode and initialize mSystemAudioMode and
// start to monitor the preference value and invoke SystemAudioActionFromTv if needed.
mHdmiControlEnabled = true;
@@ -199,6 +239,9 @@
// Address allocation completed for all devices. Notify each device.
if (deviceTypes.size() == finished.size()) {
+ if (mPowerStatus == HdmiCec.POWER_STATUS_TRANSIENT_TO_ON) {
+ mPowerStatus = HdmiCec.POWER_STATUS_ON;
+ }
notifyAddressAllocated(devices);
}
}
@@ -534,7 +577,7 @@
}
private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
- private IHdmiSystemAudioModeChangeListener mListener;
+ private final IHdmiSystemAudioModeChangeListener mListener;
public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
mListener = listener;
@@ -798,7 +841,7 @@
public void run() {
HdmiCecLocalDeviceTv tv = tv();
if (tv == null) {
- Log.w(TAG, "Local tv device not available to change arc mode.");
+ Slog.w(TAG, "Local tv device not available to change arc mode.");
return;
}
}
@@ -995,4 +1038,82 @@
return mHdmiControlEnabled;
}
}
+
+ int getPowerStatus() {
+ return mPowerStatus;
+ }
+
+ boolean isPowerOnOrTransient() {
+ return mPowerStatus == HdmiCec.POWER_STATUS_ON
+ || mPowerStatus == HdmiCec.POWER_STATUS_TRANSIENT_TO_ON;
+ }
+
+ boolean isPowerStandbyOrTransient() {
+ return mPowerStatus == HdmiCec.POWER_STATUS_STANDBY
+ || mPowerStatus == HdmiCec.POWER_STATUS_TRANSIENT_TO_STANDBY;
+ }
+
+ boolean isPowerStandby() {
+ return mPowerStatus == HdmiCec.POWER_STATUS_STANDBY;
+ }
+
+ @ServiceThreadOnly
+ void wakeUp() {
+ assertRunOnServiceThread();
+ PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
+ pm.wakeUp(SystemClock.uptimeMillis());
+ // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
+ // the intent, the sequence will continue at onWakeUp().
+ }
+
+ @ServiceThreadOnly
+ void standby() {
+ assertRunOnServiceThread();
+ mStandbyMessageReceived = true;
+ PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
+ pm.goToSleep(SystemClock.uptimeMillis());
+ // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
+ // the intent, the sequence will continue at onStandby().
+ }
+
+ @ServiceThreadOnly
+ private void onWakeUp() {
+ assertRunOnServiceThread();
+ mPowerStatus = HdmiCec.POWER_STATUS_TRANSIENT_TO_ON;
+ if (mCecController != null) {
+ mCecController.setOption(HdmiCec.OPTION_CEC_SERVICE_CONTROL, HdmiCec.ENABLED);
+ initializeLocalDevices(mLocalDevices);
+ } else {
+ Slog.i(TAG, "Device does not support HDMI-CEC.");
+ }
+ // TODO: Initialize MHL local devices.
+ }
+
+ @ServiceThreadOnly
+ private void onStandby() {
+ assertRunOnServiceThread();
+ mPowerStatus = HdmiCec.POWER_STATUS_TRANSIENT_TO_STANDBY;
+ for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
+ device.onTransitionToStandby(mStandbyMessageReceived);
+ }
+ }
+
+ /**
+ * Called when there are the outstanding actions in the local devices.
+ * This callback is used to wait for when the action queue is empty
+ * during the power state transition to standby.
+ */
+ @ServiceThreadOnly
+ void onPendingActionsCleared() {
+ assertRunOnServiceThread();
+ if (mPowerStatus != HdmiCec.POWER_STATUS_TRANSIENT_TO_STANDBY) {
+ return;
+ }
+ mPowerStatus = HdmiCec.POWER_STATUS_STANDBY;
+ for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
+ device.onStandBy(mStandbyMessageReceived);
+ }
+ mStandbyMessageReceived = false;
+ mCecController.setOption(HdmiCec.OPTION_CEC_SERVICE_CONTROL, HdmiCec.DISABLED);
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
index e0a3a8b..ebb906a 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
@@ -15,9 +15,10 @@
*/
package com.android.server.hdmi;
-import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.hdmi.HdmiCec;
import android.hardware.hdmi.HdmiCecMessage;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
import android.os.RemoteException;
import android.util.Slog;
@@ -54,7 +55,7 @@
private int mPowerStatusCounter = 0;
// Factory method. Ensures arguments are valid.
- static OneTouchPlayAction create(HdmiCecLocalDevice source,
+ static OneTouchPlayAction create(HdmiCecLocalDevicePlayback source,
int targetAddress, IHdmiControlCallback callback) {
if (source == null || callback == null) {
Slog.e(TAG, "Wrong arguments");
@@ -83,6 +84,8 @@
private void broadcastActiveSource() {
sendCommand(HdmiCecMessageBuilder.buildActiveSource(getSourceAddress(), getSourcePath()));
+ // Because only playback device can create this action, it's safe to cast.
+ playback().markActiveSource();
}
private void queryDevicePowerStatus() {