Refine new device action.

There are many ways to initiate new device action
1. When receives <Report Physcial Address>
2. When receives <Active Source> from unregistered device.

If new device is audio system, it should start
ARC and system audio initiation action.

Along with this consolidate device remove actions.

Change-Id: I189afd8bec7270d6a1734a28632593b71932d9e8
This commit is contained in:
Jungshik Jang
2014-06-12 18:03:04 +09:00
parent 401e3de791
commit 60cffce420
10 changed files with 313 additions and 86 deletions

View File

@@ -67,7 +67,10 @@ final class ActiveSourceHandler {
}
HdmiCecDeviceInfo device = mService.getDeviceInfo(deviceLogicalAddress);
if (device == null) {
// TODO: Start new device action (Device Discovery) sequence 5.
// "New device action" initiated by <Active Source> does not require
// "Routing change action".
mService.addAndStartAction(new NewDeviceAction(mService, mSourceAddress,
deviceLogicalAddress, routingPath, false));
}
if (!mService.isInPresetInstallationMode()) {

View File

@@ -55,9 +55,6 @@ final class DeviceDiscoveryAction extends FeatureAction {
private static final int DEVICE_POLLING_RETRY = 1;
// TODO: Move this to common place
private static final int INVALID_PHYSICAL_ADDRESS = 0xFFFF;
/**
* Interface used to report result of device discovery.
*/
@@ -75,7 +72,7 @@ final class DeviceDiscoveryAction extends FeatureAction {
private static final class DeviceInfo {
private final int mLogicalAddress;
private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
private int mPhysicalAddress = HdmiConstants.INVALID_PHYSICAL_ADDRESS;
private int mVendorId = HdmiCec.UNKNOWN_VENDOR_ID;
private String mDisplayName = "";
private int mDeviceType = HdmiCec.DEVICE_INACTIVE;

View File

@@ -220,4 +220,8 @@ final class DeviceSelectAction extends FeatureAction {
Slog.e(TAG, "Callback failed:" + e);
}
}
int getTargetAddress() {
return mTarget.getLogicalAddress();
}
}

View File

@@ -76,7 +76,7 @@ abstract class HdmiCecLocalDevice {
return onMessage(message);
}
protected boolean onMessage(HdmiCecMessage message) {
protected final boolean onMessage(HdmiCecMessage message) {
switch (message.getOpcode()) {
case HdmiCec.MESSAGE_GET_MENU_LANGUAGE:
return handleGetMenuLanguage(message);
@@ -88,6 +88,8 @@ abstract class HdmiCecLocalDevice {
return handleGiveDeviceVendorId();
case HdmiCec.MESSAGE_GET_CEC_VERSION:
return handleGetCecVersion(message);
case HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS:
return handleReportPhysicalAddress(message);
default:
return false;
}
@@ -143,6 +145,10 @@ abstract class HdmiCecLocalDevice {
return false;
}
protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
return false;
}
final void handleAddressAllocated(int logicalAddress) {
mAddress = mPreferredAddress = logicalAddress;
onAddressAllocated(logicalAddress);

View File

@@ -16,13 +16,15 @@
package com.android.server.hdmi;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.hdmi.HdmiCec;
import android.hardware.hdmi.HdmiCecDeviceInfo;
import android.hardware.hdmi.HdmiCecMessage;
import android.hardware.hdmi.IHdmiControlCallback;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
@@ -46,20 +48,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
mAddress, mService.getVendorId()));
mService.launchDeviceDiscovery(mAddress);
launchDeviceDiscovery();
// TODO: Start routing control action, device discovery action.
}
@Override
protected boolean onMessage(HdmiCecMessage message) {
switch (message.getOpcode()) {
case HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS:
return handleReportPhysicalAddress(message);
default:
return super.onMessage(message);
}
}
/**
* Performs the action 'device select', or 'one touch play' initiated by TV.
*
@@ -98,7 +90,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
return true;
}
private boolean handleReportPhysicalAddress(HdmiCecMessage message) {
@Override
protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
// Ignore if [Device Discovery Action] is going on.
if (mService.hasAction(DeviceDiscoveryAction.class)) {
Slog.i(TAG, "Ignore unrecognizable <Report Physical Address> "
@@ -107,9 +100,15 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
}
int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
mService.addAndStartAction(new NewDeviceAction(mService,
mAddress, message.getSource(), physicalAddress));
int logicalAddress = message.getSource();
// If it is a new device and connected to the tail of active path,
// it's required to change routing path.
boolean requireRoutingChange = !mService.isInDeviceList(physicalAddress, logicalAddress)
&& mService.isTailOfActivePath(physicalAddress);
mService.addAndStartAction(new NewDeviceAction(mService,
mAddress, message.getSource(), physicalAddress,
requireRoutingChange));
return true;
}
@@ -137,4 +136,29 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
HdmiConstants.ABORT_REFUSED));
return true;
}
private void launchDeviceDiscovery() {
mService.clearAllDeviceInfo();
// TODO: Move the following callback to HdmiLocalDeviceTv.
DeviceDiscoveryAction action = new DeviceDiscoveryAction(mService, mAddress,
new DeviceDiscoveryCallback() {
@Override
public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) {
for (HdmiCecDeviceInfo info : deviceInfos) {
mService.addCecDevice(info);
}
// Since we removed all devices when it's start and
// device discovery action does not poll local devices,
// we should put device info of local device manually here
for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
mService.addCecDevice(device.getDeviceInfo());
}
mService.addAndStartAction(new HotplugDetectionAction(mService,
mAddress));
}
});
mService.addAndStartAction(action);
}
}

View File

@@ -75,5 +75,8 @@ final class HdmiConstants {
*/
static final int FLAG_HDMI_OPTION_SYSTEM_CEC_CONTROL = 3;
static final int INVALID_PORT_ID = -1;
static final int INVALID_PHYSICAL_ADDRESS = 0xFFFF;
private HdmiConstants() { /* cannot be instantiated */ }
}

View File

@@ -21,6 +21,7 @@ import android.content.Context;
import android.hardware.hdmi.HdmiCec;
import android.hardware.hdmi.HdmiCecDeviceInfo;
import android.hardware.hdmi.HdmiCecMessage;
import android.hardware.hdmi.HdmiHotplugEvent;
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.hdmi.IHdmiControlService;
@@ -37,7 +38,6 @@ import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.server.SystemService;
import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
import java.util.ArrayList;
@@ -296,7 +296,7 @@ public final class HdmiControlService extends SystemService {
HdmiPortInfo portInfo = getPortInfo(portId);
if (portInfo == null) {
Slog.e(TAG, "Cannot find the port info: " + portId);
return 0xFFFF; // Use HdmiConstants.INVALID_PHYSICAL_ADDRESS;
return HdmiConstants.INVALID_PHYSICAL_ADDRESS;
}
return portInfo.getAddress();
}
@@ -314,7 +314,7 @@ public final class HdmiControlService extends SystemService {
return info.getId();
}
}
return -1; // Use HdmiConstants.INVALID_PORT_ID;
return HdmiConstants.INVALID_PORT_ID;
}
/**
@@ -446,7 +446,12 @@ public final class HdmiControlService extends SystemService {
void setSystemAudioMode(boolean on) {
synchronized (mLock) {
mSystemAudioMode = on;
if (on != mSystemAudioMode) {
mSystemAudioMode = on;
// TODO: Need to set the preference for SystemAudioMode.
// TODO: Need to handle the notification of changing the mode and
// to identify the notification should be handled in the service or TvSettings.
}
}
}
@@ -456,6 +461,19 @@ public final class HdmiControlService extends SystemService {
}
}
/**
* Whether a device of the specified physical address is connected to ARC enabled port.
*/
boolean isConnectedToArcPort(int physicalAddress) {
for (HdmiPortInfo portInfo : mPortInfo) {
if (hasSameTopPort(portInfo.getAddress(), physicalAddress)
&& portInfo.isArcSupported()) {
return true;
}
}
return false;
}
// See if we have an action of a given type in progress.
<T extends FeatureAction> boolean hasAction(final Class<T> clazz) {
for (FeatureAction action : mActions) {
@@ -466,6 +484,17 @@ public final class HdmiControlService extends SystemService {
return false;
}
// Returns all actions matched with given class type.
<T extends FeatureAction> List<T> getActions(final Class<T> clazz) {
ArrayList<T> actions = new ArrayList<>();
for (FeatureAction action : mActions) {
if (action.getClass().equals(clazz)) {
actions.add((T) action);
}
}
return actions;
}
/**
* Remove the given {@link FeatureAction} object from the action queue.
*
@@ -529,6 +558,15 @@ public final class HdmiControlService extends SystemService {
}
}
/**
* Returns whether ARC is enabled or not.
*/
boolean getArcStatus() {
synchronized (mLock) {
return mArcStatusEnabled;
}
}
/**
* Transmit a CEC command to CEC bus.
*
@@ -573,12 +611,23 @@ public final class HdmiControlService extends SystemService {
return dispatchMessageToLocalDevice(message);
}
private boolean dispatchMessageToAction(HdmiCecMessage message) {
for (FeatureAction action : mActions) {
if (action.processCommand(message)) {
return true;
}
}
return false;
}
private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
if (device.dispatchMessage(message)) {
return true;
}
}
Slog.w(TAG, "Unhandled cec command:" + message);
return false;
}
@@ -589,7 +638,18 @@ public final class HdmiControlService extends SystemService {
* @param connected whether to be plugged in or not
*/
void onHotplug(int portNo, boolean connected) {
// TODO: Start "RequestArcInitiationAction" if ARC port.
assertRunOnServiceThread();
// TODO: delegate onHotplug event to each local device.
// Tv device will have permanent HotplugDetectionAction.
List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class);
if (!hotplugActions.isEmpty()) {
// Note that hotplug action is single action running on a machine.
// "pollAllDevicesNow" cleans up timer and start poll action immediately.
hotplugActions.get(0).pollAllDevicesNow();
}
announceHotplugEvent(portNo, connected);
}
/**
@@ -617,35 +677,32 @@ public final class HdmiControlService extends SystemService {
return strategy | iterationStrategy;
}
/**
* Launch device discovery sequence. It starts with clearing the existing device info list.
* Note that it assumes that logical address of all local devices is already allocated.
*
* @param sourceAddress a logical address of tv
*/
void launchDeviceDiscovery(final int sourceAddress) {
// At first, clear all existing device infos.
void clearAllDeviceInfo() {
assertRunOnServiceThread();
mCecController.clearDeviceInfoList();
// TODO: flush cec message cache when CEC is turned off.
}
DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, sourceAddress,
new DeviceDiscoveryCallback() {
@Override
public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) {
for (HdmiCecDeviceInfo info : deviceInfos) {
addCecDevice(info);
}
List<HdmiCecLocalDevice> getAllLocalDevices() {
assertRunOnServiceThread();
return mCecController.getLocalDeviceList();
}
// Add device info of all local devices.
for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
addCecDevice(device.getDeviceInfo());
}
addAndStartAction(new HotplugDetectionAction(HdmiControlService.this,
sourceAddress));
}
});
addAndStartAction(action);
/**
* Whether a device of the specified physical address and logical address exists
* in a device info list. However, both are minimal condition and it could
* be different device from the original one.
*
* @param physicalAddress physical address of a device to be searched
* @param logicalAddress logical address of a device to be searched
* @return true if exist; otherwise false
*/
boolean isInDeviceList(int physicalAddress, int logicalAddress) {
assertRunOnServiceThread();
HdmiCecDeviceInfo device = mCecController.getDeviceInfo(logicalAddress);
if (device == null) {
return false;
}
return device.getPhysicalAddress() == physicalAddress;
}
private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) {
@@ -675,16 +732,6 @@ public final class HdmiControlService extends SystemService {
addAndStartAction(action);
}
private boolean dispatchMessageToAction(HdmiCecMessage message) {
for (FeatureAction action : mActions) {
if (action.processCommand(message)) {
return true;
}
}
Slog.w(TAG, "Unsupported cec command:" + message);
return false;
}
private void handleSetSystemAudioMode(HdmiCecMessage message) {
if (dispatchMessageToAction(message) || !isMessageForSystemAudio(message)) {
return;
@@ -730,10 +777,6 @@ public final class HdmiControlService extends SystemService {
}
}
void addCecDevice(HdmiCecDeviceInfo info) {
mCecController.addDeviceInfo(info);
}
private void enforceAccessPermission() {
getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
}
@@ -768,7 +811,6 @@ public final class HdmiControlService extends SystemService {
});
}
@Override
public void oneTouchPlay(final IHdmiControlCallback callback) {
enforceAccessPermission();
@@ -910,6 +952,17 @@ public final class HdmiControlService extends SystemService {
}
}
/**
* Called when a device is newly added or a new device is detected.
*
* @param info device info of a new device.
*/
void addCecDevice(HdmiCecDeviceInfo info) {
mCecController.addDeviceInfo(info);
// TODO: announce new device detection.
}
/**
* Called when a device is removed or removal of device is detected.
*
@@ -918,9 +971,61 @@ public final class HdmiControlService extends SystemService {
void removeCecDevice(int address) {
mCecController.removeDeviceInfo(address);
mCecMessageCache.flushMessagesFrom(address);
// TODO: announce a device removal.
}
private void announceHotplugEvent(int portNo, boolean connected) {
HdmiHotplugEvent event = new HdmiHotplugEvent(portNo, connected);
synchronized (mLock) {
for (IHdmiHotplugEventListener listener : mHotplugEventListeners) {
invokeHotplugEventListener(listener, event);
}
}
}
private void invokeHotplugEventListener(IHdmiHotplugEventListener listener,
HdmiHotplugEvent event) {
try {
listener.onReceived(event);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
}
}
HdmiCecMessageCache getCecMessageCache() {
return mCecMessageCache;
}
private static boolean hasSameTopPort(int path1, int path2) {
return (path1 & HdmiConstants.ROUTING_PATH_TOP_MASK)
== (path2 & HdmiConstants.ROUTING_PATH_TOP_MASK);
}
/**
* Whether the given path is located in the tail of current active path.
*
* @param path to be tested
* @return true if the given path is located in the tail of current active path; otherwise,
* false
*/
// TODO: move this to local device tv.
boolean isTailOfActivePath(int path) {
// If active routing path is internal source, return false.
if (mActiveRoutingPath == 0) {
return false;
}
for (int i = 12; i >= 0; i -= 4) {
int curActivePath = (mActiveRoutingPath >> i) & 0xF;
if (curActivePath == 0) {
return true;
} else {
int curPath = (path >> i) & 0xF;
if (curPath != curActivePath) {
return false;
}
}
}
return false;
}
}

