1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.server.hdmi; 17 18 import android.hardware.hdmi.HdmiDeviceInfo; 19 import android.util.Slog; 20 21 import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource; 22 23 import java.io.UnsupportedEncodingException; 24 25 /** 26 * Feature action that discovers the information of a newly found logical device. 27 * 28 * This action is created when receiving <Report Physical Address>, a CEC command a newly 29 * connected HDMI-CEC device broadcasts to announce its advent. Additional commands are issued in 30 * this action to gather more information on the device such as OSD name and device vendor ID. 31 * 32 * <p>The result is made in the form of {@link HdmiDeviceInfo} object, and passed to service 33 * for the management through its life cycle. 34 * 35 * <p>Package-private, accessed by {@link HdmiControlService} only. 36 */ 37 final class NewDeviceAction extends HdmiCecFeatureAction { 38 39 private static final String TAG = "NewDeviceAction"; 40 41 // State in which the action sent <Give OSD Name> and is waiting for <Set OSD Name> 42 // that contains the name of the device for display on screen. 43 static final int STATE_WAITING_FOR_SET_OSD_NAME = 1; 44 45 // State in which the action sent <Give Device Vendor ID> and is waiting for 46 // <Device Vendor ID> that contains the vendor ID of the device. 47 static final int STATE_WAITING_FOR_DEVICE_VENDOR_ID = 2; 48 49 private final int mDeviceLogicalAddress; 50 private final int mDevicePhysicalAddress; 51 private final int mDeviceType; 52 53 private int mVendorId; 54 private String mDisplayName; 55 private int mTimeoutRetry; 56 57 /** 58 * Constructor. 59 * 60 * @param source {@link HdmiCecLocalDevice} instance 61 * @param deviceLogicalAddress logical address of the device in interest 62 * @param devicePhysicalAddress physical address of the device in interest 63 * @param deviceType type of the device 64 */ NewDeviceAction(HdmiCecLocalDevice source, int deviceLogicalAddress, int devicePhysicalAddress, int deviceType)65 NewDeviceAction(HdmiCecLocalDevice source, int deviceLogicalAddress, 66 int devicePhysicalAddress, int deviceType) { 67 super(source); 68 mDeviceLogicalAddress = deviceLogicalAddress; 69 mDevicePhysicalAddress = devicePhysicalAddress; 70 mDeviceType = deviceType; 71 mVendorId = Constants.VENDOR_ID_UNKNOWN; 72 } 73 74 @Override start()75 public boolean start() { 76 requestOsdName(true); 77 return true; 78 } 79 requestOsdName(boolean firstTry)80 private void requestOsdName(boolean firstTry) { 81 if (firstTry) { 82 mTimeoutRetry = 0; 83 } 84 mState = STATE_WAITING_FOR_SET_OSD_NAME; 85 if (mayProcessCommandIfCached(mDeviceLogicalAddress, Constants.MESSAGE_SET_OSD_NAME)) { 86 return; 87 } 88 89 sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(), 90 mDeviceLogicalAddress)); 91 addTimer(mState, HdmiConfig.TIMEOUT_MS); 92 } 93 94 @Override processCommand(HdmiCecMessage cmd)95 public boolean processCommand(HdmiCecMessage cmd) { 96 // For the logical device in interest, we want two more pieces of information - 97 // osd name and vendor id. They are requested in sequence. In case we don't 98 // get the expected responses (either by timeout or by receiving <feature abort> command), 99 // set them to a default osd name and unknown vendor id respectively. 100 int opcode = cmd.getOpcode(); 101 int src = cmd.getSource(); 102 byte[] params = cmd.getParams(); 103 104 if (mDeviceLogicalAddress != src) { 105 return false; 106 } 107 108 if (mState == STATE_WAITING_FOR_SET_OSD_NAME) { 109 if (opcode == Constants.MESSAGE_SET_OSD_NAME) { 110 try { 111 mDisplayName = new String(params, "US-ASCII"); 112 } catch (UnsupportedEncodingException e) { 113 Slog.e(TAG, "Failed to get OSD name: " + e.getMessage()); 114 } 115 requestVendorId(true); 116 return true; 117 } else if (opcode == Constants.MESSAGE_FEATURE_ABORT) { 118 int requestOpcode = params[0] & 0xFF; 119 if (requestOpcode == Constants.MESSAGE_GIVE_OSD_NAME) { 120 requestVendorId(true); 121 return true; 122 } 123 } 124 } else if (mState == STATE_WAITING_FOR_DEVICE_VENDOR_ID) { 125 if (opcode == Constants.MESSAGE_DEVICE_VENDOR_ID) { 126 mVendorId = HdmiUtils.threeBytesToInt(params); 127 addDeviceInfo(); 128 finish(); 129 return true; 130 } else if (opcode == Constants.MESSAGE_FEATURE_ABORT) { 131 int requestOpcode = params[0] & 0xFF; 132 if (requestOpcode == Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID) { 133 addDeviceInfo(); 134 finish(); 135 return true; 136 } 137 } 138 } 139 return false; 140 } 141 mayProcessCommandIfCached(int destAddress, int opcode)142 private boolean mayProcessCommandIfCached(int destAddress, int opcode) { 143 HdmiCecMessage message = getCecMessageCache().getMessage(destAddress, opcode); 144 if (message != null) { 145 return processCommand(message); 146 } 147 return false; 148 } 149 requestVendorId(boolean firstTry)150 private void requestVendorId(boolean firstTry) { 151 if (firstTry) { 152 mTimeoutRetry = 0; 153 } 154 // At first, transit to waiting status for <Device Vendor Id>. 155 mState = STATE_WAITING_FOR_DEVICE_VENDOR_ID; 156 // If the message is already in cache, process it. 157 if (mayProcessCommandIfCached(mDeviceLogicalAddress, 158 Constants.MESSAGE_DEVICE_VENDOR_ID)) { 159 return; 160 } 161 sendCommand(HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(getSourceAddress(), 162 mDeviceLogicalAddress)); 163 addTimer(mState, HdmiConfig.TIMEOUT_MS); 164 } 165 addDeviceInfo()166 private void addDeviceInfo() { 167 // The device should be in the device list with default information. 168 if (!localDevice().mService.getHdmiCecNetwork().isInDeviceList(mDeviceLogicalAddress, 169 mDevicePhysicalAddress)) { 170 Slog.w(TAG, String.format("Device not found (%02x, %04x)", 171 mDeviceLogicalAddress, mDevicePhysicalAddress)); 172 return; 173 } 174 if (mDisplayName == null) { 175 mDisplayName = ""; 176 } 177 HdmiDeviceInfo deviceInfo = HdmiDeviceInfo.cecDeviceBuilder() 178 .setLogicalAddress(mDeviceLogicalAddress) 179 .setPhysicalAddress(mDevicePhysicalAddress) 180 .setPortId(tv().getPortId(mDevicePhysicalAddress)) 181 .setDeviceType(mDeviceType) 182 .setVendorId(mVendorId) 183 .setDisplayName(mDisplayName) 184 .build(); 185 localDevice().mService.getHdmiCecNetwork().addCecDevice(deviceInfo); 186 187 // Consume CEC messages we already got for this newly found device. 188 tv().processDelayedMessages(mDeviceLogicalAddress); 189 190 if (HdmiUtils.isEligibleAddressForDevice(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM, 191 mDeviceLogicalAddress)) { 192 tv().onNewAvrAdded(deviceInfo); 193 } 194 } 195 196 @Override handleTimerEvent(int state)197 public void handleTimerEvent(int state) { 198 if (mState == STATE_NONE || mState != state) { 199 return; 200 } 201 if (state == STATE_WAITING_FOR_SET_OSD_NAME) { 202 if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) { 203 requestOsdName(false); 204 return; 205 } 206 // Osd name request timed out. Try vendor id 207 requestVendorId(true); 208 } else if (state == STATE_WAITING_FOR_DEVICE_VENDOR_ID) { 209 if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) { 210 requestVendorId(false); 211 return; 212 } 213 // vendor id timed out. Go ahead creating the device info what we've got so far. 214 addDeviceInfo(); 215 finish(); 216 } 217 } 218 isActionOf(ActiveSource activeSource)219 boolean isActionOf(ActiveSource activeSource) { 220 return (mDeviceLogicalAddress == activeSource.logicalAddress) 221 && (mDevicePhysicalAddress == activeSource.physicalAddress); 222 } 223 } 224