/* * 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.HdmiPortInfo; import android.os.Handler; import android.os.Looper; import android.os.MessageQueue; import android.util.Slog; import android.util.SparseArray; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Predicate; import com.android.server.hdmi.HdmiAnnotations.IoThreadOnly; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; import com.android.server.hdmi.HdmiControlService.DevicePollingCallback; import libcore.util.EmptyArray; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; /** * Manages HDMI-CEC command and behaviors. It converts user's command into CEC command * and pass it to CEC HAL so that it sends message to other device. For incoming * message it translates the message and delegates it to proper module. * *
It should be careful to access member variables on IO thread because * it can be accessed from system thread as well. * *
It can be created only by {@link HdmiCecController#create} * *
Declared as package-private, accessed by {@link HdmiControlService} only.
*/
final class HdmiCecController {
private static final String TAG = "HdmiCecController";
/**
* Interface to report allocated logical address.
*/
interface AllocateAddressCallback {
/**
* Called when a new logical address is allocated.
*
* @param deviceType requested device type to allocate logical address
* @param logicalAddress allocated logical address. If it is
* {@link Constants#ADDR_UNREGISTERED}, it means that
* it failed to allocate logical address for the given device type
*/
void onAllocated(int deviceType, int logicalAddress);
}
private static final byte[] EMPTY_BODY = EmptyArray.BYTE;
private static final int NUM_LOGICAL_ADDRESS = 16;
// Predicate for whether the given logical address is remote device's one or not.
private final Predicate Declared as package-private, accessed by {@link HdmiControlService} only.
* @param service {@link HdmiControlService} instance used to create internal handler
* and to pass callback for incoming message or event.
* @return {@link HdmiCecController} if device is initialized successfully. Otherwise,
* returns {@code null}.
*/
static HdmiCecController create(HdmiControlService service) {
HdmiCecController controller = new HdmiCecController(service);
long nativePtr = nativeInit(controller, service.getServiceLooper().getQueue());
if (nativePtr == 0L) {
controller = null;
return null;
}
controller.init(nativePtr);
return controller;
}
private void init(long nativePtr) {
mIoHandler = new Handler(mService.getIoLooper());
mControlHandler = new Handler(mService.getServiceLooper());
mNativePtr = nativePtr;
}
@ServiceThreadOnly
void addLocalDevice(int deviceType, HdmiCecLocalDevice device) {
assertRunOnServiceThread();
mLocalDevices.put(deviceType, device);
}
/**
* Allocate a new logical address of the given device type. Allocated
* address will be reported through {@link AllocateAddressCallback}.
*
* Declared as package-private, accessed by {@link HdmiControlService} only.
*
* @param deviceType type of device to used to determine logical address
* @param preferredAddress a logical address preferred to be allocated.
* If sets {@link Constants#ADDR_UNREGISTERED}, scans
* the smallest logical address matched with the given device type.
* Otherwise, scan address will start from {@code preferredAddress}
* @param callback callback interface to report allocated logical address to caller
*/
@ServiceThreadOnly
void allocateLogicalAddress(final int deviceType, final int preferredAddress,
final AllocateAddressCallback callback) {
assertRunOnServiceThread();
runOnIoThread(new Runnable() {
@Override
public void run() {
handleAllocateLogicalAddress(deviceType, preferredAddress, callback);
}
});
}
@IoThreadOnly
private void handleAllocateLogicalAddress(final int deviceType, int preferredAddress,
final AllocateAddressCallback callback) {
assertRunOnIoThread();
int startAddress = preferredAddress;
// If preferred address is "unregistered", start address will be the smallest
// address matched with the given device type.
if (preferredAddress == Constants.ADDR_UNREGISTERED) {
for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
if (deviceType == HdmiUtils.getTypeFromAddress(i)) {
startAddress = i;
break;
}
}
}
int logicalAddress = Constants.ADDR_UNREGISTERED;
// Iterates all possible addresses which has the same device type.
for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS;
if (curAddress != Constants.ADDR_UNREGISTERED
&& deviceType == HdmiUtils.getTypeFromAddress(curAddress)) {
int failedPollingCount = 0;
for (int j = 0; j < HdmiConfig.ADDRESS_ALLOCATION_RETRY; ++j) {
if (!sendPollMessage(curAddress, curAddress, 1)) {
failedPollingCount++;
}
}
// Pick logical address if failed ratio is more than a half of all retries.
if (failedPollingCount * 2 > HdmiConfig.ADDRESS_ALLOCATION_RETRY) {
logicalAddress = curAddress;
break;
}
}
}
final int assignedAddress = logicalAddress;
HdmiLogger.debug("New logical address for device [%d]: [preferred:%d, assigned:%d]",
deviceType, preferredAddress, assignedAddress);
if (callback != null) {
runOnServiceThread(new Runnable() {
@Override
public void run() {
callback.onAllocated(deviceType, assignedAddress);
}
});
}
}
private static byte[] buildBody(int opcode, byte[] params) {
byte[] body = new byte[params.length + 1];
body[0] = (byte) opcode;
System.arraycopy(params, 0, body, 1, params.length);
return body;
}
HdmiPortInfo[] getPortInfos() {
return nativeGetPortInfos(mNativePtr);
}
/**
* Return the locally hosted logical device of a given type.
*
* @param deviceType logical device type
* @return {@link HdmiCecLocalDevice} instance if the instance of the type is available;
* otherwise null.
*/
HdmiCecLocalDevice getLocalDevice(int deviceType) {
return mLocalDevices.get(deviceType);
}
/**
* Add a new logical address to the device. Device's HW should be notified
* when a new logical address is assigned to a device, so that it can accept
* a command having available destinations.
*
* Declared as package-private. accessed by {@link HdmiControlService} only.
*
* @param newLogicalAddress a logical address to be added
* @return 0 on success. Otherwise, returns negative value
*/
@ServiceThreadOnly
int addLogicalAddress(int newLogicalAddress) {
assertRunOnServiceThread();
if (HdmiUtils.isValidAddress(newLogicalAddress)) {
return nativeAddLogicalAddress(mNativePtr, newLogicalAddress);
} else {
return -1;
}
}
/**
* Clear all logical addresses registered in the device.
*
* Declared as package-private. accessed by {@link HdmiControlService} only.
*/
@ServiceThreadOnly
void clearLogicalAddress() {
assertRunOnServiceThread();
for (int i = 0; i < mLocalDevices.size(); ++i) {
mLocalDevices.valueAt(i).clearAddress();
}
nativeClearLogicalAddress(mNativePtr);
}
@ServiceThreadOnly
void clearLocalDevices() {
assertRunOnServiceThread();
mLocalDevices.clear();
}
/**
* Return the physical address of the device.
*
* Declared as package-private. accessed by {@link HdmiControlService} only.
*
* @return CEC physical address of the device. The range of success address
* is between 0x0000 and 0xFFFF. If failed it returns -1
*/
@ServiceThreadOnly
int getPhysicalAddress() {
assertRunOnServiceThread();
return nativeGetPhysicalAddress(mNativePtr);
}
/**
* Return CEC version of the device.
*
* Declared as package-private. accessed by {@link HdmiControlService} only.
*/
@ServiceThreadOnly
int getVersion() {
assertRunOnServiceThread();
return nativeGetVersion(mNativePtr);
}
/**
* Return vendor id of the device.
*
* Declared as package-private. accessed by {@link HdmiControlService} only.
*/
@ServiceThreadOnly
int getVendorId() {
assertRunOnServiceThread();
return nativeGetVendorId(mNativePtr);
}
/**
* Set an option to CEC HAL.
*
* @param flag key of option
* @param value value of option
*/
@ServiceThreadOnly
void setOption(int flag, int value) {
assertRunOnServiceThread();
HdmiLogger.debug("setOption: [flag:%d, value:%d]", flag, value);
nativeSetOption(mNativePtr, flag, value);
}
/**
* Configure ARC circuit in the hardware logic to start or stop the feature.
*
* @param port ID of HDMI port to which AVR is connected
* @param enabled whether to enable/disable ARC
*/
@ServiceThreadOnly
void setAudioReturnChannel(int port, boolean enabled) {
assertRunOnServiceThread();
nativeSetAudioReturnChannel(mNativePtr, port, enabled);
}
/**
* Return the connection status of the specified port
*
* @param port port number to check connection status
* @return true if connected; otherwise, return false
*/
@ServiceThreadOnly
boolean isConnected(int port) {
assertRunOnServiceThread();
return nativeIsConnected(mNativePtr, port);
}
/**
* Poll all remote devices. It sends <Polling Message> to all remote
* devices.
*
* Declared as package-private. accessed by {@link HdmiControlService} only.
*
* @param callback an interface used to get a list of all remote devices' address
* @param sourceAddress a logical address of source device where sends polling message
* @param pickStrategy strategy how to pick polling candidates
* @param retryCount the number of retry used to send polling message to remote devices
*/
@ServiceThreadOnly
void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
int retryCount) {
assertRunOnServiceThread();
// Extract polling candidates. No need to poll against local devices.
List Declared as package-private. accessed by {@link HdmiControlService} only.
*/
@ServiceThreadOnly
List