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