Merge "Handle the power state change."

This commit is contained in:
Yuncheol Heo
2014-07-04 01:47:37 +00:00
committed by Android (Google) Code Review
7 changed files with 384 additions and 6 deletions

View File

@@ -155,6 +155,18 @@ abstract class HdmiCecLocalDevice {
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;
}
@@ -275,6 +287,67 @@ abstract class HdmiCecLocalDevice {
return false;
}
@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();
@@ -323,6 +396,10 @@ abstract class HdmiCecLocalDevice {
@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 @@ abstract class HdmiCecLocalDevice {
void removeAction(final FeatureAction action) {
assertRunOnServiceThread();
mActions.remove(action);
checkIfPendingActionsCleared();
}
// Remove all actions matched with the given Class type.
@@ -383,8 +461,14 @@ abstract class HdmiCecLocalDevice {
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 @@ abstract class HdmiCecLocalDevice {
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) {}
}

View File

@@ -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 @@ import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
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 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice {
@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();
}
}

View File

@@ -441,6 +441,26 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
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;
}
}

View File

@@ -269,6 +269,18 @@ public class HdmiCecMessageBuilder {
physicalAddressToParam(physicalAddress));
}
/**
* Build &lt;Inactive Source&gt; 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 &lt;Set Stream Path&gt; command.
*
@@ -312,6 +324,21 @@ public class HdmiCecMessageBuilder {
return buildCommand(src, dest, HdmiCec.MESSAGE_GIVE_DEVICE_POWER_STATUS);
}
/**
* Build &lt;Report Power Status&gt; 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 &lt;System Audio Mode Request&gt; command.
*
@@ -388,6 +415,17 @@ public class HdmiCecMessageBuilder {
return buildCommand(src, dest, HdmiCec.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS);
}
/**
* Build &lt;Standby&gt; 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. ******/
/**

View File

@@ -45,6 +45,8 @@ final class HdmiConstants {
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.

View File

@@ -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.Handler;
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 @@ public final class HdmiControlService extends SystemService {
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 @@ public final class HdmiControlService extends SystemService {
// 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 @@ public final class HdmiControlService extends SystemService {
@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 @@ public final class HdmiControlService extends SystemService {
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 @@ public final class HdmiControlService extends SystemService {
// 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 @@ public final class HdmiControlService extends SystemService {
}
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 final class HdmiControlService extends SystemService {
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 @@ public final class HdmiControlService extends SystemService {
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);
}
}

View File

@@ -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 @@ final class OneTouchPlayAction extends FeatureAction {
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 @@ final class OneTouchPlayAction extends FeatureAction {
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() {