/* * 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.HdmiDeviceInfo; import android.util.Slog; import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource; import java.io.UnsupportedEncodingException; /** * Feature action that discovers the information of a newly found logical device. * * This action is created when receiving <Report Physical Address>, a CEC command a newly * connected HDMI-CEC device broadcasts to announce its advent. Additional commands are issued in * this action to gather more information on the device such as OSD name and device vendor ID. * * <p>The result is made in the form of {@link HdmiDeviceInfo} object, and passed to service * for the management through its life cycle. * * <p>Package-private, accessed by {@link HdmiControlService} only. */ final class NewDeviceAction extends HdmiCecFeatureAction { private static final String TAG = "NewDeviceAction"; // State in which the action sent <Give OSD Name> and is waiting for <Set OSD Name> // that contains the name of the device for display on screen. static final int STATE_WAITING_FOR_SET_OSD_NAME = 1; // State in which the action sent <Give Device Vendor ID> and is waiting for // <Device Vendor ID> that contains the vendor ID of the device. static final int STATE_WAITING_FOR_DEVICE_VENDOR_ID = 2; private final int mDeviceLogicalAddress; private final int mDevicePhysicalAddress; private final int mDeviceType; private int mVendorId; private String mDisplayName; private int mTimeoutRetry; /** * Constructor. * * @param source {@link HdmiCecLocalDevice} instance * @param deviceLogicalAddress logical address of the device in interest * @param devicePhysicalAddress physical address of the device in interest * @param deviceType type of the device */ NewDeviceAction(HdmiCecLocalDevice source, int deviceLogicalAddress, int devicePhysicalAddress, int deviceType) { super(source); mDeviceLogicalAddress = deviceLogicalAddress; mDevicePhysicalAddress = devicePhysicalAddress; mDeviceType = deviceType; mVendorId = Constants.UNKNOWN_VENDOR_ID; } @Override public boolean start() { requestOsdName(true); return true; } private void requestOsdName(boolean firstTry) { if (firstTry) { mTimeoutRetry = 0; } mState = STATE_WAITING_FOR_SET_OSD_NAME; if (mayProcessCommandIfCached(mDeviceLogicalAddress, Constants.MESSAGE_SET_OSD_NAME)) { return; } sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(), mDeviceLogicalAddress)); addTimer(mState, HdmiConfig.TIMEOUT_MS); } @Override public boolean processCommand(HdmiCecMessage cmd) { // For the logical device in interest, we want two more pieces of information - // osd name and vendor id. They are requested in sequence. In case we don't // get the expected responses (either by timeout or by receiving <feature abort> command), // set them to a default osd name and unknown vendor id respectively. int opcode = cmd.getOpcode(); int src = cmd.getSource(); byte[] params = cmd.getParams(); if (mDeviceLogicalAddress != src) { return false; } if (mState == STATE_WAITING_FOR_SET_OSD_NAME) { if (opcode == Constants.MESSAGE_SET_OSD_NAME) { try { mDisplayName = new String(params, "US-ASCII"); } catch (UnsupportedEncodingException e) { Slog.e(TAG, "Failed to get OSD name: " + e.getMessage()); } requestVendorId(true); return true; } else if (opcode == Constants.MESSAGE_FEATURE_ABORT) { int requestOpcode = params[0] & 0xFF; if (requestOpcode == Constants.MESSAGE_GIVE_OSD_NAME) { requestVendorId(true); return true; } } } else if (mState == STATE_WAITING_FOR_DEVICE_VENDOR_ID) { if (opcode == Constants.MESSAGE_DEVICE_VENDOR_ID) { mVendorId = HdmiUtils.threeBytesToInt(params); addDeviceInfo(); finish(); return true; } else if (opcode == Constants.MESSAGE_FEATURE_ABORT) { int requestOpcode = params[0] & 0xFF; if (requestOpcode == Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID) { addDeviceInfo(); finish(); return true; } } } return false; } private boolean mayProcessCommandIfCached(int destAddress, int opcode) { HdmiCecMessage message = getCecMessageCache().getMessage(destAddress, opcode); if (message != null) { return processCommand(message); } return false; } private void requestVendorId(boolean firstTry) { if (firstTry) { mTimeoutRetry = 0; } // At first, transit to waiting status for <Device Vendor Id>. mState = STATE_WAITING_FOR_DEVICE_VENDOR_ID; // If the message is already in cache, process it. if (mayProcessCommandIfCached(mDeviceLogicalAddress, Constants.MESSAGE_DEVICE_VENDOR_ID)) { return; } sendCommand(HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(getSourceAddress(), mDeviceLogicalAddress)); addTimer(mState, HdmiConfig.TIMEOUT_MS); } private void addDeviceInfo() { // The device should be in the device list with default information. if (!tv().isInDeviceList(mDeviceLogicalAddress, mDevicePhysicalAddress)) { Slog.w(TAG, String.format("Device not found (%02x, %04x)", mDeviceLogicalAddress, mDevicePhysicalAddress)); return; } if (mDisplayName == null) { mDisplayName = HdmiUtils.getDefaultDeviceName(mDeviceLogicalAddress); } HdmiDeviceInfo deviceInfo = new HdmiDeviceInfo( mDeviceLogicalAddress, mDevicePhysicalAddress, tv().getPortId(mDevicePhysicalAddress), mDeviceType, mVendorId, mDisplayName); tv().addCecDevice(deviceInfo); // Consume CEC messages we already got for this newly found device. tv().processDelayedMessages(mDeviceLogicalAddress); if (HdmiUtils.getTypeFromAddress(mDeviceLogicalAddress) == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) { tv().onNewAvrAdded(deviceInfo); } } @Override public void handleTimerEvent(int state) { if (mState == STATE_NONE || mState != state) { return; } if (state == STATE_WAITING_FOR_SET_OSD_NAME) { if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) { requestOsdName(false); return; } // Osd name request timed out. Try vendor id requestVendorId(true); } else if (state == STATE_WAITING_FOR_DEVICE_VENDOR_ID) { if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) { requestVendorId(false); return; } // vendor id timed out. Go ahead creating the device info what we've got so far. addDeviceInfo(); finish(); } } boolean isActionOf(ActiveSource activeSource) { return (mDeviceLogicalAddress == activeSource.logicalAddress) && (mDevicePhysicalAddress == activeSource.physicalAddress); } }