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