HdmiControlService.java revision 401e3de791c0e2a4348361fbd560da9530156e22
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.HdmiPortInfo; 25import android.hardware.hdmi.IHdmiControlCallback; 26import android.hardware.hdmi.IHdmiControlService; 27import android.hardware.hdmi.IHdmiHotplugEventListener; 28import android.os.Build; 29import android.os.Handler; 30import android.os.HandlerThread; 31import android.os.IBinder; 32import android.os.Looper; 33import android.os.RemoteException; 34import android.util.Slog; 35import android.util.SparseArray; 36import android.util.SparseIntArray; 37 38import com.android.internal.annotations.GuardedBy; 39import com.android.server.SystemService; 40import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback; 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 0xFFFF; // Use 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 -1; // Use 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 mSystemAudioMode = on; 450 } 451 } 452 453 boolean getSystemAudioMode() { 454 synchronized (mLock) { 455 return mSystemAudioMode; 456 } 457 } 458 459 // See if we have an action of a given type in progress. 460 <T extends FeatureAction> boolean hasAction(final Class<T> clazz) { 461 for (FeatureAction action : mActions) { 462 if (action.getClass().equals(clazz)) { 463 return true; 464 } 465 } 466 return false; 467 } 468 469 /** 470 * Remove the given {@link FeatureAction} object from the action queue. 471 * 472 * @param action {@link FeatureAction} to remove 473 */ 474 void removeAction(final FeatureAction action) { 475 assertRunOnServiceThread(); 476 mActions.remove(action); 477 } 478 479 // Remove all actions matched with the given Class type. 480 <T extends FeatureAction> void removeAction(final Class<T> clazz) { 481 removeActionExcept(clazz, null); 482 } 483 484 // Remove all actions matched with the given Class type besides |exception|. 485 <T extends FeatureAction> void removeActionExcept(final Class<T> clazz, 486 final FeatureAction exception) { 487 assertRunOnServiceThread(); 488 Iterator<FeatureAction> iter = mActions.iterator(); 489 while (iter.hasNext()) { 490 FeatureAction action = iter.next(); 491 if (action != exception && action.getClass().equals(clazz)) { 492 action.clear(); 493 mActions.remove(action); 494 } 495 } 496 } 497 498 private void runOnServiceThread(Runnable runnable) { 499 mHandler.post(runnable); 500 } 501 502 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) { 503 mHandler.postAtFrontOfQueue(runnable); 504 } 505 506 private void assertRunOnServiceThread() { 507 if (Looper.myLooper() != mHandler.getLooper()) { 508 throw new IllegalStateException("Should run on service thread."); 509 } 510 } 511 512 /** 513 * Change ARC status into the given {@code enabled} status. 514 * 515 * @return {@code true} if ARC was in "Enabled" status 516 */ 517 boolean setArcStatus(boolean enabled) { 518 assertRunOnServiceThread(); 519 synchronized (mLock) { 520 boolean oldStatus = mArcStatusEnabled; 521 // 1. Enable/disable ARC circuit. 522 mCecController.setAudioReturnChannel(enabled); 523 524 // TODO: notify arc mode change to AudioManager. 525 526 // 2. Update arc status; 527 mArcStatusEnabled = enabled; 528 return oldStatus; 529 } 530 } 531 532 /** 533 * Transmit a CEC command to CEC bus. 534 * 535 * @param command CEC command to send out 536 * @param callback interface used to the result of send command 537 */ 538 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) { 539 mCecController.sendCommand(command, callback); 540 } 541 542 void sendCecCommand(HdmiCecMessage command) { 543 mCecController.sendCommand(command, null); 544 } 545 546 boolean handleCecCommand(HdmiCecMessage message) { 547 // Cache incoming message. Note that it caches only white-listed one. 548 mCecMessageCache.cacheMessage(message); 549 550 // Commands that queries system information replies directly instead 551 // of creating FeatureAction because they are state-less. 552 // TODO: move the leftover message to local device. 553 switch (message.getOpcode()) { 554 case HdmiCec.MESSAGE_INITIATE_ARC: 555 handleInitiateArc(message); 556 return true; 557 case HdmiCec.MESSAGE_TERMINATE_ARC: 558 handleTerminateArc(message); 559 return true; 560 case HdmiCec.MESSAGE_SET_SYSTEM_AUDIO_MODE: 561 handleSetSystemAudioMode(message); 562 return true; 563 case HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_STATUS: 564 handleSystemAudioModeStatus(message); 565 return true; 566 default: 567 if (dispatchMessageToAction(message)) { 568 return true; 569 } 570 break; 571 } 572 573 return dispatchMessageToLocalDevice(message); 574 } 575 576 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) { 577 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 578 if (device.dispatchMessage(message)) { 579 return true; 580 } 581 } 582 return false; 583 } 584 585 /** 586 * Called when a new hotplug event is issued. 587 * 588 * @param portNo hdmi port number where hot plug event issued. 589 * @param connected whether to be plugged in or not 590 */ 591 void onHotplug(int portNo, boolean connected) { 592 // TODO: Start "RequestArcInitiationAction" if ARC port. 593 } 594 595 /** 596 * Poll all remote devices. It sends <Polling Message> to all remote 597 * devices. 598 * 599 * @param callback an interface used to get a list of all remote devices' address 600 * @param pickStrategy strategy how to pick polling candidates 601 * @param retryCount the number of retry used to send polling message to remote devices 602 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value 603 */ 604 void pollDevices(DevicePollingCallback callback, int pickStrategy, int retryCount) { 605 mCecController.pollDevices(callback, checkPollStrategy(pickStrategy), retryCount); 606 } 607 608 private int checkPollStrategy(int pickStrategy) { 609 int strategy = pickStrategy & POLL_STRATEGY_MASK; 610 if (strategy == 0) { 611 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy); 612 } 613 int iterationStrategy = pickStrategy & POLL_ITERATION_STRATEGY_MASK; 614 if (iterationStrategy == 0) { 615 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy); 616 } 617 return strategy | iterationStrategy; 618 } 619 620 /** 621 * Launch device discovery sequence. It starts with clearing the existing device info list. 622 * Note that it assumes that logical address of all local devices is already allocated. 623 * 624 * @param sourceAddress a logical address of tv 625 */ 626 void launchDeviceDiscovery(final int sourceAddress) { 627 // At first, clear all existing device infos. 628 mCecController.clearDeviceInfoList(); 629 // TODO: flush cec message cache when CEC is turned off. 630 631 DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, sourceAddress, 632 new DeviceDiscoveryCallback() { 633 @Override 634 public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) { 635 for (HdmiCecDeviceInfo info : deviceInfos) { 636 addCecDevice(info); 637 } 638 639 // Add device info of all local devices. 640 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 641 addCecDevice(device.getDeviceInfo()); 642 } 643 644 addAndStartAction(new HotplugDetectionAction(HdmiControlService.this, 645 sourceAddress)); 646 } 647 }); 648 addAndStartAction(action); 649 } 650 651 private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) { 652 // TODO: find better name instead of model name. 653 String displayName = Build.MODEL; 654 return new HdmiCecDeviceInfo(logicalAddress, 655 getPhysicalAddress(), deviceType, getVendorId(), displayName); 656 } 657 658 private void handleInitiateArc(HdmiCecMessage message){ 659 // In case where <Initiate Arc> is started by <Request ARC Initiation> 660 // need to clean up RequestArcInitiationAction. 661 removeAction(RequestArcInitiationAction.class); 662 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 663 message.getDestination(), message.getSource(), true); 664 addAndStartAction(action); 665 } 666 667 private void handleTerminateArc(HdmiCecMessage message) { 668 // In case where <Terminate Arc> is started by <Request ARC Termination> 669 // need to clean up RequestArcInitiationAction. 670 // TODO: check conditions of power status by calling is_connected api 671 // to be added soon. 672 removeAction(RequestArcTerminationAction.class); 673 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 674 message.getDestination(), message.getSource(), false); 675 addAndStartAction(action); 676 } 677 678 private boolean dispatchMessageToAction(HdmiCecMessage message) { 679 for (FeatureAction action : mActions) { 680 if (action.processCommand(message)) { 681 return true; 682 } 683 } 684 Slog.w(TAG, "Unsupported cec command:" + message); 685 return false; 686 } 687 688 private void handleSetSystemAudioMode(HdmiCecMessage message) { 689 if (dispatchMessageToAction(message) || !isMessageForSystemAudio(message)) { 690 return; 691 } 692 SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this, 693 message.getDestination(), message.getSource(), 694 HdmiUtils.parseCommandParamSystemAudioStatus(message)); 695 addAndStartAction(action); 696 } 697 698 private void handleSystemAudioModeStatus(HdmiCecMessage message) { 699 if (!isMessageForSystemAudio(message)) { 700 return; 701 } 702 setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message)); 703 } 704 705 private boolean isMessageForSystemAudio(HdmiCecMessage message) { 706 if (message.getSource() != HdmiCec.ADDR_AUDIO_SYSTEM 707 || message.getDestination() != HdmiCec.ADDR_TV 708 || getAvrDeviceInfo() == null) { 709 Slog.w(TAG, "Skip abnormal CecMessage: " + message); 710 return false; 711 } 712 return true; 713 } 714 715 // Record class that monitors the event of the caller of being killed. Used to clean up 716 // the listener list and record list accordingly. 717 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { 718 private final IHdmiHotplugEventListener mListener; 719 720 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) { 721 mListener = listener; 722 } 723 724 @Override 725 public void binderDied() { 726 synchronized (mLock) { 727 mHotplugEventListenerRecords.remove(this); 728 mHotplugEventListeners.remove(mListener); 729 } 730 } 731 } 732 733 void addCecDevice(HdmiCecDeviceInfo info) { 734 mCecController.addDeviceInfo(info); 735 } 736 737 private void enforceAccessPermission() { 738 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); 739 } 740 741 private final class BinderService extends IHdmiControlService.Stub { 742 @Override 743 public int[] getSupportedTypes() { 744 enforceAccessPermission(); 745 // mLocalDevices is an unmodifiable list - no lock necesary. 746 int[] localDevices = new int[mLocalDevices.size()]; 747 for (int i = 0; i < localDevices.length; ++i) { 748 localDevices[i] = mLocalDevices.get(i); 749 } 750 return localDevices; 751 } 752 753 @Override 754 public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) { 755 enforceAccessPermission(); 756 runOnServiceThread(new Runnable() { 757 @Override 758 public void run() { 759 HdmiCecLocalDeviceTv tv = 760 (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiCec.DEVICE_TV); 761 if (tv == null) { 762 Slog.w(TAG, "Local playback device not available"); 763 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 764 return; 765 } 766 tv.deviceSelect(logicalAddress, callback); 767 } 768 }); 769 } 770 771 772 @Override 773 public void oneTouchPlay(final IHdmiControlCallback callback) { 774 enforceAccessPermission(); 775 runOnServiceThread(new Runnable() { 776 @Override 777 public void run() { 778 HdmiControlService.this.oneTouchPlay(callback); 779 } 780 }); 781 } 782 783 @Override 784 public void queryDisplayStatus(final IHdmiControlCallback callback) { 785 enforceAccessPermission(); 786 runOnServiceThread(new Runnable() { 787 @Override 788 public void run() { 789 HdmiControlService.this.queryDisplayStatus(callback); 790 } 791 }); 792 } 793 794 @Override 795 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 796 enforceAccessPermission(); 797 runOnServiceThread(new Runnable() { 798 @Override 799 public void run() { 800 HdmiControlService.this.addHotplugEventListener(listener); 801 } 802 }); 803 } 804 805 @Override 806 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) { 807 enforceAccessPermission(); 808 runOnServiceThread(new Runnable() { 809 @Override 810 public void run() { 811 HdmiControlService.this.removeHotplugEventListener(listener); 812 } 813 }); 814 } 815 } 816 817 private void oneTouchPlay(IHdmiControlCallback callback) { 818 if (hasAction(OneTouchPlayAction.class)) { 819 Slog.w(TAG, "oneTouchPlay already in progress"); 820 invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS); 821 return; 822 } 823 HdmiCecLocalDevice source = mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK); 824 if (source == null) { 825 Slog.w(TAG, "Local playback device not available"); 826 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 827 return; 828 } 829 // TODO: Consider the case of multiple TV sets. For now we always direct the command 830 // to the primary one. 831 OneTouchPlayAction action = OneTouchPlayAction.create(this, 832 source.getDeviceInfo().getLogicalAddress(), 833 source.getDeviceInfo().getPhysicalAddress(), HdmiCec.ADDR_TV, callback); 834 if (action == null) { 835 Slog.w(TAG, "Cannot initiate oneTouchPlay"); 836 invokeCallback(callback, HdmiCec.RESULT_EXCEPTION); 837 return; 838 } 839 addAndStartAction(action); 840 } 841 842 private void queryDisplayStatus(IHdmiControlCallback callback) { 843 if (hasAction(DevicePowerStatusAction.class)) { 844 Slog.w(TAG, "queryDisplayStatus already in progress"); 845 invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS); 846 return; 847 } 848 HdmiCecLocalDevice source = mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK); 849 if (source == null) { 850 Slog.w(TAG, "Local playback device not available"); 851 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 852 return; 853 } 854 DevicePowerStatusAction action = DevicePowerStatusAction.create(this, 855 source.getDeviceInfo().getLogicalAddress(), HdmiCec.ADDR_TV, callback); 856 if (action == null) { 857 Slog.w(TAG, "Cannot initiate queryDisplayStatus"); 858 invokeCallback(callback, HdmiCec.RESULT_EXCEPTION); 859 return; 860 } 861 addAndStartAction(action); 862 } 863 864 private void addHotplugEventListener(IHdmiHotplugEventListener listener) { 865 HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); 866 try { 867 listener.asBinder().linkToDeath(record, 0); 868 } catch (RemoteException e) { 869 Slog.w(TAG, "Listener already died"); 870 return; 871 } 872 synchronized (mLock) { 873 mHotplugEventListenerRecords.add(record); 874 mHotplugEventListeners.add(listener); 875 } 876 } 877 878 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) { 879 synchronized (mLock) { 880 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 881 if (record.mListener.asBinder() == listener.asBinder()) { 882 listener.asBinder().unlinkToDeath(record, 0); 883 mHotplugEventListenerRecords.remove(record); 884 break; 885 } 886 } 887 mHotplugEventListeners.remove(listener); 888 } 889 } 890 891 private void invokeCallback(IHdmiControlCallback callback, int result) { 892 try { 893 callback.onComplete(result); 894 } catch (RemoteException e) { 895 Slog.e(TAG, "Invoking callback failed:" + e); 896 } 897 } 898 899 HdmiCecDeviceInfo getAvrDeviceInfo() { 900 return mCecController.getDeviceInfo(HdmiCec.ADDR_AUDIO_SYSTEM); 901 } 902 903 void setAudioStatus(boolean mute, int volume) { 904 // TODO: Hook up with AudioManager. 905 } 906 907 boolean isInPresetInstallationMode() { 908 synchronized (mLock) { 909 return !mInputChangeEnabled; 910 } 911 } 912 913 /** 914 * Called when a device is removed or removal of device is detected. 915 * 916 * @param address a logical address of a device to be removed 917 */ 918 void removeCecDevice(int address) { 919 mCecController.removeDeviceInfo(address); 920 mCecMessageCache.flushMessagesFrom(address); 921 } 922 923 HdmiCecMessageCache getCecMessageCache() { 924 return mCecMessageCache; 925 } 926} 927