HdmiControlService.java revision 5344cd98e69f92e70d52969b1851c9d8f9e81853
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.BroadcastReceiver; 21import android.content.Context; 22import android.content.Intent; 23import android.content.IntentFilter; 24import android.hardware.hdmi.HdmiCec; 25import android.hardware.hdmi.HdmiCecDeviceInfo; 26import android.hardware.hdmi.HdmiCecMessage; 27import android.hardware.hdmi.HdmiHotplugEvent; 28import android.hardware.hdmi.HdmiPortInfo; 29import android.hardware.hdmi.IHdmiControlCallback; 30import android.hardware.hdmi.IHdmiControlService; 31import android.hardware.hdmi.IHdmiDeviceEventListener; 32import android.hardware.hdmi.IHdmiHotplugEventListener; 33import android.hardware.hdmi.IHdmiInputChangeListener; 34import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener; 35import android.media.AudioManager; 36import android.os.Build; 37import android.os.Handler; 38import android.os.HandlerThread; 39import android.os.IBinder; 40import android.os.Looper; 41import android.os.PowerManager; 42import android.os.RemoteException; 43import android.os.SystemClock; 44import android.util.Slog; 45import android.util.SparseArray; 46import android.util.SparseIntArray; 47 48import com.android.internal.annotations.GuardedBy; 49import com.android.server.SystemService; 50import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; 51import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; 52 53import java.util.ArrayList; 54import java.util.Collections; 55import java.util.List; 56 57/** 58 * Provides a service for sending and processing HDMI control messages, 59 * HDMI-CEC and MHL control command, and providing the information on both standard. 60 */ 61public final class HdmiControlService extends SystemService { 62 private static final String TAG = "HdmiControlService"; 63 64 // TODO: Rename the permission to HDMI_CONTROL. 65 private static final String PERMISSION = "android.permission.HDMI_CEC"; 66 67 /** 68 * Interface to report send result. 69 */ 70 interface SendMessageCallback { 71 /** 72 * Called when {@link HdmiControlService#sendCecCommand} is completed. 73 * 74 * @param error result of send request. 75 * @see {@link #SEND_RESULT_SUCCESS} 76 * @see {@link #SEND_RESULT_NAK} 77 * @see {@link #SEND_RESULT_FAILURE} 78 */ 79 void onSendCompleted(int error); 80 } 81 82 /** 83 * Interface to get a list of available logical devices. 84 */ 85 interface DevicePollingCallback { 86 /** 87 * Called when device polling is finished. 88 * 89 * @param ackedAddress a list of logical addresses of available devices 90 */ 91 void onPollingFinished(List<Integer> ackedAddress); 92 } 93 94 private class PowerStateReceiver extends BroadcastReceiver { 95 @Override 96 public void onReceive(Context context, Intent intent) { 97 switch (intent.getAction()) { 98 case Intent.ACTION_SCREEN_OFF: 99 if (isPowerOnOrTransient()) { 100 onStandby(); 101 } 102 break; 103 case Intent.ACTION_SCREEN_ON: 104 if (isPowerStandbyOrTransient()) { 105 onWakeUp(); 106 } 107 break; 108 } 109 } 110 } 111 112 // A thread to handle synchronous IO of CEC and MHL control service. 113 // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms) 114 // and sparse call it shares a thread to handle IO operations. 115 private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread"); 116 117 // Used to synchronize the access to the service. 118 private final Object mLock = new Object(); 119 120 // Type of logical devices hosted in the system. Stored in the unmodifiable list. 121 private final List<Integer> mLocalDevices; 122 123 // List of listeners registered by callers that want to get notified of 124 // hotplug events. 125 @GuardedBy("mLock") 126 private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>(); 127 128 // List of records for hotplug event listener to handle the the caller killed in action. 129 @GuardedBy("mLock") 130 private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords = 131 new ArrayList<>(); 132 133 // List of listeners registered by callers that want to get notified of 134 // device status events. 135 @GuardedBy("mLock") 136 private final ArrayList<IHdmiDeviceEventListener> mDeviceEventListeners = new ArrayList<>(); 137 138 // List of records for device event listener to handle the the caller killed in action. 139 @GuardedBy("mLock") 140 private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords = 141 new ArrayList<>(); 142 143 @GuardedBy("mLock") 144 private IHdmiInputChangeListener mInputChangeListener; 145 146 @GuardedBy("mLock") 147 private InputChangeListenerRecord mInputChangeListenerRecord; 148 149 // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol 150 // handling will be disabled and no request will be handled. 151 @GuardedBy("mLock") 152 private boolean mHdmiControlEnabled; 153 154 // Set to true while the service is in normal mode. While set to false, no input change is 155 // allowed. Used for situations where input change can confuse users such as channel auto-scan, 156 // system upgrade, etc., a.k.a. "prohibit mode". 157 @GuardedBy("mLock") 158 private boolean mProhibitMode; 159 160 // List of listeners registered by callers that want to get notified of 161 // system audio mode changes. 162 private final ArrayList<IHdmiSystemAudioModeChangeListener> 163 mSystemAudioModeChangeListeners = new ArrayList<>(); 164 // List of records for system audio mode change to handle the the caller killed in action. 165 private final ArrayList<SystemAudioModeChangeListenerRecord> 166 mSystemAudioModeChangeListenerRecords = new ArrayList<>(); 167 168 // Handler used to run a task in service thread. 169 private final Handler mHandler = new Handler(); 170 171 @Nullable 172 private HdmiCecController mCecController; 173 174 @Nullable 175 private HdmiMhlController mMhlController; 176 177 // HDMI port information. Stored in the unmodifiable list to keep the static information 178 // from being modified. 179 private List<HdmiPortInfo> mPortInfo; 180 181 private final PowerStateReceiver mPowerStateReceiver = new PowerStateReceiver(); 182 183 @ServiceThreadOnly 184 private int mPowerStatus = HdmiCec.POWER_STATUS_STANDBY; 185 186 @ServiceThreadOnly 187 private boolean mStandbyMessageReceived = false; 188 189 public HdmiControlService(Context context) { 190 super(context); 191 mLocalDevices = HdmiUtils.asImmutableList(getContext().getResources().getIntArray( 192 com.android.internal.R.array.config_hdmiCecLogicalDeviceType)); 193 } 194 195 @Override 196 public void onStart() { 197 mIoThread.start(); 198 mPowerStatus = HdmiCec.POWER_STATUS_TRANSIENT_TO_ON; 199 mCecController = HdmiCecController.create(this); 200 201 if (mCecController != null) { 202 mCecController.setOption(HdmiCec.OPTION_CEC_SERVICE_CONTROL, HdmiCec.DISABLED); 203 initializeLocalDevices(mLocalDevices); 204 } else { 205 Slog.i(TAG, "Device does not support HDMI-CEC."); 206 } 207 208 mMhlController = HdmiMhlController.create(this); 209 if (mMhlController == null) { 210 Slog.i(TAG, "Device does not support MHL-control."); 211 } 212 mPortInfo = initPortInfo(); 213 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); 214 215 // Register broadcast receiver for power state change. 216 if (mCecController != null || mMhlController != null) { 217 IntentFilter filter = new IntentFilter(); 218 filter.addAction(Intent.ACTION_SCREEN_OFF); 219 filter.addAction(Intent.ACTION_SCREEN_ON); 220 getContext().registerReceiver(mPowerStateReceiver, filter); 221 } 222 223 // TODO: Read the preference for SystemAudioMode and initialize mSystemAudioMode and 224 // start to monitor the preference value and invoke SystemAudioActionFromTv if needed. 225 mHdmiControlEnabled = true; 226 // TODO: Get control flag from persistent storage 227 mProhibitMode = false; 228 } 229 230 @ServiceThreadOnly 231 private void initializeLocalDevices(final List<Integer> deviceTypes) { 232 assertRunOnServiceThread(); 233 // A container for [Logical Address, Local device info]. 234 final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>(); 235 final SparseIntArray finished = new SparseIntArray(); 236 mCecController.clearLogicalAddress(); 237 for (int type : deviceTypes) { 238 final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type); 239 localDevice.init(); 240 mCecController.allocateLogicalAddress(type, 241 localDevice.getPreferredAddress(), new AllocateAddressCallback() { 242 @Override 243 public void onAllocated(int deviceType, int logicalAddress) { 244 if (logicalAddress == HdmiCec.ADDR_UNREGISTERED) { 245 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]"); 246 } else { 247 HdmiCecDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType); 248 localDevice.setDeviceInfo(deviceInfo); 249 mCecController.addLocalDevice(deviceType, localDevice); 250 mCecController.addLogicalAddress(logicalAddress); 251 devices.append(logicalAddress, localDevice); 252 } 253 finished.append(deviceType, logicalAddress); 254 255 // Address allocation completed for all devices. Notify each device. 256 if (deviceTypes.size() == finished.size()) { 257 if (mPowerStatus == HdmiCec.POWER_STATUS_TRANSIENT_TO_ON) { 258 mPowerStatus = HdmiCec.POWER_STATUS_ON; 259 } 260 notifyAddressAllocated(devices); 261 } 262 } 263 }); 264 } 265 } 266 267 @ServiceThreadOnly 268 private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices) { 269 assertRunOnServiceThread(); 270 for (int i = 0; i < devices.size(); ++i) { 271 int address = devices.keyAt(i); 272 HdmiCecLocalDevice device = devices.valueAt(i); 273 device.handleAddressAllocated(address); 274 } 275 } 276 277 // Initialize HDMI port information. Combine the information from CEC and MHL HAL and 278 // keep them in one place. 279 @ServiceThreadOnly 280 private List<HdmiPortInfo> initPortInfo() { 281 assertRunOnServiceThread(); 282 HdmiPortInfo[] cecPortInfo = null; 283 284 // CEC HAL provides majority of the info while MHL does only MHL support flag for 285 // each port. Return empty array if CEC HAL didn't provide the info. 286 if (mCecController != null) { 287 cecPortInfo = mCecController.getPortInfos(); 288 } 289 if (cecPortInfo == null) { 290 return Collections.emptyList(); 291 } 292 293 HdmiPortInfo[] mhlPortInfo = new HdmiPortInfo[0]; 294 if (mMhlController != null) { 295 // TODO: Implement plumbing logic to get MHL port information. 296 // mhlPortInfo = mMhlController.getPortInfos(); 297 } 298 299 // Use the id (port number) to find the matched info between CEC and MHL to combine them 300 // into one. Leave the field `mhlSupported` to false if matched MHL entry is not found. 301 ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length); 302 for (int i = 0; i < cecPortInfo.length; ++i) { 303 HdmiPortInfo cec = cecPortInfo[i]; 304 int id = cec.getId(); 305 boolean mhlInfoFound = false; 306 for (HdmiPortInfo mhl : mhlPortInfo) { 307 if (id == mhl.getId()) { 308 result.add(new HdmiPortInfo(id, cec.getType(), cec.getAddress(), 309 cec.isCecSupported(), mhl.isMhlSupported(), cec.isArcSupported())); 310 mhlInfoFound = true; 311 break; 312 } 313 } 314 if (!mhlInfoFound) { 315 result.add(cec); 316 } 317 } 318 319 return Collections.unmodifiableList(result); 320 } 321 322 /** 323 * Returns HDMI port information for the given port id. 324 * 325 * @param portId HDMI port id 326 * @return {@link HdmiPortInfo} for the given port 327 */ 328 HdmiPortInfo getPortInfo(int portId) { 329 // mPortInfo is an unmodifiable list and the only reference to its inner list. 330 // No lock is necessary. 331 for (HdmiPortInfo info : mPortInfo) { 332 if (portId == info.getId()) { 333 return info; 334 } 335 } 336 return null; 337 } 338 339 /** 340 * Returns the routing path (physical address) of the HDMI port for the given 341 * port id. 342 */ 343 int portIdToPath(int portId) { 344 HdmiPortInfo portInfo = getPortInfo(portId); 345 if (portInfo == null) { 346 Slog.e(TAG, "Cannot find the port info: " + portId); 347 return HdmiConstants.INVALID_PHYSICAL_ADDRESS; 348 } 349 return portInfo.getAddress(); 350 } 351 352 /** 353 * Returns the id of HDMI port located at the top of the hierarchy of 354 * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance, 355 * the port id to be returned is the ID associated with the port address 356 * 0x1000 (1.0.0.0) which is the topmost path of the given routing path. 357 */ 358 int pathToPortId(int path) { 359 int portAddress = path & HdmiConstants.ROUTING_PATH_TOP_MASK; 360 for (HdmiPortInfo info : mPortInfo) { 361 if (portAddress == info.getAddress()) { 362 return info.getId(); 363 } 364 } 365 return HdmiConstants.INVALID_PORT_ID; 366 } 367 368 /** 369 * Returns {@link Looper} for IO operation. 370 * 371 * <p>Declared as package-private. 372 */ 373 Looper getIoLooper() { 374 return mIoThread.getLooper(); 375 } 376 377 /** 378 * Returns {@link Looper} of main thread. Use this {@link Looper} instance 379 * for tasks that are running on main service thread. 380 * 381 * <p>Declared as package-private. 382 */ 383 Looper getServiceLooper() { 384 return mHandler.getLooper(); 385 } 386 387 /** 388 * Returns physical address of the device. 389 */ 390 int getPhysicalAddress() { 391 return mCecController.getPhysicalAddress(); 392 } 393 394 /** 395 * Returns vendor id of CEC service. 396 */ 397 int getVendorId() { 398 return mCecController.getVendorId(); 399 } 400 401 @ServiceThreadOnly 402 HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) { 403 assertRunOnServiceThread(); 404 HdmiCecLocalDeviceTv tv = tv(); 405 if (tv == null) { 406 return null; 407 } 408 return tv.getDeviceInfo(logicalAddress); 409 } 410 411 /** 412 * Returns version of CEC. 413 */ 414 int getCecVersion() { 415 return mCecController.getVersion(); 416 } 417 418 /** 419 * Whether a device of the specified physical address is connected to ARC enabled port. 420 */ 421 boolean isConnectedToArcPort(int physicalAddress) { 422 for (HdmiPortInfo portInfo : mPortInfo) { 423 if (hasSameTopPort(portInfo.getAddress(), physicalAddress) 424 && portInfo.isArcSupported()) { 425 return true; 426 } 427 } 428 return false; 429 } 430 431 void runOnServiceThread(Runnable runnable) { 432 mHandler.post(runnable); 433 } 434 435 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) { 436 mHandler.postAtFrontOfQueue(runnable); 437 } 438 439 private void assertRunOnServiceThread() { 440 if (Looper.myLooper() != mHandler.getLooper()) { 441 throw new IllegalStateException("Should run on service thread."); 442 } 443 } 444 445 /** 446 * Transmit a CEC command to CEC bus. 447 * 448 * @param command CEC command to send out 449 * @param callback interface used to the result of send command 450 */ 451 @ServiceThreadOnly 452 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) { 453 assertRunOnServiceThread(); 454 mCecController.sendCommand(command, callback); 455 } 456 457 @ServiceThreadOnly 458 void sendCecCommand(HdmiCecMessage command) { 459 assertRunOnServiceThread(); 460 mCecController.sendCommand(command, null); 461 } 462 463 @ServiceThreadOnly 464 boolean handleCecCommand(HdmiCecMessage message) { 465 assertRunOnServiceThread(); 466 return dispatchMessageToLocalDevice(message); 467 } 468 469 void setAudioReturnChannel(boolean enabled) { 470 mCecController.setAudioReturnChannel(enabled); 471 } 472 473 @ServiceThreadOnly 474 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) { 475 assertRunOnServiceThread(); 476 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 477 if (device.dispatchMessage(message) 478 && message.getDestination() != HdmiCec.ADDR_BROADCAST) { 479 return true; 480 } 481 } 482 483 if (message.getDestination() != HdmiCec.ADDR_BROADCAST) { 484 Slog.w(TAG, "Unhandled cec command:" + message); 485 } 486 return false; 487 } 488 489 /** 490 * Called when a new hotplug event is issued. 491 * 492 * @param portNo hdmi port number where hot plug event issued. 493 * @param connected whether to be plugged in or not 494 */ 495 @ServiceThreadOnly 496 void onHotplug(int portNo, boolean connected) { 497 assertRunOnServiceThread(); 498 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 499 device.onHotplug(portNo, connected); 500 } 501 announceHotplugEvent(portNo, connected); 502 } 503 504 /** 505 * Poll all remote devices. It sends <Polling Message> to all remote 506 * devices. 507 * 508 * @param callback an interface used to get a list of all remote devices' address 509 * @param sourceAddress a logical address of source device where sends polling message 510 * @param pickStrategy strategy how to pick polling candidates 511 * @param retryCount the number of retry used to send polling message to remote devices 512 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value 513 */ 514 @ServiceThreadOnly 515 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, 516 int retryCount) { 517 assertRunOnServiceThread(); 518 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy), 519 retryCount); 520 } 521 522 private int checkPollStrategy(int pickStrategy) { 523 int strategy = pickStrategy & HdmiConstants.POLL_STRATEGY_MASK; 524 if (strategy == 0) { 525 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy); 526 } 527 int iterationStrategy = pickStrategy & HdmiConstants.POLL_ITERATION_STRATEGY_MASK; 528 if (iterationStrategy == 0) { 529 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy); 530 } 531 return strategy | iterationStrategy; 532 } 533 534 List<HdmiCecLocalDevice> getAllLocalDevices() { 535 assertRunOnServiceThread(); 536 return mCecController.getLocalDeviceList(); 537 } 538 539 Object getServiceLock() { 540 return mLock; 541 } 542 543 void setAudioStatus(boolean mute, int volume) { 544 // TODO: Hook up with AudioManager. 545 } 546 547 void announceSystemAudioModeChange(boolean enabled) { 548 for (IHdmiSystemAudioModeChangeListener listener : mSystemAudioModeChangeListeners) { 549 invokeSystemAudioModeChange(listener, enabled); 550 } 551 } 552 553 private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) { 554 // TODO: find better name instead of model name. 555 String displayName = Build.MODEL; 556 return new HdmiCecDeviceInfo(logicalAddress, 557 getPhysicalAddress(), deviceType, getVendorId(), displayName); 558 } 559 560 // Record class that monitors the event of the caller of being killed. Used to clean up 561 // the listener list and record list accordingly. 562 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { 563 private final IHdmiHotplugEventListener mListener; 564 565 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) { 566 mListener = listener; 567 } 568 569 @Override 570 public void binderDied() { 571 synchronized (mLock) { 572 mHotplugEventListenerRecords.remove(this); 573 mHotplugEventListeners.remove(mListener); 574 } 575 } 576 } 577 578 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient { 579 private final IHdmiDeviceEventListener mListener; 580 581 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) { 582 mListener = listener; 583 } 584 585 @Override 586 public void binderDied() { 587 synchronized (mLock) { 588 mDeviceEventListenerRecords.remove(this); 589 mDeviceEventListeners.remove(mListener); 590 } 591 } 592 } 593 594 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient { 595 private final IHdmiSystemAudioModeChangeListener mListener; 596 597 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) { 598 mListener = listener; 599 } 600 601 @Override 602 public void binderDied() { 603 synchronized (mLock) { 604 mSystemAudioModeChangeListenerRecords.remove(this); 605 mSystemAudioModeChangeListeners.remove(mListener); 606 } 607 } 608 } 609 610 private void enforceAccessPermission() { 611 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); 612 } 613 614 private final class BinderService extends IHdmiControlService.Stub { 615 @Override 616 public int[] getSupportedTypes() { 617 enforceAccessPermission(); 618 // mLocalDevices is an unmodifiable list - no lock necesary. 619 int[] localDevices = new int[mLocalDevices.size()]; 620 for (int i = 0; i < localDevices.length; ++i) { 621 localDevices[i] = mLocalDevices.get(i); 622 } 623 return localDevices; 624 } 625 626 @Override 627 public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) { 628 enforceAccessPermission(); 629 runOnServiceThread(new Runnable() { 630 @Override 631 public void run() { 632 HdmiCecLocalDeviceTv tv = tv(); 633 if (tv == null) { 634 Slog.w(TAG, "Local tv device not available"); 635 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 636 return; 637 } 638 tv.deviceSelect(logicalAddress, callback); 639 } 640 }); 641 } 642 643 @Override 644 public void portSelect(final int portId, final IHdmiControlCallback callback) { 645 enforceAccessPermission(); 646 runOnServiceThread(new Runnable() { 647 @Override 648 public void run() { 649 HdmiCecLocalDeviceTv tv = tv(); 650 if (tv == null) { 651 Slog.w(TAG, "Local tv device not available"); 652 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 653 return; 654 } 655 tv.doManualPortSwitching(portId, callback); 656 } 657 }); 658 } 659 660 @Override 661 public void sendKeyEvent(final int keyCode, final boolean isPressed) { 662 enforceAccessPermission(); 663 runOnServiceThread(new Runnable() { 664 @Override 665 public void run() { 666 // TODO: sendKeyEvent is for TV device only for now. Allow other 667 // local devices of different types to use this as well. 668 HdmiCecLocalDeviceTv tv = tv(); 669 if (tv == null) { 670 Slog.w(TAG, "Local tv device not available"); 671 return; 672 } 673 tv.sendKeyEvent(keyCode, isPressed); 674 } 675 }); 676 } 677 678 @Override 679 public void oneTouchPlay(final IHdmiControlCallback callback) { 680 enforceAccessPermission(); 681 runOnServiceThread(new Runnable() { 682 @Override 683 public void run() { 684 HdmiControlService.this.oneTouchPlay(callback); 685 } 686 }); 687 } 688 689 @Override 690 public void queryDisplayStatus(final IHdmiControlCallback callback) { 691 enforceAccessPermission(); 692 runOnServiceThread(new Runnable() { 693 @Override 694 public void run() { 695 HdmiControlService.this.queryDisplayStatus(callback); 696 } 697 }); 698 } 699 700 @Override 701 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 702 enforceAccessPermission(); 703 runOnServiceThread(new Runnable() { 704 @Override 705 public void run() { 706 HdmiControlService.this.addHotplugEventListener(listener); 707 } 708 }); 709 } 710 711 @Override 712 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) { 713 enforceAccessPermission(); 714 runOnServiceThread(new Runnable() { 715 @Override 716 public void run() { 717 HdmiControlService.this.removeHotplugEventListener(listener); 718 } 719 }); 720 } 721 722 @Override 723 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) { 724 enforceAccessPermission(); 725 runOnServiceThread(new Runnable() { 726 @Override 727 public void run() { 728 HdmiControlService.this.addDeviceEventListener(listener); 729 } 730 }); 731 } 732 733 @Override 734 public List<HdmiPortInfo> getPortInfo() { 735 enforceAccessPermission(); 736 return mPortInfo; 737 } 738 739 @Override 740 public boolean canChangeSystemAudioMode() { 741 enforceAccessPermission(); 742 HdmiCecLocalDeviceTv tv = tv(); 743 if (tv == null) { 744 return false; 745 } 746 return tv.hasSystemAudioDevice(); 747 } 748 749 @Override 750 public boolean getSystemAudioMode() { 751 enforceAccessPermission(); 752 HdmiCecLocalDeviceTv tv = tv(); 753 if (tv == null) { 754 return false; 755 } 756 return tv.getSystemAudioMode(); 757 } 758 759 @Override 760 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) { 761 enforceAccessPermission(); 762 runOnServiceThread(new Runnable() { 763 @Override 764 public void run() { 765 HdmiCecLocalDeviceTv tv = tv(); 766 if (tv == null) { 767 Slog.w(TAG, "Local tv device not available"); 768 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 769 return; 770 } 771 tv.changeSystemAudioMode(enabled, callback); 772 } 773 }); 774 } 775 776 @Override 777 public void addSystemAudioModeChangeListener( 778 final IHdmiSystemAudioModeChangeListener listener) { 779 enforceAccessPermission(); 780 HdmiControlService.this.addSystemAudioModeChangeListner(listener); 781 } 782 783 @Override 784 public void removeSystemAudioModeChangeListener( 785 final IHdmiSystemAudioModeChangeListener listener) { 786 enforceAccessPermission(); 787 HdmiControlService.this.removeSystemAudioModeChangeListener(listener); 788 } 789 790 @Override 791 public void setInputChangeListener(final IHdmiInputChangeListener listener) { 792 enforceAccessPermission(); 793 HdmiControlService.this.setInputChangeListener(listener); 794 } 795 796 @Override 797 public List<HdmiCecDeviceInfo> getInputDevices() { 798 enforceAccessPermission(); 799 // No need to hold the lock for obtaining TV device as the local device instance 800 // is preserved while the HDMI control is enabled. 801 HdmiCecLocalDeviceTv tv = tv(); 802 if (tv == null) { 803 return Collections.emptyList(); 804 } 805 return tv.getSafeExternalInputs(); 806 } 807 808 @Override 809 public void setControlEnabled(final boolean enabled) { 810 enforceAccessPermission(); 811 synchronized (mLock) { 812 mHdmiControlEnabled = enabled; 813 } 814 // TODO: Stop the running actions when disabled, and start 815 // address allocation/device discovery when enabled. 816 if (!enabled) { 817 return; 818 } 819 runOnServiceThread(new Runnable() { 820 @Override 821 public void run() { 822 HdmiCecLocalDeviceTv tv = tv(); 823 if (tv == null) { 824 return; 825 } 826 int value = enabled ? HdmiCec.ENABLED : HdmiCec.DISABLED; 827 mCecController.setOption(HdmiCec.OPTION_CEC_ENABLE, value); 828 if (mMhlController != null) { 829 mMhlController.setOption(HdmiCec.OPTION_MHL_ENABLE, value); 830 } 831 tv.launchRoutingControl(false); 832 } 833 }); 834 } 835 836 @Override 837 public void setSystemAudioVolume(final int oldIndex, final int newIndex, 838 final int maxIndex) { 839 enforceAccessPermission(); 840 runOnServiceThread(new Runnable() { 841 @Override 842 public void run() { 843 HdmiCecLocalDeviceTv tv = tv(); 844 if (tv == null) { 845 Slog.w(TAG, "Local tv device not available"); 846 return; 847 } 848 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex); 849 } 850 }); 851 } 852 853 @Override 854 public void setSystemAudioMute(final boolean mute) { 855 enforceAccessPermission(); 856 runOnServiceThread(new Runnable() { 857 @Override 858 public void run() { 859 HdmiCecLocalDeviceTv tv = tv(); 860 if (tv == null) { 861 Slog.w(TAG, "Local tv device not available"); 862 return; 863 } 864 tv.changeMute(mute); 865 } 866 }); 867 } 868 869 @Override 870 public void setArcMode(final boolean enabled) { 871 enforceAccessPermission(); 872 runOnServiceThread(new Runnable() { 873 @Override 874 public void run() { 875 HdmiCecLocalDeviceTv tv = tv(); 876 if (tv == null) { 877 Slog.w(TAG, "Local tv device not available to change arc mode."); 878 return; 879 } 880 } 881 }); 882 } 883 884 @Override 885 public void setOption(final int key, final int value) { 886 enforceAccessPermission(); 887 if (!isTvDevice()) { 888 return; 889 } 890 switch (key) { 891 case HdmiCec.OPTION_CEC_AUTO_WAKEUP: 892 mCecController.setOption(key, value); 893 break; 894 case HdmiCec.OPTION_CEC_AUTO_DEVICE_OFF: 895 // No need to pass this option to HAL. 896 tv().setAutoDeviceOff(value == HdmiCec.ENABLED); 897 break; 898 case HdmiCec.OPTION_MHL_INPUT_SWITCHING: // Fall through 899 case HdmiCec.OPTION_MHL_POWER_CHARGE: 900 if (mMhlController != null) { 901 mMhlController.setOption(key, value); 902 } 903 break; 904 } 905 } 906 907 private boolean isTvDevice() { 908 return tv() != null; 909 } 910 911 @Override 912 public void setProhibitMode(final boolean enabled) { 913 enforceAccessPermission(); 914 if (!isTvDevice()) { 915 return; 916 } 917 HdmiControlService.this.setProhibitMode(enabled); 918 } 919 } 920 921 @ServiceThreadOnly 922 private void oneTouchPlay(final IHdmiControlCallback callback) { 923 assertRunOnServiceThread(); 924 HdmiCecLocalDevicePlayback source = playback(); 925 if (source == null) { 926 Slog.w(TAG, "Local playback device not available"); 927 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 928 return; 929 } 930 source.oneTouchPlay(callback); 931 } 932 933 @ServiceThreadOnly 934 private void queryDisplayStatus(final IHdmiControlCallback callback) { 935 assertRunOnServiceThread(); 936 HdmiCecLocalDevicePlayback source = playback(); 937 if (source == null) { 938 Slog.w(TAG, "Local playback device not available"); 939 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 940 return; 941 } 942 source.queryDisplayStatus(callback); 943 } 944 945 private void addHotplugEventListener(IHdmiHotplugEventListener listener) { 946 HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); 947 try { 948 listener.asBinder().linkToDeath(record, 0); 949 } catch (RemoteException e) { 950 Slog.w(TAG, "Listener already died"); 951 return; 952 } 953 synchronized (mLock) { 954 mHotplugEventListenerRecords.add(record); 955 mHotplugEventListeners.add(listener); 956 } 957 } 958 959 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) { 960 synchronized (mLock) { 961 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 962 if (record.mListener.asBinder() == listener.asBinder()) { 963 listener.asBinder().unlinkToDeath(record, 0); 964 mHotplugEventListenerRecords.remove(record); 965 break; 966 } 967 } 968 mHotplugEventListeners.remove(listener); 969 } 970 } 971 972 private void addDeviceEventListener(IHdmiDeviceEventListener listener) { 973 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener); 974 try { 975 listener.asBinder().linkToDeath(record, 0); 976 } catch (RemoteException e) { 977 Slog.w(TAG, "Listener already died"); 978 return; 979 } 980 synchronized (mLock) { 981 mDeviceEventListeners.add(listener); 982 mDeviceEventListenerRecords.add(record); 983 } 984 } 985 986 void invokeDeviceEventListeners(HdmiCecDeviceInfo device, boolean activated) { 987 synchronized (mLock) { 988 for (IHdmiDeviceEventListener listener : mDeviceEventListeners) { 989 try { 990 listener.onStatusChanged(device, activated); 991 } catch (RemoteException e) { 992 Slog.e(TAG, "Failed to report device event:" + e); 993 } 994 } 995 } 996 } 997 998 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) { 999 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord( 1000 listener); 1001 try { 1002 listener.asBinder().linkToDeath(record, 0); 1003 } catch (RemoteException e) { 1004 Slog.w(TAG, "Listener already died"); 1005 return; 1006 } 1007 synchronized (mLock) { 1008 mSystemAudioModeChangeListeners.add(listener); 1009 mSystemAudioModeChangeListenerRecords.add(record); 1010 } 1011 } 1012 1013 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) { 1014 synchronized (mLock) { 1015 for (SystemAudioModeChangeListenerRecord record : 1016 mSystemAudioModeChangeListenerRecords) { 1017 if (record.mListener.asBinder() == listener) { 1018 listener.asBinder().unlinkToDeath(record, 0); 1019 mSystemAudioModeChangeListenerRecords.remove(record); 1020 break; 1021 } 1022 } 1023 mSystemAudioModeChangeListeners.remove(listener); 1024 } 1025 } 1026 1027 private final class InputChangeListenerRecord implements IBinder.DeathRecipient { 1028 @Override 1029 public void binderDied() { 1030 synchronized (mLock) { 1031 mInputChangeListener = null; 1032 } 1033 } 1034 } 1035 1036 private void setInputChangeListener(IHdmiInputChangeListener listener) { 1037 synchronized (mLock) { 1038 mInputChangeListenerRecord = new InputChangeListenerRecord(); 1039 try { 1040 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0); 1041 } catch (RemoteException e) { 1042 Slog.w(TAG, "Listener already died"); 1043 return; 1044 } 1045 mInputChangeListener = listener; 1046 } 1047 } 1048 1049 void invokeInputChangeListener(int activeAddress) { 1050 synchronized (mLock) { 1051 if (mInputChangeListener != null) { 1052 HdmiCecDeviceInfo activeSource = getDeviceInfo(activeAddress); 1053 try { 1054 mInputChangeListener.onChanged(activeSource); 1055 } catch (RemoteException e) { 1056 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e); 1057 } 1058 } 1059 } 1060 } 1061 1062 private void invokeCallback(IHdmiControlCallback callback, int result) { 1063 try { 1064 callback.onComplete(result); 1065 } catch (RemoteException e) { 1066 Slog.e(TAG, "Invoking callback failed:" + e); 1067 } 1068 } 1069 1070 private void invokeSystemAudioModeChange(IHdmiSystemAudioModeChangeListener listener, 1071 boolean enabled) { 1072 try { 1073 listener.onStatusChanged(enabled); 1074 } catch (RemoteException e) { 1075 Slog.e(TAG, "Invoking callback failed:" + e); 1076 } 1077 } 1078 1079 private void announceHotplugEvent(int portId, boolean connected) { 1080 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected); 1081 synchronized (mLock) { 1082 for (IHdmiHotplugEventListener listener : mHotplugEventListeners) { 1083 invokeHotplugEventListenerLocked(listener, event); 1084 } 1085 } 1086 } 1087 1088 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener, 1089 HdmiHotplugEvent event) { 1090 try { 1091 listener.onReceived(event); 1092 } catch (RemoteException e) { 1093 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e); 1094 } 1095 } 1096 1097 private static boolean hasSameTopPort(int path1, int path2) { 1098 return (path1 & HdmiConstants.ROUTING_PATH_TOP_MASK) 1099 == (path2 & HdmiConstants.ROUTING_PATH_TOP_MASK); 1100 } 1101 1102 private HdmiCecLocalDeviceTv tv() { 1103 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiCec.DEVICE_TV); 1104 } 1105 1106 private HdmiCecLocalDevicePlayback playback() { 1107 return (HdmiCecLocalDevicePlayback) mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK); 1108 } 1109 1110 AudioManager getAudioManager() { 1111 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); 1112 } 1113 1114 boolean isControlEnabled() { 1115 synchronized (mLock) { 1116 return mHdmiControlEnabled; 1117 } 1118 } 1119 1120 int getPowerStatus() { 1121 return mPowerStatus; 1122 } 1123 1124 boolean isPowerOnOrTransient() { 1125 return mPowerStatus == HdmiCec.POWER_STATUS_ON 1126 || mPowerStatus == HdmiCec.POWER_STATUS_TRANSIENT_TO_ON; 1127 } 1128 1129 boolean isPowerStandbyOrTransient() { 1130 return mPowerStatus == HdmiCec.POWER_STATUS_STANDBY 1131 || mPowerStatus == HdmiCec.POWER_STATUS_TRANSIENT_TO_STANDBY; 1132 } 1133 1134 boolean isPowerStandby() { 1135 return mPowerStatus == HdmiCec.POWER_STATUS_STANDBY; 1136 } 1137 1138 @ServiceThreadOnly 1139 void wakeUp() { 1140 assertRunOnServiceThread(); 1141 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); 1142 pm.wakeUp(SystemClock.uptimeMillis()); 1143 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets 1144 // the intent, the sequence will continue at onWakeUp(). 1145 } 1146 1147 @ServiceThreadOnly 1148 void standby() { 1149 assertRunOnServiceThread(); 1150 mStandbyMessageReceived = true; 1151 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); 1152 pm.goToSleep(SystemClock.uptimeMillis()); 1153 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets 1154 // the intent, the sequence will continue at onStandby(). 1155 } 1156 1157 @ServiceThreadOnly 1158 private void onWakeUp() { 1159 assertRunOnServiceThread(); 1160 mPowerStatus = HdmiCec.POWER_STATUS_TRANSIENT_TO_ON; 1161 if (mCecController != null) { 1162 mCecController.setOption(HdmiCec.OPTION_CEC_SERVICE_CONTROL, HdmiCec.ENABLED); 1163 initializeLocalDevices(mLocalDevices); 1164 } else { 1165 Slog.i(TAG, "Device does not support HDMI-CEC."); 1166 } 1167 // TODO: Initialize MHL local devices. 1168 } 1169 1170 @ServiceThreadOnly 1171 private void onStandby() { 1172 assertRunOnServiceThread(); 1173 mPowerStatus = HdmiCec.POWER_STATUS_TRANSIENT_TO_STANDBY; 1174 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 1175 device.onTransitionToStandby(mStandbyMessageReceived); 1176 } 1177 } 1178 1179 /** 1180 * Called when there are the outstanding actions in the local devices. 1181 * This callback is used to wait for when the action queue is empty 1182 * during the power state transition to standby. 1183 */ 1184 @ServiceThreadOnly 1185 void onPendingActionsCleared() { 1186 assertRunOnServiceThread(); 1187 if (mPowerStatus != HdmiCec.POWER_STATUS_TRANSIENT_TO_STANDBY) { 1188 return; 1189 } 1190 mPowerStatus = HdmiCec.POWER_STATUS_STANDBY; 1191 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 1192 device.onStandBy(mStandbyMessageReceived); 1193 } 1194 mStandbyMessageReceived = false; 1195 mCecController.setOption(HdmiCec.OPTION_CEC_SERVICE_CONTROL, HdmiCec.DISABLED); 1196 } 1197 1198 boolean isProhibitMode() { 1199 synchronized (mLock) { 1200 return mProhibitMode; 1201 } 1202 } 1203 1204 void setProhibitMode(boolean enabled) { 1205 synchronized (mLock) { 1206 mProhibitMode = enabled; 1207 } 1208 } 1209} 1210