diff options
author | Jungshik Jang <jayjang@google.com> | 2014-05-27 13:27:36 +0900 |
---|---|---|
committer | Jungshik Jang <jayjang@google.com> | 2014-06-02 10:59:03 +0900 |
commit | d42a7a322b7adf532ae0b70cb9eb1df7e62a8f2d (patch) | |
tree | 6c1d65bb855a78f15e7a6069b1c2964d2d12c70d /services | |
parent | a8fd44b74b8822426cdc0e45351899c95bf30e16 (diff) | |
download | frameworks_base-d42a7a322b7adf532ae0b70cb9eb1df7e62a8f2d.zip frameworks_base-d42a7a322b7adf532ae0b70cb9eb1df7e62a8f2d.tar.gz frameworks_base-d42a7a322b7adf532ae0b70cb9eb1df7e62a8f2d.tar.bz2 |
DO NOT MERGE: Implement device discovery sequence.
When device discover is launched it goes through the following step
1. clear all existing devices
2. send <Polling Message> of all logical addresses
excecpt one of local device
3. Once got all allocated logical addresses, gather physical address of
them
4. Once got physical address of them, gather display name of them
5. Once got display names, gather vendor id of them
5. Once got vendor id of them, register all gathered info to
internal device info list.
Change-Id: Ic9aca3b15d88ac7650f10b6d0bfa9c97923975e8
Diffstat (limited to 'services')
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 <Polling Message> + * <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 <Give Physical Address> 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 <Give Osd Name> 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); } |