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