DeviceDiscoveryAction.java revision 79c58a4b97f27ede6a1b680d2fece9c2a0edf7b7
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 /** 59 * Interface used to report result of device discovery. 60 */ 61 interface DeviceDiscoveryCallback { 62 /** 63 * Called when device discovery is done. 64 * 65 * @param deviceInfos a list of all non-local devices. It can be empty list. 66 */ 67 void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos); 68 } 69 70 // An internal container used to keep track of device information during 71 // this action. 72 private static final class DeviceInfo { 73 private final int mLogicalAddress; 74 75 private int mPhysicalAddress = HdmiConstants.INVALID_PHYSICAL_ADDRESS; 76 private int mVendorId = HdmiCec.UNKNOWN_VENDOR_ID; 77 private String mDisplayName = ""; 78 private int mDeviceType = HdmiCec.DEVICE_INACTIVE; 79 80 private DeviceInfo(int logicalAddress) { 81 mLogicalAddress = logicalAddress; 82 } 83 84 private HdmiCecDeviceInfo toHdmiCecDeviceInfo() { 85 return new HdmiCecDeviceInfo(mLogicalAddress, mPhysicalAddress, mDeviceType, mVendorId, 86 mDisplayName); 87 } 88 } 89 90 private final ArrayList<DeviceInfo> mDevices = new ArrayList<>(); 91 private final DeviceDiscoveryCallback mCallback; 92 private int mProcessedDeviceCount = 0; 93 94 /** 95 * Constructor. 96 * 97 * @param source an instance of {@link HdmiCecLocalDevice}. 98 */ 99 DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback) { 100 super(source); 101 mCallback = Preconditions.checkNotNull(callback); 102 } 103 104 @Override 105 boolean start() { 106 mDevices.clear(); 107 mState = STATE_WAITING_FOR_DEVICE_POLLING; 108 109 pollDevices(new DevicePollingCallback() { 110 @Override 111 public void onPollingFinished(List<Integer> ackedAddress) { 112 if (ackedAddress.isEmpty()) { 113 Slog.v(TAG, "No device is detected."); 114 finish(); 115 return; 116 } 117 118 Slog.v(TAG, "Device detected: " + ackedAddress); 119 allocateDevices(ackedAddress); 120 startPhysicalAddressStage(); 121 } 122 }, HdmiControlService.POLL_STRATEGY_REMOTES_DEVICES, DEVICE_POLLING_RETRY); 123 return true; 124 } 125 126 private void allocateDevices(List<Integer> addresses) { 127 for (Integer i : addresses) { 128 DeviceInfo info = new DeviceInfo(i); 129 mDevices.add(info); 130 } 131 } 132 133 private void startPhysicalAddressStage() { 134 Slog.v(TAG, "Start [Physical Address Stage]:" + mDevices.size()); 135 mProcessedDeviceCount = 0; 136 mState = STATE_WAITING_FOR_PHYSICAL_ADDRESS; 137 138 checkAndProceedStage(); 139 } 140 141 private boolean verifyValidLogicalAddress(int address) { 142 return address >= HdmiCec.ADDR_TV && address < HdmiCec.ADDR_UNREGISTERED; 143 } 144 145 private void queryPhysicalAddress(int address) { 146 if (!verifyValidLogicalAddress(address)) { 147 checkAndProceedStage(); 148 return; 149 } 150 151 mActionTimer.clearTimerMessage(); 152 153 // Check cache first and send request if not exist. 154 if (mayProcessMessageIfCached(address, HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS)) { 155 return; 156 } 157 sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(), address)); 158 addTimer(mState, TIMEOUT_MS); 159 } 160 161 private void startOsdNameStage() { 162 Slog.v(TAG, "Start [Osd Name Stage]:" + mDevices.size()); 163 mProcessedDeviceCount = 0; 164 mState = STATE_WAITING_FOR_OSD_NAME; 165 166 checkAndProceedStage(); 167 } 168 169 private void queryOsdName(int address) { 170 if (!verifyValidLogicalAddress(address)) { 171 checkAndProceedStage(); 172 return; 173 } 174 175 mActionTimer.clearTimerMessage(); 176 177 if (mayProcessMessageIfCached(address, HdmiCec.MESSAGE_SET_OSD_NAME)) { 178 return; 179 } 180 sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(), address)); 181 addTimer(mState, TIMEOUT_MS); 182 } 183 184 private void startVendorIdStage() { 185 Slog.v(TAG, "Start [Vendor Id Stage]:" + mDevices.size()); 186 187 mProcessedDeviceCount = 0; 188 mState = STATE_WAITING_FOR_VENDOR_ID; 189 190 checkAndProceedStage(); 191 } 192 193 private void queryVendorId(int address) { 194 if (!verifyValidLogicalAddress(address)) { 195 checkAndProceedStage(); 196 return; 197 } 198 199 mActionTimer.clearTimerMessage(); 200 201 if (mayProcessMessageIfCached(address, HdmiCec.MESSAGE_DEVICE_VENDOR_ID)) { 202 return; 203 } 204 sendCommand( 205 HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(getSourceAddress(), address)); 206 addTimer(mState, TIMEOUT_MS); 207 } 208 209 private boolean mayProcessMessageIfCached(int address, int opcode) { 210 HdmiCecMessage message = getCecMessageCache().getMessage(address, opcode); 211 if (message != null) { 212 processCommand(message); 213 return true; 214 } 215 return false; 216 } 217 218 @Override 219 boolean processCommand(HdmiCecMessage cmd) { 220 switch (mState) { 221 case STATE_WAITING_FOR_PHYSICAL_ADDRESS: 222 if (cmd.getOpcode() == HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS) { 223 handleReportPhysicalAddress(cmd); 224 return true; 225 } 226 return false; 227 case STATE_WAITING_FOR_OSD_NAME: 228 if (cmd.getOpcode() == HdmiCec.MESSAGE_SET_OSD_NAME) { 229 handleSetOsdName(cmd); 230 return true; 231 } 232 return false; 233 case STATE_WAITING_FOR_VENDOR_ID: 234 if (cmd.getOpcode() == HdmiCec.MESSAGE_DEVICE_VENDOR_ID) { 235 handleVendorId(cmd); 236 return true; 237 } 238 return false; 239 case STATE_WAITING_FOR_DEVICE_POLLING: 240 // Fall through. 241 default: 242 return false; 243 } 244 } 245 246 private void handleReportPhysicalAddress(HdmiCecMessage cmd) { 247 Preconditions.checkState(mProcessedDeviceCount < mDevices.size()); 248 249 DeviceInfo current = mDevices.get(mProcessedDeviceCount); 250 if (current.mLogicalAddress != cmd.getSource()) { 251 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" + 252 cmd.getSource()); 253 return; 254 } 255 256 byte params[] = cmd.getParams(); 257 if (params.length == 3) { 258 current.mPhysicalAddress = HdmiUtils.twoBytesToInt(params); 259 current.mDeviceType = params[2] & 0xFF; 260 261 increaseProcessedDeviceCount(); 262 checkAndProceedStage(); 263 } else { 264 // Physical address is a critical element in device info. 265 // If failed, remove device from device list and proceed to the next device. 266 removeDevice(mProcessedDeviceCount); 267 checkAndProceedStage(); 268 } 269 } 270 271 private void handleSetOsdName(HdmiCecMessage cmd) { 272 Preconditions.checkState(mProcessedDeviceCount < mDevices.size()); 273 274 DeviceInfo current = mDevices.get(mProcessedDeviceCount); 275 if (current.mLogicalAddress != cmd.getSource()) { 276 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" + 277 cmd.getSource()); 278 return; 279 } 280 281 String displayName = null; 282 try { 283 displayName = new String(cmd.getParams(), "US-ASCII"); 284 } catch (UnsupportedEncodingException e) { 285 Slog.w(TAG, "Failed to decode display name: " + cmd.toString()); 286 // If failed to get display name, use the default name of device. 287 displayName = HdmiCec.getDefaultDeviceName(current.mLogicalAddress); 288 } 289 current.mDisplayName = displayName; 290 increaseProcessedDeviceCount(); 291 checkAndProceedStage(); 292 } 293 294 private void handleVendorId(HdmiCecMessage cmd) { 295 Preconditions.checkState(mProcessedDeviceCount < mDevices.size()); 296 297 DeviceInfo current = mDevices.get(mProcessedDeviceCount); 298 if (current.mLogicalAddress != cmd.getSource()) { 299 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" + 300 cmd.getSource()); 301 return; 302 } 303 304 byte[] params = cmd.getParams(); 305 if (params.length == 3) { 306 int vendorId = HdmiUtils.threeBytesToInt(params); 307 current.mVendorId = vendorId; 308 } else { 309 Slog.w(TAG, "Invalid vendor id: " + cmd.toString()); 310 } 311 increaseProcessedDeviceCount(); 312 checkAndProceedStage(); 313 } 314 315 private void increaseProcessedDeviceCount() { 316 mProcessedDeviceCount++; 317 } 318 319 private void removeDevice(int index) { 320 mDevices.remove(index); 321 } 322 323 private void wrapUpAndFinish() { 324 Slog.v(TAG, "---------Wrap up Device Discovery:[" + mDevices.size() + "]---------"); 325 ArrayList<HdmiCecDeviceInfo> result = new ArrayList<>(); 326 for (DeviceInfo info : mDevices) { 327 HdmiCecDeviceInfo cecDeviceInfo = info.toHdmiCecDeviceInfo(); 328 Slog.v(TAG, " DeviceInfo: " + cecDeviceInfo); 329 result.add(cecDeviceInfo); 330 } 331 Slog.v(TAG, "--------------------------------------------"); 332 mCallback.onDeviceDiscoveryDone(result); 333 finish(); 334 } 335 336 private void checkAndProceedStage() { 337 if (mDevices.isEmpty()) { 338 wrapUpAndFinish(); 339 return; 340 } 341 342 // If finished current stage, move on to next stage. 343 if (mProcessedDeviceCount == mDevices.size()) { 344 mProcessedDeviceCount = 0; 345 switch (mState) { 346 case STATE_WAITING_FOR_PHYSICAL_ADDRESS: 347 startOsdNameStage(); 348 return; 349 case STATE_WAITING_FOR_OSD_NAME: 350 startVendorIdStage(); 351 return; 352 case STATE_WAITING_FOR_VENDOR_ID: 353 wrapUpAndFinish(); 354 return; 355 default: 356 return; 357 } 358 } else { 359 int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress; 360 switch (mState) { 361 case STATE_WAITING_FOR_PHYSICAL_ADDRESS: 362 queryPhysicalAddress(address); 363 return; 364 case STATE_WAITING_FOR_OSD_NAME: 365 queryOsdName(address); 366 return; 367 case STATE_WAITING_FOR_VENDOR_ID: 368 queryVendorId(address); 369 default: 370 return; 371 } 372 } 373 } 374 375 @Override 376 void handleTimerEvent(int state) { 377 if (mState == STATE_NONE || mState != state) { 378 return; 379 } 380 381 Slog.v(TAG, "Timeout[State=" + mState + ", Processed=" + mProcessedDeviceCount); 382 removeDevice(mProcessedDeviceCount); 383 checkAndProceedStage(); 384 } 385} 386