HdmiControlService.java revision 0d3985a977886a0d6d6bb7ba6d048c1e21b05a24
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.annotation.Nullable; 20import android.content.Context; 21import android.hardware.hdmi.HdmiCec; 22import android.hardware.hdmi.HdmiCecDeviceInfo; 23import android.hardware.hdmi.HdmiCecMessage; 24import android.hardware.hdmi.HdmiHotplugEvent; 25import android.hardware.hdmi.HdmiPortInfo; 26import android.hardware.hdmi.IHdmiControlCallback; 27import android.hardware.hdmi.IHdmiControlService; 28import android.hardware.hdmi.IHdmiDeviceEventListener; 29import android.hardware.hdmi.IHdmiHotplugEventListener; 30import android.os.Build; 31import android.os.Handler; 32import android.os.HandlerThread; 33import android.os.IBinder; 34import android.os.Looper; 35import android.os.RemoteException; 36import android.util.Slog; 37import android.util.SparseArray; 38import android.util.SparseIntArray; 39 40import com.android.internal.annotations.GuardedBy; 41import com.android.server.SystemService; 42import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; 43 44import java.util.ArrayList; 45import java.util.Collections; 46import java.util.Iterator; 47import java.util.LinkedList; 48import java.util.List; 49 50/** 51 * Provides a service for sending and processing HDMI control messages, 52 * HDMI-CEC and MHL control command, and providing the information on both standard. 53 */ 54public final class HdmiControlService extends SystemService { 55 private static final String TAG = "HdmiControlService"; 56 57 // TODO: Rename the permission to HDMI_CONTROL. 58 private static final String PERMISSION = "android.permission.HDMI_CEC"; 59 60 static final int SEND_RESULT_SUCCESS = 0; 61 static final int SEND_RESULT_NAK = -1; 62 static final int SEND_RESULT_FAILURE = -2; 63 64 static final int POLL_STRATEGY_MASK = 0x3; // first and second bit. 65 static final int POLL_STRATEGY_REMOTES_DEVICES = 0x1; 66 static final int POLL_STRATEGY_SYSTEM_AUDIO = 0x2; 67 68 static final int POLL_ITERATION_STRATEGY_MASK = 0x30000; // first and second bit. 69 static final int POLL_ITERATION_IN_ORDER = 0x10000; 70 static final int POLL_ITERATION_REVERSE_ORDER = 0x20000; 71 72 /** 73 * Interface to report send result. 74 */ 75 interface SendMessageCallback { 76 /** 77 * Called when {@link HdmiControlService#sendCecCommand} is completed. 78 * 79 * @param error result of send request. 80 * @see {@link #SEND_RESULT_SUCCESS} 81 * @see {@link #SEND_RESULT_NAK} 82 * @see {@link #SEND_RESULT_FAILURE} 83 */ 84 void onSendCompleted(int error); 85 } 86 87 /** 88 * Interface to get a list of available logical devices. 89 */ 90 interface DevicePollingCallback { 91 /** 92 * Called when device polling is finished. 93 * 94 * @param ackedAddress a list of logical addresses of available devices 95 */ 96 void onPollingFinished(List<Integer> ackedAddress); 97 } 98 99 // A thread to handle synchronous IO of CEC and MHL control service. 100 // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms) 101 // and sparse call it shares a thread to handle IO operations. 102 private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread"); 103 104 // A collection of FeatureAction. 105 // Note that access to this collection should happen in service thread. 106 private final LinkedList<FeatureAction> mActions = new LinkedList<>(); 107 108 // Used to synchronize the access to the service. 109 private final Object mLock = new Object(); 110 111 // Type of logical devices hosted in the system. Stored in the unmodifiable list. 112 private final List<Integer> mLocalDevices; 113 114 // List of listeners registered by callers that want to get notified of 115 // hotplug events. 116 private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>(); 117 118 // List of records for hotplug event listener to handle the the caller killed in action. 119 private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords = 120 new ArrayList<>(); 121 122 // List of listeners registered by callers that want to get notified of 123 // device status events. 124 private final ArrayList<IHdmiDeviceEventListener> mDeviceEventListeners = new ArrayList<>(); 125 126 // List of records for device event listener to handle the the caller killed in action. 127 private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords = 128 new ArrayList<>(); 129 130 // Handler running on service thread. It's used to run a task in service thread. 131 private final Handler mHandler = new Handler(); 132 133 private final HdmiCecMessageCache mCecMessageCache = new HdmiCecMessageCache(); 134 135 @Nullable 136 private HdmiCecController mCecController; 137 138 @Nullable 139 private HdmiMhlController mMhlController; 140 141 // HDMI port information. Stored in the unmodifiable list to keep the static information 142 // from being modified. 143 private List<HdmiPortInfo> mPortInfo; 144 145 // Logical address of the active source. 146 @GuardedBy("mLock") 147 private int mActiveSource; 148 149 // Active routing path. Physical address of the active source but not all the time, such as 150 // when the new active source does not claim itself to be one. Note that we don't keep 151 // the active port id (or active input) since it can be gotten by {@link #pathToPortId(int)}. 152 @GuardedBy("mLock") 153 private int mActiveRoutingPath; 154 155 // Set to true while the service is in normal mode. While set to false, no input change is 156 // allowed. Used for situations where input change can confuse users such as channel auto-scan, 157 // system upgrade, etc., a.k.a. "prohibit mode". 158 @GuardedBy("mLock") 159 private boolean mInputChangeEnabled; 160 161 @GuardedBy("mLock") 162 // Whether ARC is "enabled" or not. 163 // TODO: it may need to hold lock if it's accessed from others. 164 private boolean mArcStatusEnabled = false; 165 166 @GuardedBy("mLock") 167 // Whether SystemAudioMode is "On" or not. 168 private boolean mSystemAudioMode; 169 170 public HdmiControlService(Context context) { 171 super(context); 172 mLocalDevices = HdmiUtils.asImmutableList(getContext().getResources().getIntArray( 173 com.android.internal.R.array.config_hdmiCecLogicalDeviceType)); 174 // TODO: Get control flag from persistent storage 175 mInputChangeEnabled = true; 176 } 177 178 @Override 179 public void onStart() { 180 mIoThread.start(); 181 mCecController = HdmiCecController.create(this); 182 183 if (mCecController != null) { 184 initializeLocalDevices(mLocalDevices); 185 } else { 186 Slog.i(TAG, "Device does not support HDMI-CEC."); 187 } 188 189 mMhlController = HdmiMhlController.create(this); 190 if (mMhlController == null) { 191 Slog.i(TAG, "Device does not support MHL-control."); 192 } 193 mPortInfo = initPortInfo(); 194 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); 195 196 // TODO: Read the preference for SystemAudioMode and initialize mSystemAudioMode and 197 // start to monitor the preference value and invoke SystemAudioActionFromTv if needed. 198 } 199 200 private void initializeLocalDevices(final List<Integer> deviceTypes) { 201 // A container for [Logical Address, Local device info]. 202 final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>(); 203 final SparseIntArray finished = new SparseIntArray(); 204 for (int type : deviceTypes) { 205 final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type); 206 localDevice.init(); 207 mCecController.allocateLogicalAddress(type, 208 localDevice.getPreferredAddress(), new AllocateAddressCallback() { 209 @Override 210 public void onAllocated(int deviceType, int logicalAddress) { 211 if (logicalAddress == HdmiCec.ADDR_UNREGISTERED) { 212 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]"); 213 } else { 214 HdmiCecDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType); 215 localDevice.setDeviceInfo(deviceInfo); 216 mCecController.addLocalDevice(deviceType, localDevice); 217 mCecController.addLogicalAddress(logicalAddress); 218 devices.append(logicalAddress, localDevice); 219 } 220 finished.append(deviceType, logicalAddress); 221 222 // Once finish address allocation for all devices, notify 223 // it to each device. 224 if (deviceTypes.size() == finished.size()) { 225 notifyAddressAllocated(devices); 226 } 227 } 228 }); 229 } 230 } 231 232 private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices) { 233 for (int i = 0; i < devices.size(); ++i) { 234 int address = devices.keyAt(i); 235 HdmiCecLocalDevice device = devices.valueAt(i); 236 device.onAddressAllocated(address); 237 } 238 } 239 240 // Initialize HDMI port information. Combine the information from CEC and MHL HAL and 241 // keep them in one place. 242 private List<HdmiPortInfo> initPortInfo() { 243 HdmiPortInfo[] cecPortInfo = null; 244 245 // CEC HAL provides majority of the info while MHL does only MHL support flag for 246 // each port. Return empty array if CEC HAL didn't provide the info. 247 if (mCecController != null) { 248 cecPortInfo = mCecController.getPortInfos(); 249 } 250 if (cecPortInfo == null) { 251 return Collections.emptyList(); 252 } 253 254 HdmiPortInfo[] mhlPortInfo = new HdmiPortInfo[0]; 255 if (mMhlController != null) { 256 // TODO: Implement plumbing logic to get MHL port information. 257 // mhlPortInfo = mMhlController.getPortInfos(); 258 } 259 260 // Use the id (port number) to find the matched info between CEC and MHL to combine them 261 // into one. Leave the field `mhlSupported` to false if matched MHL entry is not found. 262 ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length); 263 for (int i = 0; i < cecPortInfo.length; ++i) { 264 HdmiPortInfo cec = cecPortInfo[i]; 265 int id = cec.getId(); 266 boolean mhlInfoFound = false; 267 for (HdmiPortInfo mhl : mhlPortInfo) { 268 if (id == mhl.getId()) { 269 result.add(new HdmiPortInfo(id, cec.getType(), cec.getAddress(), 270 cec.isCecSupported(), mhl.isMhlSupported(), cec.isArcSupported())); 271 mhlInfoFound = true; 272 break; 273 } 274 } 275 if (!mhlInfoFound) { 276 result.add(cec); 277 } 278 } 279 280 return Collections.unmodifiableList(result); 281 } 282 283 /** 284 * Returns HDMI port information for the given port id. 285 * 286 * @param portId HDMI port id 287 * @return {@link HdmiPortInfo} for the given port 288 */ 289 HdmiPortInfo getPortInfo(int portId) { 290 // mPortInfo is an unmodifiable list and the only reference to its inner list. 291 // No lock is necessary. 292 for (HdmiPortInfo info : mPortInfo) { 293 if (portId == info.getId()) { 294 return info; 295 } 296 } 297 return null; 298 } 299 300 /** 301 * Returns the routing path (physical address) of the HDMI port for the given 302 * port id. 303 */ 304 int portIdToPath(int portId) { 305 HdmiPortInfo portInfo = getPortInfo(portId); 306 if (portInfo == null) { 307 Slog.e(TAG, "Cannot find the port info: " + portId); 308 return HdmiConstants.INVALID_PHYSICAL_ADDRESS; 309 } 310 return portInfo.getAddress(); 311 } 312 313 /** 314 * Returns the id of HDMI port located at the top of the hierarchy of 315 * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance, 316 * the port id to be returned is the ID associated with the port address 317 * 0x1000 (1.0.0.0) which is the topmost path of the given routing path. 318 */ 319 int pathToPortId(int path) { 320 int portAddress = path & HdmiConstants.ROUTING_PATH_TOP_MASK; 321 for (HdmiPortInfo info : mPortInfo) { 322 if (portAddress == info.getAddress()) { 323 return info.getId(); 324 } 325 } 326 return HdmiConstants.INVALID_PORT_ID; 327 } 328 329 /** 330 * Returns {@link Looper} for IO operation. 331 * 332 * <p>Declared as package-private. 333 */ 334 Looper getIoLooper() { 335 return mIoThread.getLooper(); 336 } 337 338 /** 339 * Returns {@link Looper} of main thread. Use this {@link Looper} instance 340 * for tasks that are running on main service thread. 341 * 342 * <p>Declared as package-private. 343 */ 344 Looper getServiceLooper() { 345 return mHandler.getLooper(); 346 } 347 348 int getActiveSource() { 349 synchronized (mLock) { 350 return mActiveSource; 351 } 352 } 353 354 /** 355 * Returns the active routing path. 356 */ 357 int getActivePath() { 358 synchronized (mLock) { 359 return mActiveRoutingPath; 360 } 361 } 362 363 /** 364 * Returns the ID of the active HDMI port. The active input is the port that has the active 365 * routing path connected directly or indirectly under the device hierarchy. 366 */ 367 int getActiveInput() { 368 synchronized (mLock) { 369 return pathToPortId(mActiveRoutingPath); 370 } 371 } 372 373 void updateActiveDevice(int logicalAddress, int physicalAddress) { 374 synchronized (mLock) { 375 mActiveSource = logicalAddress; 376 mActiveRoutingPath = physicalAddress; 377 } 378 } 379 380 void setInputChangeEnabled(boolean enabled) { 381 synchronized (mLock) { 382 mInputChangeEnabled = enabled; 383 } 384 } 385 386 /** 387 * Returns physical address of the device. 388 */ 389 int getPhysicalAddress() { 390 return mCecController.getPhysicalAddress(); 391 } 392 393 /** 394 * Returns vendor id of CEC service. 395 */ 396 int getVendorId() { 397 return mCecController.getVendorId(); 398 } 399 400 HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) { 401 assertRunOnServiceThread(); 402 return mCecController.getDeviceInfo(logicalAddress); 403 } 404 405 /** 406 * Returns version of CEC. 407 */ 408 int getCecVersion() { 409 return mCecController.getVersion(); 410 } 411 412 /** 413 * Returns a list of {@link HdmiCecDeviceInfo}. 414 * 415 * @param includeLocalDevice whether to include local devices 416 */ 417 List<HdmiCecDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) { 418 assertRunOnServiceThread(); 419 return mCecController.getDeviceInfoList(includeLocalDevice); 420 } 421 422 /** 423 * Returns the {@link HdmiCecDeviceInfo} instance whose physical address matches 424 * the given routing path. CEC devices use routing path for its physical address to 425 * describe the hierarchy of the devices in the network. 426 * 427 * @param path routing path or physical address 428 * @return {@link HdmiCecDeviceInfo} if the matched info is found; otherwise null 429 */ 430 HdmiCecDeviceInfo getDeviceInfoByPath(int path) { 431 assertRunOnServiceThread(); 432 for (HdmiCecDeviceInfo info : mCecController.getDeviceInfoList(false)) { 433 if (info.getPhysicalAddress() == path) { 434 return info; 435 } 436 } 437 return null; 438 } 439 440 /** 441 * Add and start a new {@link FeatureAction} to the action queue. 442 * 443 * @param action {@link FeatureAction} to add and start 444 */ 445 void addAndStartAction(final FeatureAction action) { 446 // TODO: may need to check the number of stale actions. 447 runOnServiceThread(new Runnable() { 448 @Override 449 public void run() { 450 mActions.add(action); 451 action.start(); 452 } 453 }); 454 } 455 456 void setSystemAudioMode(boolean on) { 457 synchronized (mLock) { 458 if (on != mSystemAudioMode) { 459 mSystemAudioMode = on; 460 // TODO: Need to set the preference for SystemAudioMode. 461 // TODO: Need to handle the notification of changing the mode and 462 // to identify the notification should be handled in the service or TvSettings. 463 } 464 } 465 } 466 467 boolean getSystemAudioMode() { 468 synchronized (mLock) { 469 return mSystemAudioMode; 470 } 471 } 472 473 /** 474 * Whether a device of the specified physical address is connected to ARC enabled port. 475 */ 476 boolean isConnectedToArcPort(int physicalAddress) { 477 for (HdmiPortInfo portInfo : mPortInfo) { 478 if (hasSameTopPort(portInfo.getAddress(), physicalAddress) 479 && portInfo.isArcSupported()) { 480 return true; 481 } 482 } 483 return false; 484 } 485 486 // See if we have an action of a given type in progress. 487 <T extends FeatureAction> boolean hasAction(final Class<T> clazz) { 488 for (FeatureAction action : mActions) { 489 if (action.getClass().equals(clazz)) { 490 return true; 491 } 492 } 493 return false; 494 } 495 496 // Returns all actions matched with given class type. 497 <T extends FeatureAction> List<T> getActions(final Class<T> clazz) { 498 ArrayList<T> actions = new ArrayList<>(); 499 for (FeatureAction action : mActions) { 500 if (action.getClass().equals(clazz)) { 501 actions.add((T) action); 502 } 503 } 504 return actions; 505 } 506 507 /** 508 * Remove the given {@link FeatureAction} object from the action queue. 509 * 510 * @param action {@link FeatureAction} to remove 511 */ 512 void removeAction(final FeatureAction action) { 513 assertRunOnServiceThread(); 514 mActions.remove(action); 515 } 516 517 // Remove all actions matched with the given Class type. 518 <T extends FeatureAction> void removeAction(final Class<T> clazz) { 519 removeActionExcept(clazz, null); 520 } 521 522 // Remove all actions matched with the given Class type besides |exception|. 523 <T extends FeatureAction> void removeActionExcept(final Class<T> clazz, 524 final FeatureAction exception) { 525 assertRunOnServiceThread(); 526 Iterator<FeatureAction> iter = mActions.iterator(); 527 while (iter.hasNext()) { 528 FeatureAction action = iter.next(); 529 if (action != exception && action.getClass().equals(clazz)) { 530 action.clear(); 531 mActions.remove(action); 532 } 533 } 534 } 535 536 private void runOnServiceThread(Runnable runnable) { 537 mHandler.post(runnable); 538 } 539 540 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) { 541 mHandler.postAtFrontOfQueue(runnable); 542 } 543 544 private void assertRunOnServiceThread() { 545 if (Looper.myLooper() != mHandler.getLooper()) { 546 throw new IllegalStateException("Should run on service thread."); 547 } 548 } 549 550 /** 551 * Change ARC status into the given {@code enabled} status. 552 * 553 * @return {@code true} if ARC was in "Enabled" status 554 */ 555 boolean setArcStatus(boolean enabled) { 556 assertRunOnServiceThread(); 557 synchronized (mLock) { 558 boolean oldStatus = mArcStatusEnabled; 559 // 1. Enable/disable ARC circuit. 560 mCecController.setAudioReturnChannel(enabled); 561 562 // TODO: notify arc mode change to AudioManager. 563 564 // 2. Update arc status; 565 mArcStatusEnabled = enabled; 566 return oldStatus; 567 } 568 } 569 570 /** 571 * Returns whether ARC is enabled or not. 572 */ 573 boolean getArcStatus() { 574 synchronized (mLock) { 575 return mArcStatusEnabled; 576 } 577 } 578 579 /** 580 * Transmit a CEC command to CEC bus. 581 * 582 * @param command CEC command to send out 583 * @param callback interface used to the result of send command 584 */ 585 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) { 586 mCecController.sendCommand(command, callback); 587 } 588 589 void sendCecCommand(HdmiCecMessage command) { 590 mCecController.sendCommand(command, null); 591 } 592 593 boolean handleCecCommand(HdmiCecMessage message) { 594 // Cache incoming message. Note that it caches only white-listed one. 595 mCecMessageCache.cacheMessage(message); 596 597 // Commands that queries system information replies directly instead 598 // of creating FeatureAction because they are state-less. 599 // TODO: move the leftover message to local device. 600 switch (message.getOpcode()) { 601 case HdmiCec.MESSAGE_INITIATE_ARC: 602 handleInitiateArc(message); 603 return true; 604 case HdmiCec.MESSAGE_TERMINATE_ARC: 605 handleTerminateArc(message); 606 return true; 607 case HdmiCec.MESSAGE_SET_SYSTEM_AUDIO_MODE: 608 handleSetSystemAudioMode(message); 609 return true; 610 case HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_STATUS: 611 handleSystemAudioModeStatus(message); 612 return true; 613 default: 614 if (dispatchMessageToAction(message)) { 615 return true; 616 } 617 break; 618 } 619 620 return dispatchMessageToLocalDevice(message); 621 } 622 623 private boolean dispatchMessageToAction(HdmiCecMessage message) { 624 for (FeatureAction action : mActions) { 625 if (action.processCommand(message)) { 626 return true; 627 } 628 } 629 return false; 630 } 631 632 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) { 633 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 634 if (device.dispatchMessage(message)) { 635 return true; 636 } 637 } 638 639 Slog.w(TAG, "Unhandled cec command:" + message); 640 return false; 641 } 642 643 /** 644 * Called when a new hotplug event is issued. 645 * 646 * @param portNo hdmi port number where hot plug event issued. 647 * @param connected whether to be plugged in or not 648 */ 649 void onHotplug(int portNo, boolean connected) { 650 assertRunOnServiceThread(); 651 // TODO: delegate onHotplug event to each local device. 652 653 // Tv device will have permanent HotplugDetectionAction. 654 List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class); 655 if (!hotplugActions.isEmpty()) { 656 // Note that hotplug action is single action running on a machine. 657 // "pollAllDevicesNow" cleans up timer and start poll action immediately. 658 hotplugActions.get(0).pollAllDevicesNow(); 659 } 660 661 announceHotplugEvent(portNo, connected); 662 } 663 664 /** 665 * Poll all remote devices. It sends <Polling Message> to all remote 666 * devices. 667 * 668 * @param callback an interface used to get a list of all remote devices' address 669 * @param pickStrategy strategy how to pick polling candidates 670 * @param retryCount the number of retry used to send polling message to remote devices 671 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value 672 */ 673 void pollDevices(DevicePollingCallback callback, int pickStrategy, int retryCount) { 674 mCecController.pollDevices(callback, checkPollStrategy(pickStrategy), retryCount); 675 } 676 677 private int checkPollStrategy(int pickStrategy) { 678 int strategy = pickStrategy & POLL_STRATEGY_MASK; 679 if (strategy == 0) { 680 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy); 681 } 682 int iterationStrategy = pickStrategy & POLL_ITERATION_STRATEGY_MASK; 683 if (iterationStrategy == 0) { 684 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy); 685 } 686 return strategy | iterationStrategy; 687 } 688 689 void clearAllDeviceInfo() { 690 assertRunOnServiceThread(); 691 mCecController.clearDeviceInfoList(); 692 } 693 694 List<HdmiCecLocalDevice> getAllLocalDevices() { 695 assertRunOnServiceThread(); 696 return mCecController.getLocalDeviceList(); 697 } 698 699 /** 700 * Whether a device of the specified physical address and logical address exists 701 * in a device info list. However, both are minimal condition and it could 702 * be different device from the original one. 703 * 704 * @param physicalAddress physical address of a device to be searched 705 * @param logicalAddress logical address of a device to be searched 706 * @return true if exist; otherwise false 707 */ 708 boolean isInDeviceList(int physicalAddress, int logicalAddress) { 709 assertRunOnServiceThread(); 710 HdmiCecDeviceInfo device = mCecController.getDeviceInfo(logicalAddress); 711 if (device == null) { 712 return false; 713 } 714 return device.getPhysicalAddress() == physicalAddress; 715 } 716 717 private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) { 718 // TODO: find better name instead of model name. 719 String displayName = Build.MODEL; 720 return new HdmiCecDeviceInfo(logicalAddress, 721 getPhysicalAddress(), deviceType, getVendorId(), displayName); 722 } 723 724 private void handleInitiateArc(HdmiCecMessage message){ 725 // In case where <Initiate Arc> is started by <Request ARC Initiation> 726 // need to clean up RequestArcInitiationAction. 727 removeAction(RequestArcInitiationAction.class); 728 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 729 message.getDestination(), message.getSource(), true); 730 addAndStartAction(action); 731 } 732 733 private void handleTerminateArc(HdmiCecMessage message) { 734 // In case where <Terminate Arc> is started by <Request ARC Termination> 735 // need to clean up RequestArcInitiationAction. 736 // TODO: check conditions of power status by calling is_connected api 737 // to be added soon. 738 removeAction(RequestArcTerminationAction.class); 739 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 740 message.getDestination(), message.getSource(), false); 741 addAndStartAction(action); 742 } 743 744 private void handleSetSystemAudioMode(HdmiCecMessage message) { 745 if (dispatchMessageToAction(message) || !isMessageForSystemAudio(message)) { 746 return; 747 } 748 SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this, 749 message.getDestination(), message.getSource(), 750 HdmiUtils.parseCommandParamSystemAudioStatus(message)); 751 addAndStartAction(action); 752 } 753 754 private void handleSystemAudioModeStatus(HdmiCecMessage message) { 755 if (!isMessageForSystemAudio(message)) { 756 return; 757 } 758 setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message)); 759 } 760 761 private boolean isMessageForSystemAudio(HdmiCecMessage message) { 762 if (message.getSource() != HdmiCec.ADDR_AUDIO_SYSTEM 763 || message.getDestination() != HdmiCec.ADDR_TV 764 || getAvrDeviceInfo() == null) { 765 Slog.w(TAG, "Skip abnormal CecMessage: " + message); 766 return false; 767 } 768 return true; 769 } 770 771 // Record class that monitors the event of the caller of being killed. Used to clean up 772 // the listener list and record list accordingly. 773 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { 774 private final IHdmiHotplugEventListener mListener; 775 776 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) { 777 mListener = listener; 778 } 779 780 @Override 781 public void binderDied() { 782 synchronized (mLock) { 783 mHotplugEventListenerRecords.remove(this); 784 mHotplugEventListeners.remove(mListener); 785 } 786 } 787 } 788 789 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient { 790 private final IHdmiDeviceEventListener mListener; 791 792 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) { 793 mListener = listener; 794 } 795 796 @Override 797 public void binderDied() { 798 synchronized (mLock) { 799 mDeviceEventListenerRecords.remove(this); 800 mDeviceEventListeners.remove(mListener); 801 } 802 } 803 } 804 805 private void enforceAccessPermission() { 806 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); 807 } 808 809 private final class BinderService extends IHdmiControlService.Stub { 810 @Override 811 public int[] getSupportedTypes() { 812 enforceAccessPermission(); 813 // mLocalDevices is an unmodifiable list - no lock necesary. 814 int[] localDevices = new int[mLocalDevices.size()]; 815 for (int i = 0; i < localDevices.length; ++i) { 816 localDevices[i] = mLocalDevices.get(i); 817 } 818 return localDevices; 819 } 820 821 @Override 822 public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) { 823 enforceAccessPermission(); 824 runOnServiceThread(new Runnable() { 825 @Override 826 public void run() { 827 HdmiCecLocalDeviceTv tv = 828 (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiCec.DEVICE_TV); 829 if (tv == null) { 830 Slog.w(TAG, "Local playback device not available"); 831 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 832 return; 833 } 834 tv.deviceSelect(logicalAddress, callback); 835 } 836 }); 837 } 838 839 @Override 840 public void oneTouchPlay(final IHdmiControlCallback callback) { 841 enforceAccessPermission(); 842 runOnServiceThread(new Runnable() { 843 @Override 844 public void run() { 845 HdmiControlService.this.oneTouchPlay(callback); 846 } 847 }); 848 } 849 850 @Override 851 public void queryDisplayStatus(final IHdmiControlCallback callback) { 852 enforceAccessPermission(); 853 runOnServiceThread(new Runnable() { 854 @Override 855 public void run() { 856 HdmiControlService.this.queryDisplayStatus(callback); 857 } 858 }); 859 } 860 861 @Override 862 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 863 enforceAccessPermission(); 864 runOnServiceThread(new Runnable() { 865 @Override 866 public void run() { 867 HdmiControlService.this.addHotplugEventListener(listener); 868 } 869 }); 870 } 871 872 @Override 873 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) { 874 enforceAccessPermission(); 875 runOnServiceThread(new Runnable() { 876 @Override 877 public void run() { 878 HdmiControlService.this.removeHotplugEventListener(listener); 879 } 880 }); 881 } 882 883 @Override 884 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) { 885 enforceAccessPermission(); 886 runOnServiceThread(new Runnable() { 887 @Override 888 public void run() { 889 HdmiControlService.this.addDeviceEventListener(listener); 890 } 891 }); 892 } 893 894 @Override 895 public void portSelect(int portId, IHdmiControlCallback callback) { 896 // TODO: Implement this 897 } 898 899 @Override 900 public void sendKeyEvent(int keyCode, boolean isPressed) { 901 // TODO: Implement this 902 } 903 904 @Override 905 public List<HdmiPortInfo> getPortInfo() { 906 enforceAccessPermission(); 907 return mPortInfo; 908 } 909 } 910 911 private void oneTouchPlay(IHdmiControlCallback callback) { 912 if (hasAction(OneTouchPlayAction.class)) { 913 Slog.w(TAG, "oneTouchPlay already in progress"); 914 invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS); 915 return; 916 } 917 HdmiCecLocalDevice source = mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK); 918 if (source == null) { 919 Slog.w(TAG, "Local playback device not available"); 920 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 921 return; 922 } 923 // TODO: Consider the case of multiple TV sets. For now we always direct the command 924 // to the primary one. 925 OneTouchPlayAction action = OneTouchPlayAction.create(this, 926 source.getDeviceInfo().getLogicalAddress(), 927 source.getDeviceInfo().getPhysicalAddress(), HdmiCec.ADDR_TV, callback); 928 if (action == null) { 929 Slog.w(TAG, "Cannot initiate oneTouchPlay"); 930 invokeCallback(callback, HdmiCec.RESULT_EXCEPTION); 931 return; 932 } 933 addAndStartAction(action); 934 } 935 936 private void queryDisplayStatus(IHdmiControlCallback callback) { 937 if (hasAction(DevicePowerStatusAction.class)) { 938 Slog.w(TAG, "queryDisplayStatus already in progress"); 939 invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS); 940 return; 941 } 942 HdmiCecLocalDevice source = mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK); 943 if (source == null) { 944 Slog.w(TAG, "Local playback device not available"); 945 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 946 return; 947 } 948 DevicePowerStatusAction action = DevicePowerStatusAction.create(this, 949 source.getDeviceInfo().getLogicalAddress(), HdmiCec.ADDR_TV, callback); 950 if (action == null) { 951 Slog.w(TAG, "Cannot initiate queryDisplayStatus"); 952 invokeCallback(callback, HdmiCec.RESULT_EXCEPTION); 953 return; 954 } 955 addAndStartAction(action); 956 } 957 958 private void addHotplugEventListener(IHdmiHotplugEventListener listener) { 959 HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); 960 try { 961 listener.asBinder().linkToDeath(record, 0); 962 } catch (RemoteException e) { 963 Slog.w(TAG, "Listener already died"); 964 return; 965 } 966 synchronized (mLock) { 967 mHotplugEventListenerRecords.add(record); 968 mHotplugEventListeners.add(listener); 969 } 970 } 971 972 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) { 973 synchronized (mLock) { 974 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 975 if (record.mListener.asBinder() == listener.asBinder()) { 976 listener.asBinder().unlinkToDeath(record, 0); 977 mHotplugEventListenerRecords.remove(record); 978 break; 979 } 980 } 981 mHotplugEventListeners.remove(listener); 982 } 983 } 984 985 private void addDeviceEventListener(IHdmiDeviceEventListener listener) { 986 synchronized (mLock) { 987 for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) { 988 if (record.mListener.asBinder() == listener.asBinder()) { 989 listener.asBinder().unlinkToDeath(record, 0); 990 mDeviceEventListenerRecords.remove(record); 991 break; 992 } 993 } 994 mHotplugEventListeners.remove(listener); 995 } 996 } 997 998 private void invokeCallback(IHdmiControlCallback callback, int result) { 999 try { 1000 callback.onComplete(result); 1001 } catch (RemoteException e) { 1002 Slog.e(TAG, "Invoking callback failed:" + e); 1003 } 1004 } 1005 1006 HdmiCecDeviceInfo getAvrDeviceInfo() { 1007 return mCecController.getDeviceInfo(HdmiCec.ADDR_AUDIO_SYSTEM); 1008 } 1009 1010 void setAudioStatus(boolean mute, int volume) { 1011 // TODO: Hook up with AudioManager. 1012 } 1013 1014 boolean isInPresetInstallationMode() { 1015 synchronized (mLock) { 1016 return !mInputChangeEnabled; 1017 } 1018 } 1019 1020 /** 1021 * Called when a device is newly added or a new device is detected. 1022 * 1023 * @param info device info of a new device. 1024 */ 1025 void addCecDevice(HdmiCecDeviceInfo info) { 1026 mCecController.addDeviceInfo(info); 1027 1028 // TODO: announce new device detection. 1029 } 1030 1031 /** 1032 * Called when a device is removed or removal of device is detected. 1033 * 1034 * @param address a logical address of a device to be removed 1035 */ 1036 void removeCecDevice(int address) { 1037 mCecController.removeDeviceInfo(address); 1038 mCecMessageCache.flushMessagesFrom(address); 1039 1040 // TODO: announce a device removal. 1041 } 1042 1043 private void announceHotplugEvent(int portNo, boolean connected) { 1044 HdmiHotplugEvent event = new HdmiHotplugEvent(portNo, connected); 1045 synchronized (mLock) { 1046 for (IHdmiHotplugEventListener listener : mHotplugEventListeners) { 1047 invokeHotplugEventListener(listener, event); 1048 } 1049 } 1050 } 1051 1052 private void invokeHotplugEventListener(IHdmiHotplugEventListener listener, 1053 HdmiHotplugEvent event) { 1054 try { 1055 listener.onReceived(event); 1056 } catch (RemoteException e) { 1057 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e); 1058 } 1059 } 1060 1061 HdmiCecMessageCache getCecMessageCache() { 1062 return mCecMessageCache; 1063 } 1064 1065 private static boolean hasSameTopPort(int path1, int path2) { 1066 return (path1 & HdmiConstants.ROUTING_PATH_TOP_MASK) 1067 == (path2 & HdmiConstants.ROUTING_PATH_TOP_MASK); 1068 } 1069 1070 /** 1071 * Whether the given path is located in the tail of current active path. 1072 * 1073 * @param path to be tested 1074 * @return true if the given path is located in the tail of current active path; otherwise, 1075 * false 1076 */ 1077 // TODO: move this to local device tv. 1078 boolean isTailOfActivePath(int path) { 1079 // If active routing path is internal source, return false. 1080 if (mActiveRoutingPath == 0) { 1081 return false; 1082 } 1083 for (int i = 12; i >= 0; i -= 4) { 1084 int curActivePath = (mActiveRoutingPath >> i) & 0xF; 1085 if (curActivePath == 0) { 1086 return true; 1087 } else { 1088 int curPath = (path >> i) & 0xF; 1089 if (curPath != curActivePath) { 1090 return false; 1091 } 1092 } 1093 } 1094 return false; 1095 } 1096} 1097