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