HdmiControlService.java revision 0340bbc89f8162f9c2a298c98b03bfcdd1bc6e87
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. 142 @GuardedBy("mLock") 143 private int mActiveRoutingPath; 144 145 // Set to true while the service is in normal mode. While set to false, no input change is 146 // allowed. Used for situations where input change can confuse users such as channel auto-scan, 147 // system upgrade, etc., a.k.a. "prohibit mode". 148 @GuardedBy("mLock") 149 private boolean mInputChangeEnabled; 150 151 @GuardedBy("mLock") 152 // Whether ARC is "enabled" or not. 153 // TODO: it may need to hold lock if it's accessed from others. 154 private boolean mArcStatusEnabled = false; 155 156 @GuardedBy("mLock") 157 // Whether SystemAudioMode is "On" or not. 158 private boolean mSystemAudioMode; 159 160 public HdmiControlService(Context context) { 161 super(context); 162 mLocalDevices = HdmiUtils.asImmutableList(getContext().getResources().getIntArray( 163 com.android.internal.R.array.config_hdmiCecLogicalDeviceType)); 164 // TODO: Get control flag from persistent storage 165 mInputChangeEnabled = true; 166 } 167 168 @Override 169 public void onStart() { 170 mIoThread.start(); 171 mCecController = HdmiCecController.create(this); 172 173 if (mCecController != null) { 174 initializeLocalDevices(mLocalDevices); 175 } else { 176 Slog.i(TAG, "Device does not support HDMI-CEC."); 177 } 178 179 mMhlController = HdmiMhlController.create(this); 180 if (mMhlController == null) { 181 Slog.i(TAG, "Device does not support MHL-control."); 182 } 183 mPortInfo = initPortInfo(); 184 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); 185 186 // TODO: Read the preference for SystemAudioMode and initialize mSystemAudioMode and 187 // start to monitor the preference value and invoke SystemAudioActionFromTv if needed. 188 } 189 190 private void initializeLocalDevices(final List<Integer> deviceTypes) { 191 // A container for [Logical Address, Local device info]. 192 final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>(); 193 final SparseIntArray finished = new SparseIntArray(); 194 for (int type : deviceTypes) { 195 final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type); 196 localDevice.init(); 197 mCecController.allocateLogicalAddress(type, 198 localDevice.getPreferredAddress(), new AllocateAddressCallback() { 199 @Override 200 public void onAllocated(int deviceType, int logicalAddress) { 201 if (logicalAddress == HdmiCec.ADDR_UNREGISTERED) { 202 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]"); 203 } else { 204 HdmiCecDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType); 205 localDevice.setDeviceInfo(deviceInfo); 206 mCecController.addLocalDevice(deviceType, localDevice); 207 mCecController.addLogicalAddress(logicalAddress); 208 devices.append(logicalAddress, localDevice); 209 } 210 finished.append(deviceType, logicalAddress); 211 212 // Once finish address allocation for all devices, notify 213 // it to each device. 214 if (deviceTypes.size() == finished.size()) { 215 notifyAddressAllocated(devices); 216 } 217 } 218 }); 219 } 220 } 221 222 private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices) { 223 for (int i = 0; i < devices.size(); ++i) { 224 int address = devices.keyAt(i); 225 HdmiCecLocalDevice device = devices.valueAt(i); 226 device.onAddressAllocated(address); 227 } 228 } 229 230 // Initialize HDMI port information. Combine the information from CEC and MHL HAL and 231 // keep them in one place. 232 private List<HdmiPortInfo> initPortInfo() { 233 HdmiPortInfo[] cecPortInfo = null; 234 235 // CEC HAL provides majority of the info while MHL does only MHL support flag for 236 // each port. Return empty array if CEC HAL didn't provide the info. 237 if (mCecController != null) { 238 cecPortInfo = mCecController.getPortInfos(); 239 } 240 if (cecPortInfo == null) { 241 return Collections.emptyList(); 242 } 243 244 HdmiPortInfo[] mhlPortInfo = new HdmiPortInfo[0]; 245 if (mMhlController != null) { 246 // TODO: Implement plumbing logic to get MHL port information. 247 // mhlPortInfo = mMhlController.getPortInfos(); 248 } 249 250 // Use the id (port number) to find the matched info between CEC and MHL to combine them 251 // into one. Leave the field `mhlSupported` to false if matched MHL entry is not found. 252 ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length); 253 for (int i = 0; i < cecPortInfo.length; ++i) { 254 HdmiPortInfo cec = cecPortInfo[i]; 255 int id = cec.getId(); 256 boolean mhlInfoFound = false; 257 for (HdmiPortInfo mhl : mhlPortInfo) { 258 if (id == mhl.getId()) { 259 result.add(new HdmiPortInfo(id, cec.getType(), cec.getAddress(), 260 cec.isCecSupported(), mhl.isMhlSupported(), cec.isArcSupported())); 261 mhlInfoFound = true; 262 break; 263 } 264 } 265 if (!mhlInfoFound) { 266 result.add(cec); 267 } 268 } 269 270 return Collections.unmodifiableList(result); 271 } 272 273 /** 274 * Returns HDMI port information for the given port id. 275 * 276 * @param portId HDMI port id 277 * @return {@link HdmiPortInfo} for the given port 278 */ 279 HdmiPortInfo getPortInfo(int portId) { 280 // mPortInfo is an unmodifiable list and the only reference to its inner list. 281 // No lock is necessary. 282 for (HdmiPortInfo info : mPortInfo) { 283 if (portId == info.getId()) { 284 return info; 285 } 286 } 287 return null; 288 } 289 290 /** 291 * Returns {@link Looper} for IO operation. 292 * 293 * <p>Declared as package-private. 294 */ 295 Looper getIoLooper() { 296 return mIoThread.getLooper(); 297 } 298 299 /** 300 * Returns {@link Looper} of main thread. Use this {@link Looper} instance 301 * for tasks that are running on main service thread. 302 * 303 * <p>Declared as package-private. 304 */ 305 Looper getServiceLooper() { 306 return mHandler.getLooper(); 307 } 308 309 int getActiveSource() { 310 synchronized (mLock) { 311 return mActiveSource; 312 } 313 } 314 315 int getActivePath() { 316 synchronized (mLock) { 317 return mActiveRoutingPath; 318 } 319 } 320 321 /** 322 * Returns the path (physical address) of the device at the top of the currently active 323 * routing path. Used to get the corresponding port address of the HDMI input of the TV. 324 */ 325 int getActiveInput() { 326 synchronized (mLock) { 327 return mActiveRoutingPath & HdmiConstants.ROUTING_PATH_TOP_MASK; 328 } 329 } 330 331 void updateActiveDevice(int logicalAddress, int physicalAddress) { 332 synchronized (mLock) { 333 mActiveSource = logicalAddress; 334 mActiveRoutingPath = physicalAddress; 335 } 336 } 337 338 void setInputChangeEnabled(boolean enabled) { 339 synchronized (mLock) { 340 mInputChangeEnabled = enabled; 341 } 342 } 343 344 /** 345 * Returns physical address of the device. 346 */ 347 int getPhysicalAddress() { 348 return mCecController.getPhysicalAddress(); 349 } 350 351 /** 352 * Returns vendor id of CEC service. 353 */ 354 int getVendorId() { 355 return mCecController.getVendorId(); 356 } 357 358 HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) { 359 assertRunOnServiceThread(); 360 return mCecController.getDeviceInfo(logicalAddress); 361 } 362 363 /** 364 * Returns version of CEC. 365 */ 366 int getCecVersion() { 367 return mCecController.getVersion(); 368 } 369 370 /** 371 * Returns a list of {@link HdmiCecDeviceInfo}. 372 * 373 * @param includeLocalDevice whether to include local devices 374 */ 375 List<HdmiCecDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) { 376 assertRunOnServiceThread(); 377 return mCecController.getDeviceInfoList(includeLocalDevice); 378 } 379 380 /** 381 * Returns the {@link HdmiCecDeviceInfo} instance whose physical address matches 382 * the given routing path. CEC devices use routing path for its physical address to 383 * describe the hierarchy of the devices in the network. 384 * 385 * @param path routing path or physical address 386 * @return {@link HdmiCecDeviceInfo} if the matched info is found; otherwise null 387 */ 388 HdmiCecDeviceInfo getDeviceInfoByPath(int path) { 389 assertRunOnServiceThread(); 390 for (HdmiCecDeviceInfo info : mCecController.getDeviceInfoList(false)) { 391 if (info.getPhysicalAddress() == path) { 392 return info; 393 } 394 } 395 return null; 396 } 397 398 /** 399 * Add and start a new {@link FeatureAction} to the action queue. 400 * 401 * @param action {@link FeatureAction} to add and start 402 */ 403 void addAndStartAction(final FeatureAction action) { 404 // TODO: may need to check the number of stale actions. 405 runOnServiceThread(new Runnable() { 406 @Override 407 public void run() { 408 mActions.add(action); 409 action.start(); 410 } 411 }); 412 } 413 414 void setSystemAudioMode(boolean on) { 415 synchronized (mLock) { 416 mSystemAudioMode = on; 417 } 418 } 419 420 boolean getSystemAudioMode() { 421 synchronized (mLock) { 422 return mSystemAudioMode; 423 } 424 } 425 426 // See if we have an action of a given type in progress. 427 <T extends FeatureAction> boolean hasAction(final Class<T> clazz) { 428 for (FeatureAction action : mActions) { 429 if (action.getClass().equals(clazz)) { 430 return true; 431 } 432 } 433 return false; 434 } 435 436 /** 437 * Remove the given {@link FeatureAction} object from the action queue. 438 * 439 * @param action {@link FeatureAction} to remove 440 */ 441 void removeAction(final FeatureAction action) { 442 assertRunOnServiceThread(); 443 mActions.remove(action); 444 } 445 446 // Remove all actions matched with the given Class type. 447 <T extends FeatureAction> void removeAction(final Class<T> clazz) { 448 removeActionExcept(clazz, null); 449 } 450 451 // Remove all actions matched with the given Class type besides |exception|. 452 <T extends FeatureAction> void removeActionExcept(final Class<T> clazz, 453 final FeatureAction exception) { 454 assertRunOnServiceThread(); 455 Iterator<FeatureAction> iter = mActions.iterator(); 456 while (iter.hasNext()) { 457 FeatureAction action = iter.next(); 458 if (action != exception && action.getClass().equals(clazz)) { 459 action.clear(); 460 mActions.remove(action); 461 } 462 } 463 } 464 465 private void runOnServiceThread(Runnable runnable) { 466 mHandler.post(runnable); 467 } 468 469 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) { 470 mHandler.postAtFrontOfQueue(runnable); 471 } 472 473 private void assertRunOnServiceThread() { 474 if (Looper.myLooper() != mHandler.getLooper()) { 475 throw new IllegalStateException("Should run on service thread."); 476 } 477 } 478 479 /** 480 * Change ARC status into the given {@code enabled} status. 481 * 482 * @return {@code true} if ARC was in "Enabled" status 483 */ 484 boolean setArcStatus(boolean enabled) { 485 assertRunOnServiceThread(); 486 synchronized (mLock) { 487 boolean oldStatus = mArcStatusEnabled; 488 // 1. Enable/disable ARC circuit. 489 mCecController.setAudioReturnChannel(enabled); 490 491 // TODO: notify arc mode change to AudioManager. 492 493 // 2. Update arc status; 494 mArcStatusEnabled = enabled; 495 return oldStatus; 496 } 497 } 498 499 /** 500 * Transmit a CEC command to CEC bus. 501 * 502 * @param command CEC command to send out 503 * @param callback interface used to the result of send command 504 */ 505 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) { 506 mCecController.sendCommand(command, callback); 507 } 508 509 void sendCecCommand(HdmiCecMessage command) { 510 mCecController.sendCommand(command, null); 511 } 512 513 boolean handleCecCommand(HdmiCecMessage message) { 514 // Cache incoming message. Note that it caches only white-listed one. 515 mCecMessageCache.cacheMessage(message); 516 517 // Commands that queries system information replies directly instead 518 // of creating FeatureAction because they are state-less. 519 // TODO: move the leftover message to local device. 520 switch (message.getOpcode()) { 521 case HdmiCec.MESSAGE_INITIATE_ARC: 522 handleInitiateArc(message); 523 return true; 524 case HdmiCec.MESSAGE_TERMINATE_ARC: 525 handleTerminateArc(message); 526 return true; 527 case HdmiCec.MESSAGE_SET_SYSTEM_AUDIO_MODE: 528 handleSetSystemAudioMode(message); 529 return true; 530 case HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_STATUS: 531 handleSystemAudioModeStatus(message); 532 return true; 533 default: 534 if (dispatchMessageToAction(message)) { 535 return true; 536 } 537 break; 538 } 539 540 return dispatchMessageToLocalDevice(message); 541 } 542 543 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) { 544 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 545 if (device.dispatchMessage(message)) { 546 return true; 547 } 548 } 549 return false; 550 } 551 552 /** 553 * Called when a new hotplug event is issued. 554 * 555 * @param portNo hdmi port number where hot plug event issued. 556 * @param connected whether to be plugged in or not 557 */ 558 void onHotplug(int portNo, boolean connected) { 559 // TODO: Start "RequestArcInitiationAction" if ARC port. 560 } 561 562 /** 563 * Poll all remote devices. It sends <Polling Message> to all remote 564 * devices. 565 * 566 * @param callback an interface used to get a list of all remote devices' address 567 * @param pickStrategy strategy how to pick polling candidates 568 * @param retryCount the number of retry used to send polling message to remote devices 569 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value 570 */ 571 void pollDevices(DevicePollingCallback callback, int pickStrategy, int retryCount) { 572 mCecController.pollDevices(callback, checkPollStrategy(pickStrategy), retryCount); 573 } 574 575 private int checkPollStrategy(int pickStrategy) { 576 int strategy = pickStrategy & POLL_STRATEGY_MASK; 577 if (strategy == 0) { 578 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy); 579 } 580 int iterationStrategy = pickStrategy & POLL_ITERATION_STRATEGY_MASK; 581 if (iterationStrategy == 0) { 582 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy); 583 } 584 return strategy | iterationStrategy; 585 } 586 587 /** 588 * Launch device discovery sequence. It starts with clearing the existing device info list. 589 * Note that it assumes that logical address of all local devices is already allocated. 590 * 591 * @param sourceAddress a logical address of tv 592 */ 593 void launchDeviceDiscovery(final int sourceAddress) { 594 // At first, clear all existing device infos. 595 mCecController.clearDeviceInfoList(); 596 // TODO: flush cec message cache when CEC is turned off. 597 598 DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, sourceAddress, 599 new DeviceDiscoveryCallback() { 600 @Override 601 public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) { 602 for (HdmiCecDeviceInfo info : deviceInfos) { 603 addCecDevice(info); 604 } 605 606 // Add device info of all local devices. 607 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 608 addCecDevice(device.getDeviceInfo()); 609 } 610 611 addAndStartAction(new HotplugDetectionAction(HdmiControlService.this, 612 sourceAddress)); 613 } 614 }); 615 addAndStartAction(action); 616 } 617 618 private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) { 619 // TODO: find better name instead of model name. 620 String displayName = Build.MODEL; 621 return new HdmiCecDeviceInfo(logicalAddress, 622 getPhysicalAddress(), deviceType, getVendorId(), displayName); 623 } 624 625 private void handleInitiateArc(HdmiCecMessage message){ 626 // In case where <Initiate Arc> is started by <Request ARC Initiation> 627 // need to clean up RequestArcInitiationAction. 628 removeAction(RequestArcInitiationAction.class); 629 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 630 message.getDestination(), message.getSource(), true); 631 addAndStartAction(action); 632 } 633 634 private void handleTerminateArc(HdmiCecMessage message) { 635 // In case where <Terminate Arc> is started by <Request ARC Termination> 636 // need to clean up RequestArcInitiationAction. 637 // TODO: check conditions of power status by calling is_connected api 638 // to be added soon. 639 removeAction(RequestArcTerminationAction.class); 640 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 641 message.getDestination(), message.getSource(), false); 642 addAndStartAction(action); 643 } 644 645 private boolean dispatchMessageToAction(HdmiCecMessage message) { 646 for (FeatureAction action : mActions) { 647 if (action.processCommand(message)) { 648 return true; 649 } 650 } 651 Slog.w(TAG, "Unsupported cec command:" + message); 652 return false; 653 } 654 655 private void handleSetSystemAudioMode(HdmiCecMessage message) { 656 if (dispatchMessageToAction(message) || !isMessageForSystemAudio(message)) { 657 return; 658 } 659 SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this, 660 message.getDestination(), message.getSource(), 661 HdmiUtils.parseCommandParamSystemAudioStatus(message)); 662 addAndStartAction(action); 663 } 664 665 private void handleSystemAudioModeStatus(HdmiCecMessage message) { 666 if (!isMessageForSystemAudio(message)) { 667 return; 668 } 669 setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message)); 670 } 671 672 private boolean isMessageForSystemAudio(HdmiCecMessage message) { 673 if (message.getSource() != HdmiCec.ADDR_AUDIO_SYSTEM 674 || message.getDestination() != HdmiCec.ADDR_TV 675 || getAvrDeviceInfo() == null) { 676 Slog.w(TAG, "Skip abnormal CecMessage: " + message); 677 return false; 678 } 679 return true; 680 } 681 682 // Record class that monitors the event of the caller of being killed. Used to clean up 683 // the listener list and record list accordingly. 684 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { 685 private final IHdmiHotplugEventListener mListener; 686 687 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) { 688 mListener = listener; 689 } 690 691 @Override 692 public void binderDied() { 693 synchronized (mLock) { 694 mHotplugEventListenerRecords.remove(this); 695 mHotplugEventListeners.remove(mListener); 696 } 697 } 698 } 699 700 void addCecDevice(HdmiCecDeviceInfo info) { 701 mCecController.addDeviceInfo(info); 702 } 703 704 private void enforceAccessPermission() { 705 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); 706 } 707 708 private final class BinderService extends IHdmiControlService.Stub { 709 @Override 710 public int[] getSupportedTypes() { 711 enforceAccessPermission(); 712 // mLocalDevices is an unmodifiable list - no lock necesary. 713 int[] localDevices = new int[mLocalDevices.size()]; 714 for (int i = 0; i < localDevices.length; ++i) { 715 localDevices[i] = mLocalDevices.get(i); 716 } 717 return localDevices; 718 } 719 720 @Override 721 public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) { 722 enforceAccessPermission(); 723 runOnServiceThread(new Runnable() { 724 @Override 725 public void run() { 726 HdmiCecLocalDeviceTv tv = 727 (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiCec.DEVICE_TV); 728 if (tv == null) { 729 Slog.w(TAG, "Local playback device not available"); 730 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 731 return; 732 } 733 tv.deviceSelect(logicalAddress, callback); 734 } 735 }); 736 } 737 738 739 @Override 740 public void oneTouchPlay(final IHdmiControlCallback callback) { 741 enforceAccessPermission(); 742 runOnServiceThread(new Runnable() { 743 @Override 744 public void run() { 745 HdmiControlService.this.oneTouchPlay(callback); 746 } 747 }); 748 } 749 750 @Override 751 public void queryDisplayStatus(final IHdmiControlCallback callback) { 752 enforceAccessPermission(); 753 runOnServiceThread(new Runnable() { 754 @Override 755 public void run() { 756 HdmiControlService.this.queryDisplayStatus(callback); 757 } 758 }); 759 } 760 761 @Override 762 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 763 enforceAccessPermission(); 764 runOnServiceThread(new Runnable() { 765 @Override 766 public void run() { 767 HdmiControlService.this.addHotplugEventListener(listener); 768 } 769 }); 770 } 771 772 @Override 773 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) { 774 enforceAccessPermission(); 775 runOnServiceThread(new Runnable() { 776 @Override 777 public void run() { 778 HdmiControlService.this.removeHotplugEventListener(listener); 779 } 780 }); 781 } 782 } 783 784 private void oneTouchPlay(IHdmiControlCallback callback) { 785 if (hasAction(OneTouchPlayAction.class)) { 786 Slog.w(TAG, "oneTouchPlay already in progress"); 787 invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS); 788 return; 789 } 790 HdmiCecLocalDevice source = mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK); 791 if (source == null) { 792 Slog.w(TAG, "Local playback device not available"); 793 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 794 return; 795 } 796 // TODO: Consider the case of multiple TV sets. For now we always direct the command 797 // to the primary one. 798 OneTouchPlayAction action = OneTouchPlayAction.create(this, 799 source.getDeviceInfo().getLogicalAddress(), 800 source.getDeviceInfo().getPhysicalAddress(), HdmiCec.ADDR_TV, callback); 801 if (action == null) { 802 Slog.w(TAG, "Cannot initiate oneTouchPlay"); 803 invokeCallback(callback, HdmiCec.RESULT_EXCEPTION); 804 return; 805 } 806 addAndStartAction(action); 807 } 808 809 private void queryDisplayStatus(IHdmiControlCallback callback) { 810 if (hasAction(DevicePowerStatusAction.class)) { 811 Slog.w(TAG, "queryDisplayStatus already in progress"); 812 invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS); 813 return; 814 } 815 HdmiCecLocalDevice source = mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK); 816 if (source == null) { 817 Slog.w(TAG, "Local playback device not available"); 818 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 819 return; 820 } 821 DevicePowerStatusAction action = DevicePowerStatusAction.create(this, 822 source.getDeviceInfo().getLogicalAddress(), HdmiCec.ADDR_TV, callback); 823 if (action == null) { 824 Slog.w(TAG, "Cannot initiate queryDisplayStatus"); 825 invokeCallback(callback, HdmiCec.RESULT_EXCEPTION); 826 return; 827 } 828 addAndStartAction(action); 829 } 830 831 private void addHotplugEventListener(IHdmiHotplugEventListener listener) { 832 HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); 833 try { 834 listener.asBinder().linkToDeath(record, 0); 835 } catch (RemoteException e) { 836 Slog.w(TAG, "Listener already died"); 837 return; 838 } 839 synchronized (mLock) { 840 mHotplugEventListenerRecords.add(record); 841 mHotplugEventListeners.add(listener); 842 } 843 } 844 845 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) { 846 synchronized (mLock) { 847 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 848 if (record.mListener.asBinder() == listener.asBinder()) { 849 listener.asBinder().unlinkToDeath(record, 0); 850 mHotplugEventListenerRecords.remove(record); 851 break; 852 } 853 } 854 mHotplugEventListeners.remove(listener); 855 } 856 } 857 858 private void invokeCallback(IHdmiControlCallback callback, int result) { 859 try { 860 callback.onComplete(result); 861 } catch (RemoteException e) { 862 Slog.e(TAG, "Invoking callback failed:" + e); 863 } 864 } 865 866 HdmiCecDeviceInfo getAvrDeviceInfo() { 867 return mCecController.getDeviceInfo(HdmiCec.ADDR_AUDIO_SYSTEM); 868 } 869 870 void setAudioStatus(boolean mute, int volume) { 871 // TODO: Hook up with AudioManager. 872 } 873 874 boolean isInPresetInstallationMode() { 875 synchronized (mLock) { 876 return !mInputChangeEnabled; 877 } 878 } 879 880 /** 881 * Called when a device is removed or removal of device is detected. 882 * 883 * @param address a logical address of a device to be removed 884 */ 885 void removeCecDevice(int address) { 886 mCecController.removeDeviceInfo(address); 887 mCecMessageCache.flushMessagesFrom(address); 888 } 889 890 HdmiCecMessageCache getCecMessageCache() { 891 return mCecMessageCache; 892 } 893} 894