DeviceDiscoveryAction.java revision cc5ef8c918e96516a5c51cc40735a1b8a24d8497
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 17package com.android.server.hdmi; 18 19import android.hardware.hdmi.HdmiCec; 20import android.hardware.hdmi.HdmiCecDeviceInfo; 21import android.hardware.hdmi.HdmiCecMessage; 22import android.util.Slog; 23 24import com.android.internal.util.Preconditions; 25import com.android.server.hdmi.HdmiControlService.DevicePollingCallback; 26 27import java.io.UnsupportedEncodingException; 28import java.util.ArrayList; 29import java.util.List; 30 31/** 32 * Feature action that handles device discovery sequences. 33 * Device discovery is launched when TV device is woken from "Standby" state 34 * or enabled "Control for Hdmi" from disabled state. 35 * 36 * <p>Device discovery goes through the following steps. 37 * <ol> 38 * <li>Poll all non-local devices by sending <Polling Message> 39 * <li>Gather "Physical address" and "device type" of all acknowledged devices 40 * <li>Gather "OSD (display) name" of all acknowledge devices 41 * <li>Gather "Vendor id" of all acknowledge devices 42 * </ol> 43 */ 44final class DeviceDiscoveryAction extends FeatureAction { 45 private static final String TAG = "DeviceDiscoveryAction"; 46 47 // State in which the action is waiting for device polling. 48 private static final int STATE_WAITING_FOR_DEVICE_POLLING = 1; 49 // State in which the action is waiting for gathering physical address of non-local devices. 50 private static final int STATE_WAITING_FOR_PHYSICAL_ADDRESS = 2; 51 // State in which the action is waiting for gathering osd name of non-local devices. 52 private static final int STATE_WAITING_FOR_OSD_NAME = 3; 53 // State in which the action is waiting for gathering vendor id of non-local devices. 54 private static final int STATE_WAITING_FOR_VENDOR_ID = 4; 55 56 private static final int DEVICE_POLLING_RETRY = 1; 57 58 // TODO: Move this to common place 59 private static final int INVALID_PHYSICAL_ADDRESS = 0xFFFF; 60 61 /** 62 * Interface used to report result of device discovery. 63 */ 64 interface DeviceDiscoveryCallback { 65 /** 66 * Called when device discovery is done. 67 * 68 * @param deviceInfos a list of all non-local devices. It can be empty list. 69 */ 70 void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos); 71 } 72 73 // An internal container used to keep track of device information during 74 // this action. 75 private static final class DeviceInfo { 76 private final int mLogicalAddress; 77 78 private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS; 79 private int mVendorId = HdmiCec.UNKNOWN_VENDOR_ID; 80 private String mDisplayName = ""; 81 private int mDeviceType = HdmiCec.DEVICE_INACTIVE; 82 83 private DeviceInfo(int logicalAddress) { 84 mLogicalAddress = logicalAddress; 85 } 86 87 private HdmiCecDeviceInfo toHdmiCecDeviceInfo() { 88 return new HdmiCecDeviceInfo(mLogicalAddress, mPhysicalAddress, mDeviceType, mVendorId, 89 mDisplayName); 90 } 91 } 92 93 private final ArrayList<DeviceInfo> mDevices = new ArrayList<>(); 94 private final DeviceDiscoveryCallback mCallback; 95 private int mProcessedDeviceCount = 0; 96 97 /** 98 * @Constructor 99 * 100 * @param service 101 * @param sourceAddress 102 */ 103 DeviceDiscoveryAction(HdmiControlService service, int sourceAddress, 104 DeviceDiscoveryCallback callback) { 105 super(service, sourceAddress); 106 mCallback = Preconditions.checkNotNull(callback); 107 } 108 109 @Override 110 boolean start() { 111 mDevices.clear(); 112 mState = STATE_WAITING_FOR_DEVICE_POLLING; 113 114 mService.pollDevices(new DevicePollingCallback() { 115 @Override 116 public void onPollingFinished(List<Integer> ackedAddress) { 117 if (ackedAddress.isEmpty()) { 118 Slog.i(TAG, "No device is detected."); 119 finish(); 120 return; 121 } 122 123 Slog.i(TAG, "Device detected: " + ackedAddress); 124 allocateDevices(ackedAddress); 125 startPhysicalAddressStage(); 126 } 127 }, DEVICE_POLLING_RETRY); 128 return true; 129 } 130 131 private void allocateDevices(List<Integer> addresses) { 132 for (Integer i : addresses) { 133 DeviceInfo info = new DeviceInfo(i); 134 mDevices.add(info); 135 } 136 } 137 138 private void startPhysicalAddressStage() { 139 mProcessedDeviceCount = 0; 140 mState = STATE_WAITING_FOR_PHYSICAL_ADDRESS; 141 142 checkAndProceedStage(); 143 } 144 145 private boolean verifyValidLogicalAddress(int address) { 146 return address >= HdmiCec.ADDR_TV && address < HdmiCec.ADDR_UNREGISTERED; 147 } 148 149 private void queryPhysicalAddress(int address) { 150 if (!verifyValidLogicalAddress(address)) { 151 checkAndProceedStage(); 152 return; 153 } 154 155 mActionTimer.clearTimerMessage(); 156 sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(mSourceAddress, address)); 157 addTimer(mState, TIMEOUT_MS); 158 } 159 160 private void startOsdNameStage() { 161 mProcessedDeviceCount = 0; 162 mState = STATE_WAITING_FOR_OSD_NAME; 163 164 checkAndProceedStage(); 165 } 166 167 private void queryOsdName(int address) { 168 if (!verifyValidLogicalAddress(address)) { 169 checkAndProceedStage(); 170 return; 171 } 172 173 mActionTimer.clearTimerMessage(); 174 sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(mSourceAddress, address)); 175 addTimer(mState, TIMEOUT_MS); 176 } 177 178 private void startVendorIdStage() { 179 mProcessedDeviceCount = 0; 180 mState = STATE_WAITING_FOR_VENDOR_ID; 181 182 checkAndProceedStage(); 183 } 184 185 private void queryVendorId(int address) { 186 if (!verifyValidLogicalAddress(address)) { 187 checkAndProceedStage(); 188 return; 189 } 190 191 mActionTimer.clearTimerMessage(); 192 sendCommand(HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(mSourceAddress, address)); 193 addTimer(mState, TIMEOUT_MS); 194 } 195 196 @Override 197 boolean processCommand(HdmiCecMessage cmd) { 198 switch (mState) { 199 case STATE_WAITING_FOR_PHYSICAL_ADDRESS: 200 if (cmd.getOpcode() == HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS) { 201 handleReportPhysicalAddress(cmd); 202 return true; 203 } 204 return false; 205 case STATE_WAITING_FOR_OSD_NAME: 206 if (cmd.getOpcode() == HdmiCec.MESSAGE_SET_OSD_NAME) { 207 handleSetOsdName(cmd); 208 return true; 209 } 210 return false; 211 case STATE_WAITING_FOR_VENDOR_ID: 212 if (cmd.getOpcode() == HdmiCec.MESSAGE_DEVICE_VENDOR_ID) { 213 handleVendorId(cmd); 214 return true; 215 } 216 return false; 217 case STATE_WAITING_FOR_DEVICE_POLLING: 218 // Fall through. 219 default: 220 return false; 221 } 222 } 223 224 private void handleReportPhysicalAddress(HdmiCecMessage cmd) { 225 Preconditions.checkState(mProcessedDeviceCount < mDevices.size()); 226 227 DeviceInfo current = mDevices.get(mProcessedDeviceCount); 228 if (current.mLogicalAddress != cmd.getSource()) { 229 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" + 230 cmd.getSource()); 231 return; 232 } 233 234 byte params[] = cmd.getParams(); 235 if (params.length == 3) { 236 current.mPhysicalAddress = ((params[0] & 0xFF) << 8) | (params[1] & 0xFF); 237 current.mDeviceType = params[2] & 0xFF; 238 239 increaseProcessedDeviceCount(); 240 checkAndProceedStage(); 241 } else { 242 // Physical address is a critical element in device info. 243 // If failed, remove device from device list and proceed to the next device. 244 removeDevice(mProcessedDeviceCount); 245 checkAndProceedStage(); 246 } 247 } 248 249 private void handleSetOsdName(HdmiCecMessage cmd) { 250 Preconditions.checkState(mProcessedDeviceCount < mDevices.size()); 251 252 DeviceInfo current = mDevices.get(mProcessedDeviceCount); 253 if (current.mLogicalAddress != cmd.getSource()) { 254 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" + 255 cmd.getSource()); 256 return; 257 } 258 259 String displayName = null; 260 try { 261 displayName = new String(cmd.getParams(), "US-ASCII"); 262 } catch (UnsupportedEncodingException e) { 263 Slog.w(TAG, "Failed to decode display name: " + cmd.toString()); 264 // If failed to get display name, use the default name of device. 265 displayName = HdmiCec.getDefaultDeviceName(current.mLogicalAddress); 266 } 267 current.mDisplayName = displayName; 268 increaseProcessedDeviceCount(); 269 checkAndProceedStage(); 270 } 271 272 private void handleVendorId(HdmiCecMessage cmd) { 273 Preconditions.checkState(mProcessedDeviceCount < mDevices.size()); 274 275 DeviceInfo current = mDevices.get(mProcessedDeviceCount); 276 if (current.mLogicalAddress != cmd.getSource()) { 277 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" + 278 cmd.getSource()); 279 return; 280 } 281 282 byte[] params = cmd.getParams(); 283 if (params.length == 3) { 284 int vendorId = ((params[0] & 0xFF) << 16) 285 | ((params[1] & 0xFF) << 8) 286 | (params[2] & 0xFF); 287 current.mVendorId = vendorId; 288 } else { 289 Slog.w(TAG, "Invalid vendor id: " + cmd.toString()); 290 } 291 increaseProcessedDeviceCount(); 292 checkAndProceedStage(); 293 } 294 295 private void increaseProcessedDeviceCount() { 296 mProcessedDeviceCount++; 297 } 298 299 private void removeDevice(int index) { 300 mDevices.remove(index); 301 } 302 303 private void wrapUpAndFinish() { 304 ArrayList<HdmiCecDeviceInfo> result = new ArrayList<>(); 305 for (DeviceInfo info : mDevices) { 306 HdmiCecDeviceInfo cecDeviceInfo = info.toHdmiCecDeviceInfo(); 307 result.add(cecDeviceInfo); 308 } 309 mCallback.onDeviceDiscoveryDone(result); 310 finish(); 311 } 312 313 private void checkAndProceedStage() { 314 if (mDevices.isEmpty()) { 315 wrapUpAndFinish(); 316 return; 317 } 318 319 // If finished current stage, move on to next stage. 320 if (mProcessedDeviceCount == mDevices.size()) { 321 mProcessedDeviceCount = 0; 322 switch (mState) { 323 case STATE_WAITING_FOR_PHYSICAL_ADDRESS: 324 startOsdNameStage(); 325 return; 326 case STATE_WAITING_FOR_OSD_NAME: 327 startVendorIdStage(); 328 return; 329 case STATE_WAITING_FOR_VENDOR_ID: 330 wrapUpAndFinish(); 331 return; 332 default: 333 return; 334 } 335 } else { 336 int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress; 337 switch (mState) { 338 case STATE_WAITING_FOR_PHYSICAL_ADDRESS: 339 queryPhysicalAddress(address); 340 return; 341 case STATE_WAITING_FOR_OSD_NAME: 342 queryOsdName(address); 343 return; 344 case STATE_WAITING_FOR_VENDOR_ID: 345 queryVendorId(address); 346 default: 347 return; 348 } 349 } 350 } 351 352 @Override 353 void handleTimerEvent(int state) { 354 if (mState == STATE_NONE || mState != state) { 355 return; 356 } 357 358 removeDevice(mProcessedDeviceCount); 359 checkAndProceedStage(); 360 } 361} 362