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