summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
Diffstat (limited to 'services')
-rw-r--r--services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java361
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecController.java35
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java11
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java33
4 files changed, 434 insertions, 6 deletions
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
new file mode 100644
index 0000000..d46cc7b
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -0,0 +1,361 @@
+/*
+ * 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;
+import android.hardware.hdmi.HdmiCecDeviceInfo;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.util.Slog;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Feature action that handles device discovery sequences.
+ * Device discovery is launched when TV device is woken from "Standby" state
+ * or enabled "Control for Hdmi" from disabled state.
+ *
+ * <p>Device discovery goes through the following steps.
+ * <ol>
+ * <li>Poll all non-local devices by sending &lt;Polling Message&gt;
+ * <li>Gather "Physical address" and "device type" of all acknowledged devices
+ * <li>Gather "OSD (display) name" of all acknowledge devices
+ * <li>Gather "Vendor id" of all acknowledge devices
+ * </ol>
+ */
+final class DeviceDiscoveryAction extends FeatureAction {
+ private static final String TAG = "DeviceDiscoveryAction";
+
+ // State in which the action is waiting for device polling.
+ private static final int STATE_WAITING_FOR_DEVICE_POLLING = 1;
+ // State in which the action is waiting for gathering physical address of non-local devices.
+ private static final int STATE_WAITING_FOR_PHYSICAL_ADDRESS = 2;
+ // State in which the action is waiting for gathering osd name of non-local devices.
+ private static final int STATE_WAITING_FOR_OSD_NAME = 3;
+ // State in which the action is waiting for gathering vendor id of non-local devices.
+ private static final int STATE_WAITING_FOR_VENDOR_ID = 4;
+
+ 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.
+ */
+ interface DeviceDiscoveryCallback {
+ /**
+ * Called when device discovery is done.
+ *
+ * @param deviceInfos a list of all non-local devices. It can be empty list.
+ */
+ void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos);
+ }
+
+ // An internal container used to keep track of device information during
+ // this action.
+ private static final class DeviceInfo {
+ private final int mLogicalAddress;
+
+ private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
+ private int mVendorId = HdmiCec.UNKNOWN_VENDOR_ID;
+ private String mDisplayName = "";
+ private int mDeviceType = HdmiCec.DEVICE_INACTIVE;
+
+ private DeviceInfo(int logicalAddress) {
+ mLogicalAddress = logicalAddress;
+ }
+
+ private HdmiCecDeviceInfo toHdmiCecDeviceInfo() {
+ return new HdmiCecDeviceInfo(mLogicalAddress, mPhysicalAddress, mDeviceType, mVendorId,
+ mDisplayName);
+ }
+ }
+
+ private final ArrayList<DeviceInfo> mDevices = new ArrayList<>();
+ private final DeviceDiscoveryCallback mCallback;
+ private int mProcessedDeviceCount = 0;
+
+ /**
+ * @Constructor
+ *
+ * @param service
+ * @param sourceAddress
+ */
+ DeviceDiscoveryAction(HdmiControlService service, int sourceAddress,
+ DeviceDiscoveryCallback callback) {
+ super(service, sourceAddress);
+ mCallback = Preconditions.checkNotNull(callback);
+ }
+
+ @Override
+ boolean start() {
+ mDevices.clear();
+ mState = STATE_WAITING_FOR_DEVICE_POLLING;
+
+ mService.pollDevices(new DevicePollingCallback() {
+ @Override
+ public void onPollingFinished(List<Integer> ackedAddress) {
+ if (ackedAddress.isEmpty()) {
+ Slog.i(TAG, "No device is detected.");
+ finish();
+ return;
+ }
+
+ Slog.i(TAG, "Device detected: " + ackedAddress);
+ allocateDevices(ackedAddress);
+ startPhysicalAddressStage();
+ }
+ }, DEVICE_POLLING_RETRY);
+ return true;
+ }
+
+ private void allocateDevices(List<Integer> addresses) {
+ for (Integer i : addresses) {
+ DeviceInfo info = new DeviceInfo(i);
+ mDevices.add(info);
+ }
+ }
+
+ private void startPhysicalAddressStage() {
+ mProcessedDeviceCount = 0;
+ mState = STATE_WAITING_FOR_PHYSICAL_ADDRESS;
+
+ checkAndProceedStage();
+ }
+
+ private boolean verifyValidLogicalAddress(int address) {
+ return address >= HdmiCec.ADDR_TV && address < HdmiCec.ADDR_UNREGISTERED;
+ }
+
+ private void queryPhysicalAddress(int address) {
+ if (!verifyValidLogicalAddress(address)) {
+ checkAndProceedStage();
+ return;
+ }
+
+ mActionTimer.clearTimerMessage();
+ sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(mSourceAddress, address));
+ addTimer(mState, TIMEOUT_MS);
+ }
+
+ private void startOsdNameStage() {
+ mProcessedDeviceCount = 0;
+ mState = STATE_WAITING_FOR_OSD_NAME;
+
+ checkAndProceedStage();
+ }
+
+ private void queryOsdName(int address) {
+ if (!verifyValidLogicalAddress(address)) {
+ checkAndProceedStage();
+ return;
+ }
+
+ mActionTimer.clearTimerMessage();
+ sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(mSourceAddress, address));
+ addTimer(mState, TIMEOUT_MS);
+ }
+
+ private void startVendorIdStage() {
+ mProcessedDeviceCount = 0;
+ mState = STATE_WAITING_FOR_VENDOR_ID;
+
+ checkAndProceedStage();
+ }
+
+ private void queryVendorId(int address) {
+ if (!verifyValidLogicalAddress(address)) {
+ checkAndProceedStage();
+ return;
+ }
+
+ mActionTimer.clearTimerMessage();
+ sendCommand(HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(mSourceAddress, address));
+ addTimer(mState, TIMEOUT_MS);
+ }
+
+ @Override
+ boolean processCommand(HdmiCecMessage cmd) {
+ switch (mState) {
+ case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
+ if (cmd.getOpcode() == HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS) {
+ handleReportPhysicalAddress(cmd);
+ return true;
+ }
+ return false;
+ case STATE_WAITING_FOR_OSD_NAME:
+ if (cmd.getOpcode() == HdmiCec.MESSAGE_SET_OSD_NAME) {
+ handleSetOsdName(cmd);
+ return true;
+ }
+ return false;
+ case STATE_WAITING_FOR_VENDOR_ID:
+ if (cmd.getOpcode() == HdmiCec.MESSAGE_DEVICE_VENDOR_ID) {
+ handleVendorId(cmd);
+ return true;
+ }
+ return false;
+ case STATE_WAITING_FOR_DEVICE_POLLING:
+ // Fall through.
+ default:
+ return false;
+ }
+ }
+
+ private void handleReportPhysicalAddress(HdmiCecMessage cmd) {
+ Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
+
+ DeviceInfo current = mDevices.get(mProcessedDeviceCount);
+ if (current.mLogicalAddress != cmd.getSource()) {
+ Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
+ cmd.getSource());
+ return;
+ }
+
+ byte params[] = cmd.getParams();
+ if (params.length == 3) {
+ current.mPhysicalAddress = ((params[0] & 0xFF) << 8) | (params[1] & 0xFF);
+ current.mDeviceType = params[2] & 0xFF;
+
+ increaseProcessedDeviceCount();
+ checkAndProceedStage();
+ } else {
+ // Physical address is a critical element in device info.
+ // If failed, remove device from device list and proceed to the next device.
+ removeDevice(mProcessedDeviceCount);
+ checkAndProceedStage();
+ }
+ }
+
+ private void handleSetOsdName(HdmiCecMessage cmd) {
+ Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
+
+ DeviceInfo current = mDevices.get(mProcessedDeviceCount);
+ if (current.mLogicalAddress != cmd.getSource()) {
+ Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
+ cmd.getSource());
+ return;
+ }
+
+ String displayName = null;
+ try {
+ displayName = new String(cmd.getParams(), "US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ Slog.w(TAG, "Failed to decode display name: " + cmd.toString());
+ // If failed to get display name, use the default name of device.
+ displayName = HdmiCec.getDefaultDeviceName(current.mLogicalAddress);
+ }
+ current.mDisplayName = displayName;
+ increaseProcessedDeviceCount();
+ checkAndProceedStage();
+ }
+
+ private void handleVendorId(HdmiCecMessage cmd) {
+ Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
+
+ DeviceInfo current = mDevices.get(mProcessedDeviceCount);
+ if (current.mLogicalAddress != cmd.getSource()) {
+ Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
+ cmd.getSource());
+ return;
+ }
+
+ byte[] params = cmd.getParams();
+ if (params.length == 3) {
+ int vendorId = ((params[0] & 0xFF) << 16)
+ | ((params[1] & 0xFF) << 8)
+ | (params[2] & 0xFF);
+ current.mVendorId = vendorId;
+ } else {
+ Slog.w(TAG, "Invalid vendor id: " + cmd.toString());
+ }
+ increaseProcessedDeviceCount();
+ checkAndProceedStage();
+ }
+
+ private void increaseProcessedDeviceCount() {
+ mProcessedDeviceCount++;
+ }
+
+ private void removeDevice(int index) {
+ mDevices.remove(index);
+ }
+
+ private void wrapUpAndFinish() {
+ ArrayList<HdmiCecDeviceInfo> result = new ArrayList<>();
+ for (DeviceInfo info : mDevices) {
+ HdmiCecDeviceInfo cecDeviceInfo = info.toHdmiCecDeviceInfo();
+ result.add(cecDeviceInfo);
+ }
+ mCallback.onDeviceDiscoveryDone(result);
+ finish();
+ }
+
+ private void checkAndProceedStage() {
+ if (mDevices.isEmpty()) {
+ wrapUpAndFinish();
+ return;
+ }
+
+ // If finished current stage, move on to next stage.
+ if (mProcessedDeviceCount == mDevices.size()) {
+ mProcessedDeviceCount = 0;
+ switch (mState) {
+ case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
+ startOsdNameStage();
+ return;
+ case STATE_WAITING_FOR_OSD_NAME:
+ startVendorIdStage();
+ return;
+ case STATE_WAITING_FOR_VENDOR_ID:
+ wrapUpAndFinish();
+ return;
+ default:
+ return;
+ }
+ } else {
+ int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress;
+ switch (mState) {
+ case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
+ queryPhysicalAddress(address);
+ return;
+ case STATE_WAITING_FOR_OSD_NAME:
+ queryOsdName(address);
+ return;
+ case STATE_WAITING_FOR_VENDOR_ID:
+ queryVendorId(address);
+ default:
+ return;
+ }
+ }
+ }
+
+ @Override
+ void handleTimerEvent(int state) {
+ if (mState == STATE_NONE || mState != state) {
+ return;
+ }
+
+ removeDevice(mProcessedDeviceCount);
+ checkAndProceedStage();
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 19fdaf4..292f1cd 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -261,18 +261,23 @@ final class HdmiCecController {
}
/**
+ * Clear all device info.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ */
+ void clearDeviceInfoList() {
+ assertRunOnServiceThread();
+ mDeviceInfos.clear();
+ }
+
+ /**
* Return a list of all {@link HdmiCecDeviceInfo}.
*
* <p>Declared as package-private. accessed by {@link HdmiControlService} only.
*/
List<HdmiCecDeviceInfo> getDeviceInfoList() {
assertRunOnServiceThread();
-
- List<HdmiCecDeviceInfo> deviceInfoList = new ArrayList<>(mDeviceInfos.size());
- for (int i = 0; i < mDeviceInfos.size(); ++i) {
- deviceInfoList.add(mDeviceInfos.valueAt(i));
- }
- return deviceInfoList;
+ return sparseArrayToList(mDeviceInfos);
}
/**
@@ -389,6 +394,24 @@ final class HdmiCecController {
runDevicePolling(pollingCandidates, retryCount, callback);
}
+ /**
+ * Return a list of all {@link HdmiCecLocalDevice}s.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ */
+ List<HdmiCecLocalDevice> getLocalDeviceList() {
+ assertRunOnServiceThread();
+ return sparseArrayToList(mLocalDevices);
+ }
+
+ private static <T> List<T> sparseArrayToList(SparseArray<T> array) {
+ ArrayList<T> list = new ArrayList<>();
+ for (int i = 0; i < array.size(); ++i) {
+ list.add(array.valueAt(i));
+ }
+ return list;
+ }
+
private boolean isAllocatedLocalDeviceAddress(int address) {
for (int i = 0; i < mLocalDevices.size(); ++i) {
if (mLocalDevices.valueAt(i).isAddressOf(address)) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index 1da363b..9a76734 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -73,6 +73,17 @@ public class HdmiCecMessageBuilder {
}
/**
+ * Build &lt;Give Physical Address&gt; command.
+ *
+ * @param src source address of command
+ * @param dest destination address of command
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildGivePhysicalAddress(int src, int dest) {
+ return buildCommand(src, dest, HdmiCec.MESSAGE_GIVE_PHYSICAL_ADDRESS);
+ }
+
+ /**
* Build &lt;Give Osd Name&gt; command.
*
* @param src source address of command
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 475852f..2fc5e88 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -33,6 +33,7 @@ import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.server.SystemService;
+import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
import java.util.ArrayList;
import java.util.Iterator;
@@ -411,6 +412,38 @@ public final class HdmiControlService extends SystemService {
}
}
+ void addCecDevice(HdmiCecDeviceInfo info) {
+ mCecController.addDeviceInfo(info);
+ }
+
+ // 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.
+ void launchDeviceDiscovery() {
+ // At first, clear all existing device infos.
+ mCecController.clearDeviceInfoList();
+
+ // TODO: check whether TV is one of local devices.
+ DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, HdmiCec.ADDR_TV,
+ new DeviceDiscoveryCallback() {
+ @Override
+ public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) {
+ for (HdmiCecDeviceInfo info : deviceInfos) {
+ mCecController.addDeviceInfo(info);
+ }
+
+ // Add device info of all local devices.
+ for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
+ mCecController.addDeviceInfo(device.getDeviceInfo());
+ }
+
+ // TODO: start hot-plug detection sequence here.
+ // addAndStartAction(new HotplugDetectionAction());
+ }
+ });
+ addAndStartAction(action);
+ }
+
private void enforceAccessPermission() {
getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
}