| /* |
| * 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); |
| } |
| } |