View File

@@ -90,6 +90,20 @@ final class HotplugDetectionAction extends FeatureAction {
}
}
/**
* Start device polling immediately.
*/
void pollAllDevicesNow() {
// Clear existing timer to avoid overlapped execution
mActionTimer.clearTimerMessage();
mTimeoutCount = 0;
mState = STATE_WAIT_FOR_NEXT_POLLING;
pollAllDevices();
addTimer(mState, POLLING_INTERVAL_MS);
}
// This method is called every 5 seconds.
private void pollDevices() {
// All device check called every 15 seconds.
@@ -180,15 +194,56 @@ final class HotplugDetectionAction extends FeatureAction {
}
private void addDevice(int addedAddress) {
// TODO: implement this.
// Send <Give Physical Address>.
sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(mSourceAddress, addedAddress));
}
private void removeDevice(int removedAddress) {
// TODO: move the following logic to local device once move many logic to
// local device.
mayChangeRoutingPath(removedAddress);
mayCancelDeviceSelect(removedAddress);
mayCancelOneTouchRecord(removedAddress);
mayDisableSystemAudioAndARC(removedAddress);
mService.removeCecDevice(removedAddress);
// TODO: implements following steps.
// 1. Launch routing control sequence
// 2. Stop one touch play sequence if removed device is the device to be selected.
// 3. If audio system, start system audio off and arc off
// 4. Inform device remove to others
}
private void mayChangeRoutingPath(int address) {
// TODO: if removed address is current active source, it should change active source
// path new one. we can keep previous selection or can choose default input,
// such as internal tuner. Consider send intent to app so that app
// can handle it.
}
private void mayCancelDeviceSelect(int address) {
List<DeviceSelectAction> actions = mService.getActions(DeviceSelectAction.class);
if (actions.isEmpty()) {
return;
}
// Should ave only one Device Select Action
DeviceSelectAction action = actions.get(0);
if (action.getTargetAddress() == address) {
mService.removeAction(DeviceSelectAction.class);
}
}
private void mayCancelOneTouchRecord(int address) {
// TODO: implement this.
}
private void mayDisableSystemAudioAndARC(int address) {
if (HdmiCec.getTypeFromAddress(address) != HdmiCec.DEVICE_AUDIO_SYSTEM) {
return;
}
// Turn off system audio mode.
mService.setSystemAudioMode(false);
if (mService.getArcStatus()) {
mService.addAndStartAction(
new RequestArcTerminationAction(mService, mSourceAddress, address));
}
}
}

