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