DeviceDiscoveryAction.java revision 3ecdd832c77483c909fbf90d17d0e6d97ca365ee
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 }, HdmiConstants.POLL_ITERATION_REVERSE_ORDER 123 | HdmiConstants.POLL_STRATEGY_REMOTES_DEVICES, DEVICE_POLLING_RETRY); 124 return true; 125 } 126 127 private void allocateDevices(List<Integer> addresses) { 128 for (Integer i : addresses) { 129 DeviceInfo info = new DeviceInfo(i); 130 mDevices.add(info); 131 } 132 } 133 134 private void startPhysicalAddressStage() { 135 Slog.v(TAG, "Start [Physical Address Stage]:" + mDevices.size()); 136 mProcessedDeviceCount = 0; 137 mState = STATE_WAITING_FOR_PHYSICAL_ADDRESS; 138 139 checkAndProceedStage(); 140 } 141 142 private boolean verifyValidLogicalAddress(int address) { 143 return address >= HdmiCec.ADDR_TV && address < HdmiCec.ADDR_UNREGISTERED; 144 } 145 146 private void queryPhysicalAddress(int address) { 147 if (!verifyValidLogicalAddress(address)) { 148 checkAndProceedStage(); 149 return; 150 } 151 152 mActionTimer.clearTimerMessage(); 153 154 // Check cache first and send request if not exist. 155 if (mayProcessMessageIfCached(address, HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS)) { 156 return; 157 } 158 sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(), address)); 159 addTimer(mState, TIMEOUT_MS); 160 } 161 162 private void startOsdNameStage() { 163 Slog.v(TAG, "Start [Osd Name Stage]:" + mDevices.size()); 164 mProcessedDeviceCount = 0; 165 mState = STATE_WAITING_FOR_OSD_NAME; 166 167 checkAndProceedStage(); 168 } 169 170 private void queryOsdName(int address) { 171 if (!verifyValidLogicalAddress(address)) { 172 checkAndProceedStage(); 173 return; 174 } 175 176 mActionTimer.clearTimerMessage(); 177 178 if (mayProcessMessageIfCached(address, HdmiCec.MESSAGE_SET_OSD_NAME)) { 179 return; 180 } 181 sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(), address)); 182 addTimer(mState, TIMEOUT_MS); 183 } 184 185 private void startVendorIdStage() { 186 Slog.v(TAG, "Start [Vendor Id Stage]:" + mDevices.size()); 187 188 mProcessedDeviceCount = 0; 189 mState = STATE_WAITING_FOR_VENDOR_ID; 190 191 checkAndProceedStage(); 192 } 193 194 private void queryVendorId(int address) { 195 if (!verifyValidLogicalAddress(address)) { 196 checkAndProceedStage(); 197 return; 198 } 199 200 mActionTimer.clearTimerMessage(); 201 202 if (mayProcessMessageIfCached(address, HdmiCec.MESSAGE_DEVICE_VENDOR_ID)) { 203 return; 204 } 205 sendCommand( 206 HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(getSourceAddress(), address)); 207 addTimer(mState, TIMEOUT_MS); 208 } 209 210 private boolean mayProcessMessageIfCached(int address, int opcode) { 211 HdmiCecMessage message = getCecMessageCache().getMessage(address, opcode); 212 if (message != null) { 213 processCommand(message); 214 return true; 215 } 216 return false; 217 } 218 219 @Override 220 boolean processCommand(HdmiCecMessage cmd) { 221 switch (mState) { 222 case STATE_WAITING_FOR_PHYSICAL_ADDRESS: 223 if (cmd.getOpcode() == HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS) { 224 handleReportPhysicalAddress(cmd); 225 return true; 226 } 227 return false; 228 case STATE_WAITING_FOR_OSD_NAME: 229 if (cmd.getOpcode() == HdmiCec.MESSAGE_SET_OSD_NAME) { 230 handleSetOsdName(cmd); 231 return true; 232 } 233 return false; 234 case STATE_WAITING_FOR_VENDOR_ID: 235 if (cmd.getOpcode() == HdmiCec.MESSAGE_DEVICE_VENDOR_ID) { 236 handleVendorId(cmd); 237 return true; 238 } 239 return false; 240 case STATE_WAITING_FOR_DEVICE_POLLING: 241 // Fall through. 242 default: 243 return false; 244 } 245 } 246 247 private void handleReportPhysicalAddress(HdmiCecMessage cmd) { 248 Preconditions.checkState(mProcessedDeviceCount < mDevices.size()); 249 250 DeviceInfo current = mDevices.get(mProcessedDeviceCount); 251 if (current.mLogicalAddress != cmd.getSource()) { 252 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" + 253 cmd.getSource()); 254 return; 255 } 256 257 byte params[] = cmd.getParams(); 258 if (params.length == 3) { 259 current.mPhysicalAddress = HdmiUtils.twoBytesToInt(params); 260 current.mDeviceType = params[2] & 0xFF; 261 262 increaseProcessedDeviceCount(); 263 checkAndProceedStage(); 264 } else { 265 // Physical address is a critical element in device info. 266 // If failed, remove device from device list and proceed to the next device. 267 removeDevice(mProcessedDeviceCount); 268 checkAndProceedStage(); 269 } 270 } 271 272 private void handleSetOsdName(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 String displayName = null; 283 try { 284 displayName = new String(cmd.getParams(), "US-ASCII"); 285 } catch (UnsupportedEncodingException e) { 286 Slog.w(TAG, "Failed to decode display name: " + cmd.toString()); 287 // If failed to get display name, use the default name of device. 288 displayName = HdmiCec.getDefaultDeviceName(current.mLogicalAddress); 289 } 290 current.mDisplayName = displayName; 291 increaseProcessedDeviceCount(); 292 checkAndProceedStage(); 293 } 294 295 private void handleVendorId(HdmiCecMessage cmd) { 296 Preconditions.checkState(mProcessedDeviceCount < mDevices.size()); 297 298 DeviceInfo current = mDevices.get(mProcessedDeviceCount); 299 if (current.mLogicalAddress != cmd.getSource()) { 300 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" + 301 cmd.getSource()); 302 return; 303 } 304 305 byte[] params = cmd.getParams(); 306 if (params.length == 3) { 307 int vendorId = HdmiUtils.threeBytesToInt(params); 308 current.mVendorId = vendorId; 309 } else { 310 Slog.w(TAG, "Invalid vendor id: " + cmd.toString()); 311 } 312 increaseProcessedDeviceCount(); 313 checkAndProceedStage(); 314 } 315 316 private void increaseProcessedDeviceCount() { 317 mProcessedDeviceCount++; 318 } 319 320 private void removeDevice(int index) { 321 mDevices.remove(index); 322 } 323 324 private void wrapUpAndFinish() { 325 Slog.v(TAG, "---------Wrap up Device Discovery:[" + mDevices.size() + "]---------"); 326 ArrayList<HdmiCecDeviceInfo> result = new ArrayList<>(); 327 for (DeviceInfo info : mDevices) { 328 HdmiCecDeviceInfo cecDeviceInfo = info.toHdmiCecDeviceInfo(); 329 Slog.v(TAG, " DeviceInfo: " + cecDeviceInfo); 330 result.add(cecDeviceInfo); 331 } 332 Slog.v(TAG, "--------------------------------------------"); 333 mCallback.onDeviceDiscoveryDone(result); 334 finish(); 335 } 336 337 private void checkAndProceedStage() { 338 if (mDevices.isEmpty()) { 339 wrapUpAndFinish(); 340 return; 341 } 342 343 // If finished current stage, move on to next stage. 344 if (mProcessedDeviceCount == mDevices.size()) { 345 mProcessedDeviceCount = 0; 346 switch (mState) { 347 case STATE_WAITING_FOR_PHYSICAL_ADDRESS: 348 startOsdNameStage(); 349 return; 350 case STATE_WAITING_FOR_OSD_NAME: 351 startVendorIdStage(); 352 return; 353 case STATE_WAITING_FOR_VENDOR_ID: 354 wrapUpAndFinish(); 355 return; 356 default: 357 return; 358 } 359 } else { 360 int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress; 361 switch (mState) { 362 case STATE_WAITING_FOR_PHYSICAL_ADDRESS: 363 queryPhysicalAddress(address); 364 return; 365 case STATE_WAITING_FOR_OSD_NAME: 366 queryOsdName(address); 367 return; 368 case STATE_WAITING_FOR_VENDOR_ID: 369 queryVendorId(address); 370 default: 371 return; 372 } 373 } 374 } 375 376 @Override 377 void handleTimerEvent(int state) { 378 if (mState == STATE_NONE || mState != state) { 379 return; 380 } 381 382 Slog.v(TAG, "Timeout[State=" + mState + ", Processed=" + mProcessedDeviceCount); 383 removeDevice(mProcessedDeviceCount); 384 checkAndProceedStage(); 385 } 386} 387