View File

@@ -48,6 +48,7 @@ final class NewDeviceAction extends FeatureAction {
private final int mDeviceLogicalAddress;
private final int mDevicePhysicalAddress;
private final boolean mRequireRoutingChange;
private int mVendorId;
private String mDisplayName;
@@ -59,17 +60,36 @@ final class NewDeviceAction extends FeatureAction {
* @param sourceAddress logical address to be used as source address
* @param deviceLogicalAddress logical address of the device in interest
* @param devicePhysicalAddress physical address of the device in interest
* @param requireRoutingChange whether to initiate routing change or not
*/
NewDeviceAction(HdmiControlService service, int sourceAddress, int deviceLogicalAddress,
int devicePhysicalAddress) {
int devicePhysicalAddress, boolean requireRoutingChange) {
super(service, sourceAddress);
mDeviceLogicalAddress = deviceLogicalAddress;
mDevicePhysicalAddress = devicePhysicalAddress;
mVendorId = HdmiCec.UNKNOWN_VENDOR_ID;
mRequireRoutingChange = requireRoutingChange;
}
@Override
public boolean start() {
if (HdmiCec.getTypeFromAddress(mSourceAddress) == HdmiCec.DEVICE_AUDIO_SYSTEM) {
if (mService.getAvrDeviceInfo() == null) {
// TODO: Start system audio initiation action
}
// If new device is connected through ARC enabled port,
// initiates ARC channel establishment.
if (mService.isConnectedToArcPort(mDevicePhysicalAddress)) {
mService.addAndStartAction(new RequestArcInitiationAction(mService, mSourceAddress,
mDeviceLogicalAddress));
}
}
if (mRequireRoutingChange) {
startRoutingChange();
}
mState = STATE_WAITING_FOR_SET_OSD_NAME;
if (mayProcessCommandIfCached(mDeviceLogicalAddress, HdmiCec.MESSAGE_SET_OSD_NAME)) {
return true;
@@ -133,6 +153,22 @@ final class NewDeviceAction extends FeatureAction {
return false;
}
private void startRoutingChange() {
// Stop existing routing control.
mService.removeAction(RoutingControlAction.class);
// Send routing change. The the address is a path of the active port.
int newPath = toTopMostPortPath(mDevicePhysicalAddress);
sendCommand(HdmiCecMessageBuilder.buildRoutingChange(mSourceAddress,
mService.getActivePath(), newPath));
mService.addAndStartAction(new RoutingControlAction(mService, mSourceAddress,
mService.pathToPortId(newPath), null));
}
private static int toTopMostPortPath(int physicalAddress) {
return physicalAddress & HdmiConstants.ROUTING_PATH_TOP_MASK;
}
private boolean mayProcessCommandIfCached(int destAddress, int opcode) {
HdmiCecMessage message = mService.getCecMessageCache().getMessage(destAddress, opcode);
if (message != null) {

View File

@@ -16,12 +16,11 @@
package com.android.server.hdmi;
import java.util.concurrent.TimeUnit;
import android.hardware.hdmi.IHdmiControlCallback;
import android.annotation.Nullable;
import android.hardware.hdmi.HdmiCec;
import android.hardware.hdmi.HdmiCecDeviceInfo;
import android.hardware.hdmi.HdmiCecMessage;
import android.hardware.hdmi.IHdmiControlCallback;
import android.os.RemoteException;
import android.util.Slog;
@@ -59,7 +58,7 @@ public class RoutingControlAction extends FeatureAction {
// Time out in milliseconds used for <Report Power Status>
private static final int TIMEOUT_REPORT_POWER_STATUS_MS = 1000;
private final IHdmiControlCallback mCallback;
@Nullable private final IHdmiControlCallback mCallback;
// The latest routing path. Updated by each <Routing Information> from CEC switches.
private int mCurrentRoutingPath;
@@ -167,7 +166,7 @@ public class RoutingControlAction extends FeatureAction {
case STATE_WAIT_FOR_ROUTING_INFORMATION:
HdmiCecDeviceInfo device = mService.getDeviceInfoByPath(mCurrentRoutingPath);
if (device == null) {
maybeChangeActiveInput(mCurrentRoutingPath);
maybeChangeActiveInput(mService.pathToPortId(mCurrentRoutingPath));
} else {
// TODO: Also check followings and then proceed:
// if routing change was neither triggered by TV at CEC enable time, nor
@@ -185,7 +184,7 @@ public class RoutingControlAction extends FeatureAction {
case STATE_WAIT_FOR_REPORT_POWER_STATUS:
int tvPowerStatus = getTvPowerStatus();
if (isPowerStatusOnOrTransientToOn(tvPowerStatus)) {
if (!maybeChangeActiveInput(mCurrentRoutingPath)) {
if (!maybeChangeActiveInput(mService.pathToPortId(mCurrentRoutingPath))) {
sendSetStreamPath();
}
}
@@ -217,15 +216,10 @@ public class RoutingControlAction extends FeatureAction {
mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
addTimer(mState, TIMEOUT_REPORT_POWER_STATUS_MS);
} else {
maybeChangeActiveInput(mCurrentRoutingPath);
maybeChangeActiveInput(mService.pathToPortId(mCurrentRoutingPath));
}
}
// Given the HDMI port id, return the port address.
private int portToPath(int portId) {
return mService.getPortInfo(portId).getAddress();
}
private void invokeCallback(int result) {
if (mCallback == null) {
return;