DeviceDiscoveryAction.java revision 8b308d93c8fdcc7304b33d9b445ae3807eae97c8
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.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 }, 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 sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(mSourceAddress, 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 sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(mSourceAddress, address)); 177 addTimer(mState, TIMEOUT_MS); 178 } 179 180 private void startVendorIdStage() { 181 Slog.v(TAG, "Start [Vendor Id Stage]:" + mDevices.size()); 182 183 mProcessedDeviceCount = 0; 184 mState = STATE_WAITING_FOR_VENDOR_ID; 185 186 checkAndProceedStage(); 187 } 188 189 private void queryVendorId(int address) { 190 if (!verifyValidLogicalAddress(address)) { 191 checkAndProceedStage(); 192 return; 193 } 194 195 mActionTimer.clearTimerMessage(); 196 sendCommand(HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(mSourceAddress, address)); 197 addTimer(mState, TIMEOUT_MS); 198 } 199 200 @Override 201 boolean processCommand(HdmiCecMessage cmd) { 202 switch (mState) { 203 case STATE_WAITING_FOR_PHYSICAL_ADDRESS: 204 if (cmd.getOpcode() == HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS) { 205 handleReportPhysicalAddress(cmd); 206 return true; 207 } 208 return false; 209 case STATE_WAITING_FOR_OSD_NAME: 210 if (cmd.getOpcode() == HdmiCec.MESSAGE_SET_OSD_NAME) { 211 handleSetOsdName(cmd); 212 return true; 213 } 214 return false; 215 case STATE_WAITING_FOR_VENDOR_ID: 216 if (cmd.getOpcode() == HdmiCec.MESSAGE_DEVICE_VENDOR_ID) { 217 handleVendorId(cmd); 218 return true; 219 } 220 return false; 221 case STATE_WAITING_FOR_DEVICE_POLLING: 222 // Fall through. 223 default: 224 return false; 225 } 226 } 227 228 private void handleReportPhysicalAddress(HdmiCecMessage cmd) { 229 Preconditions.checkState(mProcessedDeviceCount < mDevices.size()); 230 231 DeviceInfo current = mDevices.get(mProcessedDeviceCount); 232 if (current.mLogicalAddress != cmd.getSource()) { 233 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" + 234 cmd.getSource()); 235 return; 236 } 237 238 byte params[] = cmd.getParams(); 239 if (params.length == 3) { 240 current.mPhysicalAddress = ((params[0] & 0xFF) << 8) | (params[1] & 0xFF); 241 current.mDeviceType = params[2] & 0xFF; 242 243 increaseProcessedDeviceCount(); 244 checkAndProceedStage(); 245 } else { 246 // Physical address is a critical element in device info. 247 // If failed, remove device from device list and proceed to the next device. 248 removeDevice(mProcessedDeviceCount); 249 checkAndProceedStage(); 250 } 251 } 252 253 private void handleSetOsdName(HdmiCecMessage cmd) { 254 Preconditions.checkState(mProcessedDeviceCount < mDevices.size()); 255 256 DeviceInfo current = mDevices.get(mProcessedDeviceCount); 257 if (current.mLogicalAddress != cmd.getSource()) { 258 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" + 259 cmd.getSource()); 260 return; 261 } 262 263 String displayName = null; 264 try { 265 displayName = new String(cmd.getParams(), "US-ASCII"); 266 } catch (UnsupportedEncodingException e) { 267 Slog.w(TAG, "Failed to decode display name: " + cmd.toString()); 268 // If failed to get display name, use the default name of device. 269 displayName = HdmiCec.getDefaultDeviceName(current.mLogicalAddress); 270 } 271 current.mDisplayName = displayName; 272 increaseProcessedDeviceCount(); 273 checkAndProceedStage(); 274 } 275 276 private void handleVendorId(HdmiCecMessage cmd) { 277 Preconditions.checkState(mProcessedDeviceCount < mDevices.size()); 278 279 DeviceInfo current = mDevices.get(mProcessedDeviceCount); 280 if (current.mLogicalAddress != cmd.getSource()) { 281 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" + 282 cmd.getSource()); 283 return; 284 } 285 286 byte[] params = cmd.getParams(); 287 if (params.length == 3) { 288 int vendorId = ((params[0] & 0xFF) << 16) 289 | ((params[1] & 0xFF) << 8) 290 | (params[2] & 0xFF); 291 current.mVendorId = vendorId; 292 } else { 293 Slog.w(TAG, "Invalid vendor id: " + cmd.toString()); 294 } 295 increaseProcessedDeviceCount(); 296 checkAndProceedStage(); 297 } 298 299 private void increaseProcessedDeviceCount() { 300 mProcessedDeviceCount++; 301 } 302 303 private void removeDevice(int index) { 304 mDevices.remove(index); 305 } 306 307 private void wrapUpAndFinish() { 308 Slog.v(TAG, "---------Wrap up Device Discovery:[" + mDevices.size() + "]---------"); 309 ArrayList<HdmiCecDeviceInfo> result = new ArrayList<>(); 310 for (DeviceInfo info : mDevices) { 311 HdmiCecDeviceInfo cecDeviceInfo = info.toHdmiCecDeviceInfo(); 312 Slog.v(TAG, " DeviceInfo: " + cecDeviceInfo); 313 result.add(cecDeviceInfo); 314 } 315 Slog.v(TAG, "--------------------------------------------"); 316 mCallback.onDeviceDiscoveryDone(result); 317 finish(); 318 } 319 320 private void checkAndProceedStage() { 321 if (mDevices.isEmpty()) { 322 wrapUpAndFinish(); 323 return; 324 } 325 326 // If finished current stage, move on to next stage. 327 if (mProcessedDeviceCount == mDevices.size()) { 328 mProcessedDeviceCount = 0; 329 switch (mState) { 330 case STATE_WAITING_FOR_PHYSICAL_ADDRESS: 331 startOsdNameStage(); 332 return; 333 case STATE_WAITING_FOR_OSD_NAME: 334 startVendorIdStage(); 335 return; 336 case STATE_WAITING_FOR_VENDOR_ID: 337 wrapUpAndFinish(); 338 return; 339 default: 340 return; 341 } 342 } else { 343 int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress; 344 switch (mState) { 345 case STATE_WAITING_FOR_PHYSICAL_ADDRESS: 346 queryPhysicalAddress(address); 347 return; 348 case STATE_WAITING_FOR_OSD_NAME: 349 queryOsdName(address); 350 return; 351 case STATE_WAITING_FOR_VENDOR_ID: 352 queryVendorId(address); 353 default: 354 return; 355 } 356 } 357 } 358 359 @Override 360 void handleTimerEvent(int state) { 361 if (mState == STATE_NONE || mState != state) { 362 return; 363 } 364 365 Slog.v(TAG, "Timeout[State=" + mState + ", Processed=" + mProcessedDeviceCount); 366 removeDevice(mProcessedDeviceCount); 367 checkAndProceedStage(); 368 } 369} 370