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