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 private HdmiDeviceInfo mOldDeviceInfo; 57 58 /** 59 * Constructor. 60 * 61 * @param source {@link HdmiCecLocalDevice} instance 62 * @param deviceLogicalAddress logical address of the device in interest 63 * @param devicePhysicalAddress physical address of the device in interest 64 * @param deviceType type of the device 65 */ NewDeviceAction(HdmiCecLocalDevice source, int deviceLogicalAddress, int devicePhysicalAddress, int deviceType)66 NewDeviceAction(HdmiCecLocalDevice source, int deviceLogicalAddress, 67 int devicePhysicalAddress, int deviceType) { 68 super(source); 69 mDeviceLogicalAddress = deviceLogicalAddress; 70 mDevicePhysicalAddress = devicePhysicalAddress; 71 mDeviceType = deviceType; 72 mVendorId = Constants.VENDOR_ID_UNKNOWN; 73 } 74 75 @Override start()76 public boolean start() { 77 mOldDeviceInfo = 78 localDevice().mService.getHdmiCecNetwork().getCecDeviceInfo(mDeviceLogicalAddress); 79 // If there's deviceInfo with same (logical address, physical address) set 80 // Then addCecDevice should be delayed until system information process is finished 81 if (mOldDeviceInfo != null 82 && mOldDeviceInfo.getPhysicalAddress() == mDevicePhysicalAddress) { 83 Slog.d(TAG, "Start NewDeviceAction with old deviceInfo:[" 84 + mOldDeviceInfo.toString() + "]"); 85 } else { 86 // Add the device ahead with default information to handle <Active Source> 87 // promptly, rather than waiting till the new device action is finished. 88 Slog.d(TAG, "Start NewDeviceAction with default deviceInfo"); 89 HdmiDeviceInfo deviceInfo = HdmiDeviceInfo.cecDeviceBuilder() 90 .setLogicalAddress(mDeviceLogicalAddress) 91 .setPhysicalAddress(mDevicePhysicalAddress) 92 .setPortId(tv().getPortId(mDevicePhysicalAddress)) 93 .setDeviceType(mDeviceType) 94 .setVendorId(Constants.VENDOR_ID_UNKNOWN) 95 .build(); 96 // If a deviceInfo with same logical address but different physical address exists 97 // We should remove the old deviceInfo first 98 // This will happen if the interval between unplugging and plugging device is too short 99 // and HotplugDetection Action fails to remove the old deviceInfo, or when the newly 100 // plugged device violates HDMI Spec and uses an occupied logical address 101 if (mOldDeviceInfo != null) { 102 Slog.d(TAG, "Remove device by NewDeviceAction, logical address conflicts: " 103 + mDevicePhysicalAddress); 104 localDevice().mService.getHdmiCecNetwork().removeCecDevice( 105 localDevice(), mDeviceLogicalAddress); 106 } 107 localDevice().mService.getHdmiCecNetwork().addCecDevice(deviceInfo); 108 } 109 requestOsdName(true); 110 return true; 111 } 112 requestOsdName(boolean firstTry)113 private void requestOsdName(boolean firstTry) { 114 if (firstTry) { 115 mTimeoutRetry = 0; 116 } 117 mState = STATE_WAITING_FOR_SET_OSD_NAME; 118 if (mayProcessCommandIfCached(mDeviceLogicalAddress, Constants.MESSAGE_SET_OSD_NAME)) { 119 return; 120 } 121 122 sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(), 123 mDeviceLogicalAddress)); 124 addTimer(mState, HdmiConfig.TIMEOUT_MS); 125 } 126 127 @Override processCommand(HdmiCecMessage cmd)128 public boolean processCommand(HdmiCecMessage cmd) { 129 // For the logical device in interest, we want two more pieces of information - 130 // osd name and vendor id. They are requested in sequence. In case we don't 131 // get the expected responses (either by timeout or by receiving <feature abort> command), 132 // set them to a default osd name and unknown vendor id respectively. 133 int opcode = cmd.getOpcode(); 134 int src = cmd.getSource(); 135 byte[] params = cmd.getParams(); 136 137 if (mDeviceLogicalAddress != src) { 138 return false; 139 } 140 141 if (mState == STATE_WAITING_FOR_SET_OSD_NAME) { 142 if (opcode == Constants.MESSAGE_SET_OSD_NAME) { 143 try { 144 mDisplayName = new String(params, "US-ASCII"); 145 } catch (UnsupportedEncodingException e) { 146 Slog.e(TAG, "Failed to get OSD name: " + e.getMessage()); 147 } 148 requestVendorId(true); 149 return true; 150 } else if (opcode == Constants.MESSAGE_FEATURE_ABORT) { 151 int requestOpcode = params[0] & 0xFF; 152 if (requestOpcode == Constants.MESSAGE_GIVE_OSD_NAME) { 153 requestVendorId(true); 154 return true; 155 } 156 } 157 } else if (mState == STATE_WAITING_FOR_DEVICE_VENDOR_ID) { 158 if (opcode == Constants.MESSAGE_DEVICE_VENDOR_ID) { 159 mVendorId = HdmiUtils.threeBytesToInt(params); 160 addDeviceInfo(); 161 finish(); 162 return true; 163 } else if (opcode == Constants.MESSAGE_FEATURE_ABORT) { 164 int requestOpcode = params[0] & 0xFF; 165 if (requestOpcode == Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID) { 166 addDeviceInfo(); 167 finish(); 168 return true; 169 } 170 } 171 } 172 return false; 173 } 174 mayProcessCommandIfCached(int destAddress, int opcode)175 private boolean mayProcessCommandIfCached(int destAddress, int opcode) { 176 HdmiCecMessage message = getCecMessageCache().getMessage(destAddress, opcode); 177 if (message != null) { 178 return processCommand(message); 179 } 180 return false; 181 } 182 requestVendorId(boolean firstTry)183 private void requestVendorId(boolean firstTry) { 184 if (firstTry) { 185 mTimeoutRetry = 0; 186 } 187 // At first, transit to waiting status for <Device Vendor Id>. 188 mState = STATE_WAITING_FOR_DEVICE_VENDOR_ID; 189 // If the message is already in cache, process it. 190 if (mayProcessCommandIfCached(mDeviceLogicalAddress, 191 Constants.MESSAGE_DEVICE_VENDOR_ID)) { 192 return; 193 } 194 sendCommand(HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(getSourceAddress(), 195 mDeviceLogicalAddress)); 196 addTimer(mState, HdmiConfig.TIMEOUT_MS); 197 } 198 addDeviceInfo()199 private void addDeviceInfo() { 200 // The device should be in the device list with default information. 201 if (!localDevice().mService.getHdmiCecNetwork().isInDeviceList(mDeviceLogicalAddress, 202 mDevicePhysicalAddress)) { 203 Slog.w(TAG, String.format("Device not found (%02x, %04x)", 204 mDeviceLogicalAddress, mDevicePhysicalAddress)); 205 return; 206 } 207 if (mDisplayName == null) { 208 mDisplayName = ""; 209 } 210 HdmiDeviceInfo deviceInfo = HdmiDeviceInfo.cecDeviceBuilder() 211 .setLogicalAddress(mDeviceLogicalAddress) 212 .setPhysicalAddress(mDevicePhysicalAddress) 213 .setPortId(tv().getPortId(mDevicePhysicalAddress)) 214 .setDeviceType(mDeviceType) 215 .setVendorId(mVendorId) 216 .setDisplayName(mDisplayName) 217 .build(); 218 219 // Check if oldDevice is same as newDevice 220 // If so, don't add newDevice info, preventing ARC or HDMI source re-connection 221 if (mOldDeviceInfo != null 222 && mOldDeviceInfo.getLogicalAddress() == mDeviceLogicalAddress 223 && mOldDeviceInfo.getPhysicalAddress() == mDevicePhysicalAddress 224 && mOldDeviceInfo.getDeviceType() == mDeviceType 225 && mOldDeviceInfo.getVendorId() == mVendorId 226 && mOldDeviceInfo.getDisplayName().equals(mDisplayName)) { 227 // Consume CEC messages we already got for this newly found device. 228 tv().processDelayedMessages(mDeviceLogicalAddress); 229 Slog.d(TAG, "Ignore NewDevice, deviceInfo is same as current device"); 230 Slog.d(TAG, "Old:[" + mOldDeviceInfo.toString() 231 + "]; New:[" + deviceInfo.toString() + "]"); 232 } else { 233 Slog.d(TAG, "Add NewDevice:[" + deviceInfo.toString() + "]"); 234 localDevice().mService.getHdmiCecNetwork().addCecDevice(deviceInfo); 235 236 // Consume CEC messages we already got for this newly found device. 237 tv().processDelayedMessages(mDeviceLogicalAddress); 238 if (HdmiUtils.isEligibleAddressForDevice(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM, 239 mDeviceLogicalAddress)) { 240 tv().onNewAvrAdded(deviceInfo); 241 } 242 } 243 } 244 245 @Override handleTimerEvent(int state)246 public void handleTimerEvent(int state) { 247 if (mState == STATE_NONE || mState != state) { 248 return; 249 } 250 if (state == STATE_WAITING_FOR_SET_OSD_NAME) { 251 if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) { 252 requestOsdName(false); 253 return; 254 } 255 // Osd name request timed out. Try vendor id 256 requestVendorId(true); 257 } else if (state == STATE_WAITING_FOR_DEVICE_VENDOR_ID) { 258 if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) { 259 requestVendorId(false); 260 return; 261 } 262 // vendor id timed out. Go ahead creating the device info what we've got so far. 263 addDeviceInfo(); 264 finish(); 265 } 266 } 267 isActionOf(ActiveSource activeSource)268 boolean isActionOf(ActiveSource activeSource) { 269 return (mDeviceLogicalAddress == activeSource.logicalAddress) 270 && (mDevicePhysicalAddress == activeSource.physicalAddress); 271 } 272 } 273