Merge "Maintain display power status for playback device" into klp-modular-dev

This commit is contained in:
Jinsuk Kim
2014-04-02 22:39:41 +00:00
committed by Android (Google) Code Review
8 changed files with 217 additions and 20 deletions

View File

@ -160,6 +160,12 @@ public final class HdmiCec {
public static final int MESSAGE_SET_EXTERNAL_TIMER = 0xA2;
public static final int MESSAGE_ABORT = 0xFF;
public static final int POWER_STATUS_UNKNOWN = -1;
public static final int POWER_STATUS_ON = 0;
public static final int POWER_STATUS_STANDBY = 1;
public static final int POWER_TRANSIENT_TO_ON = 2;
public static final int POWER_TRANSIENT_TO_STANDBY = 3;
private static final int[] ADDRESS_TO_TYPE = {
DEVICE_TV, // ADDR_TV
DEVICE_RECORDER, // ADDR_RECORDER_1

View File

@ -110,16 +110,20 @@ public final class HdmiCecClient {
}
/**
* Send <GiveDevicePowerStatus> message.
* Returns true if the TV or attached display is powered on.
* <p>
* The result of this method is only meaningful on playback devices (where the device
* type is {@link HdmiCec#DEVICE_PLAYBACK}).
* </p>
*
* @param address logical address of the device to send the message to, such as
* {@link HdmiCec#ADDR_TV}.
* @return true if TV is on; otherwise false.
*/
public void sendGiveDevicePowerStatus(int address) {
public boolean isTvOn() {
try {
mService.sendGiveDevicePowerStatus(mBinder, address);
return mService.isTvOn(mBinder);
} catch (RemoteException e) {
Log.e(TAG, "sendGiveDevicePowerStatus threw exception ", e);
Log.e(TAG, "isTvOn threw exception ", e);
}
return false;
}
}

View File

@ -45,6 +45,9 @@ public final class HdmiCecManager {
* @return {@link HdmiCecClient} instance. {@code null} on failure.
*/
public HdmiCecClient getClient(int type, HdmiCecClient.Listener listener) {
if (mService == null) {
return null;
}
try {
IBinder b = mService.allocateLogicalDevice(type, getListenerWrapper(listener));
return HdmiCecClient.create(mService, b);

View File

@ -34,7 +34,7 @@ interface IHdmiCecService {
void sendInactiveSource(IBinder b);
void sendImageViewOn(IBinder b);
void sendTextViewOn(IBinder b);
void sendGiveDevicePowerStatus(IBinder b, int address);
boolean isTvOn(IBinder b);
void sendMessage(IBinder b, in HdmiCecMessage message);
}

View File

@ -27,8 +27,12 @@ import java.util.ArrayList;
import java.util.List;
/**
* CecDevice class represents a CEC logical device characterized
* by its device type. A physical device can contain the functions of
* HdmiCecDevice class represents a CEC logical device characterized
* by its device type. It is a superclass of those serving concrete device type.
* Currently we're interested in playback(one of sources), display(sink) device type
* only. The support for the other types like recorder, audio system will come later.
*
* <p>A physical device can contain the functions of
* more than one logical device, in which case it should create
* as many logical devices as necessary.
*
@ -41,7 +45,7 @@ import java.util.List;
*
* <p>Declared as package-private, accessed by HdmiCecService only.
*/
final class HdmiCecDevice {
abstract class HdmiCecDevice {
private static final String TAG = "HdmiCecDevice";
private final int mType;
@ -49,18 +53,38 @@ final class HdmiCecDevice {
// List of listeners to the message/event coming to the device.
private final List<IHdmiCecListener> mListeners = new ArrayList<IHdmiCecListener>();
private final Binder mBinder = new Binder();
private final HdmiCecService mService;
private String mName;
private boolean mIsActiveSource;
/**
* Factory method that creates HdmiCecDevice instance to the device type.
*/
public static HdmiCecDevice create(HdmiCecService service, int type) {
if (type == HdmiCec.DEVICE_PLAYBACK) {
return new HdmiCecDevicePlayback(service, type);
} else if (type == HdmiCec.DEVICE_TV) {
return new HdmiCecDeviceTv(service, type);
}
return null;
}
/**
* Constructor.
*/
public HdmiCecDevice(int type) {
public HdmiCecDevice(HdmiCecService service, int type) {
mService = service;
mType = type;
mIsActiveSource = false;
}
/**
* Called right after the class is instantiated. This method can be used to
* implement any initialization tasks for the instance.
*/
abstract public void initialize();
/**
* Return the binder token that identifies this instance.
*/
@ -68,6 +92,13 @@ final class HdmiCecDevice {
return mBinder;
}
/**
* Return the service instance.
*/
public HdmiCecService getService() {
return mService;
}
/**
* Return the type of this device.
*/
@ -128,6 +159,7 @@ final class HdmiCecDevice {
if (opcode == HdmiCec.MESSAGE_ACTIVE_SOURCE) {
mIsActiveSource = false;
}
if (mListeners.size() == 0) {
return;
}
@ -167,4 +199,13 @@ final class HdmiCecDevice {
public void setIsActiveSource(boolean state) {
mIsActiveSource = state;
}
/**
* Check if the connected sink device is in powered-on state. The default implementation
* simply returns false. Should be overriden by subclass to report the correct state.
*/
public boolean isSinkDeviceOn() {
Log.w(TAG, "Not valid for the device type: " + mType);
return false;
}
}

View File

@ -0,0 +1,98 @@
/*
* Copyright (C) 2014 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 android.hardware.hdmi.HdmiCec;
/**
* Class for the logical device of playback type. Devices such as DVD/Blueray player
* that support 'playback' feature are classified as playback device. It is common
* that they don't have built-in display, therefore need to talk, stream their contents
* to TV/display device which is connected through HDMI cable.
*
* <p>It closely monitors the status of display device (other devices can be of interest
* too, but with much less priority), declares itself as 'active source' to have
* display show its output, switch the source state as ordered by display that may be
* talking to many other devices connected to it. It also receives commands from display
* such as remote control signal, standby, status report, playback mode.
*
* <p>Declared as package-private, accessed by HdmiCecService only.
*/
final class HdmiCecDevicePlayback extends HdmiCecDevice {
private static final String TAG = "HdmiCecDevicePlayback";
private int mSinkDevicePowerStatus;
/**
* Constructor.
*/
public HdmiCecDevicePlayback(HdmiCecService service, int type) {
super(service, type);
mSinkDevicePowerStatus = HdmiCec.POWER_STATUS_UNKNOWN;
}
@Override
public void initialize() {
// Playback device tries to obtain the power status of TV/display when created,
// and maintains it all through its lifecycle. CEC spec says there is
// a maximum 1 second response time. Therefore it should be kept in mind
// that there can be as much amount of period of time the power status
// of the display remains unknown after the query is sent out.
queryTvPowerStatus();
}
private void queryTvPowerStatus() {
getService().sendMessage(getType(), HdmiCec.ADDR_TV,
HdmiCec.MESSAGE_GIVE_DEVICE_POWER_STATUS, HdmiCecService.EMPTY_PARAM);
}
@Override
public void handleMessage(int srcAddress, int dstAddress, int opcode, byte[] params) {
// Updates power status of display. The cases are:
// 1) Response for the queried power status request arrives. Update the status.
// 2) Broadcast or direct <Standby> command from TV, which is sent as TV itself is going
// into standby mode too.
// 3) Broadcast <Report Physical Address> command from TV, which is sent while it boots up.
if (opcode == HdmiCec.MESSAGE_REPORT_POWER_STATUS) {
mSinkDevicePowerStatus = params[0];
} else if (srcAddress == HdmiCec.ADDR_TV) {
if (opcode == HdmiCec.MESSAGE_STANDBY) {
mSinkDevicePowerStatus = HdmiCec.POWER_STATUS_STANDBY;
} else if (opcode == HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS) {
mSinkDevicePowerStatus = HdmiCec.POWER_STATUS_ON;
}
}
super.handleMessage(srcAddress, dstAddress, opcode, params);
}
@Override
public void handleHotplug(boolean connected) {
// If cable get disconnected sink device becomes unreachable. Switch the status
// to unknown, and query the status once the cable gets connected back.
if (!connected) {
mSinkDevicePowerStatus = HdmiCec.POWER_STATUS_UNKNOWN;
} else {
queryTvPowerStatus();
}
super.handleHotplug(connected);
}
@Override
public boolean isSinkDeviceOn() {
return mSinkDevicePowerStatus == HdmiCec.POWER_STATUS_ON;
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2014 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;
/**
* Class for logical device of TV type.
*/
final class HdmiCecDeviceTv extends HdmiCecDevice {
private static final String TAG = "HdmiCecDeviceTv";
/**
* Constructor.
*/
public HdmiCecDeviceTv(HdmiCecService service, int type) {
super(service, type);
}
public void initialize() {
// TODO: Do the initialization task for TV device here.
}
}

View File

@ -30,6 +30,7 @@ import android.util.Log;
import android.util.SparseArray;
import com.android.server.SystemService;
import libcore.util.EmptyArray;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@ -37,8 +38,6 @@ import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Locale;
import libcore.util.EmptyArray;
/**
* Provides a service for sending and processing HDMI-CEC messages, and providing
* the information on HDMI settings in general.
@ -65,7 +64,7 @@ public final class HdmiCecService extends SystemService {
private static final String PERMISSION = "android.permission.HDMI_CEC";
private static final byte[] EMPTY_PARAM = EmptyArray.BYTE;
static final byte[] EMPTY_PARAM = EmptyArray.BYTE;
public HdmiCecService(Context context) {
super(context);
@ -76,7 +75,9 @@ public final class HdmiCecService extends SystemService {
@Override
public void onStart() {
mNativePtr = nativeInit(this);
publishBinderService(Context.HDMI_CEC_SERVICE, new BinderService());
if (mNativePtr != 0) {
publishBinderService(Context.HDMI_CEC_SERVICE, new BinderService());
}
}
/**
@ -88,7 +89,6 @@ public final class HdmiCecService extends SystemService {
// but better be handled in service by turning off the screen
// or putting the device into suspend mode. List up such messages
// and handle them here.
int type = HdmiCec.getTypeFromAddress(dstAddress);
synchronized (mLock) {
if (dstAddress == HdmiCec.ADDR_BROADCAST) {
for (int i = 0; i < mLogicalDevices.size(); ++i) {
@ -96,6 +96,7 @@ public final class HdmiCecService extends SystemService {
params);
}
} else {
int type = HdmiCec.getTypeFromAddress(dstAddress);
HdmiCecDevice device = mLogicalDevices.get(type);
if (device == null) {
Log.w(TAG, "logical device not found. type: " + type);
@ -205,6 +206,11 @@ public final class HdmiCecService extends SystemService {
throw new IllegalArgumentException("Device not found");
}
// package-private. Used by HdmiCecDevice and its subclasses only.
void sendMessage(int type, int address, int opcode, byte[] params) {
nativeSendMessage(mNativePtr, type, address, opcode, params);
}
private final class ListenerRecord implements IBinder.DeathRecipient {
private final IHdmiCecListener mListener;
private final int mType;
@ -248,8 +254,13 @@ public final class HdmiCecService extends SystemService {
Log.e(TAG, "Logical address was not allocated");
return null;
} else {
device = new HdmiCecDevice(type);
device = HdmiCecDevice.create(HdmiCecService.this, type);
if (device == null) {
Log.e(TAG, "Device type not supported yet.");
return null;
}
device.setName(HdmiCec.getDefaultDeviceName(address));
device.initialize();
mLogicalDevices.put(type, device);
}
}
@ -331,12 +342,11 @@ public final class HdmiCecService extends SystemService {
}
@Override
public void sendGiveDevicePowerStatus(IBinder b, int address) {
public boolean isTvOn(IBinder b) {
enforceAccessPermission();
synchronized (mLock) {
HdmiCecDevice device = getLogicalDeviceLocked(b);
nativeSendMessage(mNativePtr, device.getType(), address,
HdmiCec.MESSAGE_GIVE_DEVICE_POWER_STATUS, EMPTY_PARAM);
return device.isSinkDeviceOn();
}
}