HdmiControlService.java revision 5f75cbd8593e83eaf17cfac07186a3b6a7b7b1f1
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.ContentResolver; 22import android.content.Context; 23import android.content.Intent; 24import android.content.IntentFilter; 25import android.hardware.hdmi.HdmiDeviceInfo; 26import android.hardware.hdmi.HdmiControlManager; 27import android.hardware.hdmi.HdmiHotplugEvent; 28import android.hardware.hdmi.HdmiPortInfo; 29import android.hardware.hdmi.HdmiTvClient; 30import android.hardware.hdmi.IHdmiControlCallback; 31import android.hardware.hdmi.IHdmiControlService; 32import android.hardware.hdmi.IHdmiDeviceEventListener; 33import android.hardware.hdmi.IHdmiHotplugEventListener; 34import android.hardware.hdmi.IHdmiInputChangeListener; 35import android.hardware.hdmi.IHdmiRecordListener; 36import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener; 37import android.hardware.hdmi.IHdmiVendorCommandListener; 38import android.media.AudioManager; 39import android.os.Build; 40import android.os.Handler; 41import android.os.HandlerThread; 42import android.os.IBinder; 43import android.os.Looper; 44import android.os.PowerManager; 45import android.os.RemoteException; 46import android.os.SystemClock; 47import android.provider.Settings.Global; 48import android.util.ArraySet; 49import android.util.Slog; 50import android.util.SparseArray; 51import android.util.SparseIntArray; 52 53import com.android.internal.annotations.GuardedBy; 54import com.android.server.SystemService; 55import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; 56import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; 57import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource; 58import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback; 59 60import libcore.util.EmptyArray; 61 62import java.util.ArrayList; 63import java.util.Arrays; 64import java.util.Collections; 65import java.util.List; 66 67/** 68 * Provides a service for sending and processing HDMI control messages, 69 * HDMI-CEC and MHL control command, and providing the information on both standard. 70 */ 71public final class HdmiControlService extends SystemService { 72 private static final String TAG = "HdmiControlService"; 73 74 static final String PERMISSION = "android.permission.HDMI_CEC"; 75 76 // The reason code to initiate intializeCec(). 77 static final int INITIATED_BY_ENABLE_CEC = 0; 78 static final int INITIATED_BY_BOOT_UP = 1; 79 static final int INITIATED_BY_SCREEN_ON = 2; 80 static final int INITIATED_BY_WAKE_UP_MESSAGE = 3; 81 82 /** 83 * Interface to report send result. 84 */ 85 interface SendMessageCallback { 86 /** 87 * Called when {@link HdmiControlService#sendCecCommand} is completed. 88 * 89 * @param error result of send request. 90 * <ul> 91 * <li>{@link Constants#SEND_RESULT_SUCCESS} 92 * <li>{@link Constants#SEND_RESULT_NAK} 93 * <li>{@link Constants#SEND_RESULT_FAILURE} 94 * </ul> 95 */ 96 void onSendCompleted(int error); 97 } 98 99 /** 100 * Interface to get a list of available logical devices. 101 */ 102 interface DevicePollingCallback { 103 /** 104 * Called when device polling is finished. 105 * 106 * @param ackedAddress a list of logical addresses of available devices 107 */ 108 void onPollingFinished(List<Integer> ackedAddress); 109 } 110 111 private class PowerStateReceiver extends BroadcastReceiver { 112 @Override 113 public void onReceive(Context context, Intent intent) { 114 switch (intent.getAction()) { 115 case Intent.ACTION_SCREEN_OFF: 116 if (isPowerOnOrTransient()) { 117 onStandby(); 118 } 119 break; 120 case Intent.ACTION_SCREEN_ON: 121 if (isPowerStandbyOrTransient()) { 122 onWakeUp(); 123 } 124 break; 125 } 126 } 127 } 128 129 // A thread to handle synchronous IO of CEC and MHL control service. 130 // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms) 131 // and sparse call it shares a thread to handle IO operations. 132 private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread"); 133 134 // Used to synchronize the access to the service. 135 private final Object mLock = new Object(); 136 137 // Type of logical devices hosted in the system. Stored in the unmodifiable list. 138 private final List<Integer> mLocalDevices; 139 140 // List of listeners registered by callers that want to get notified of 141 // hotplug events. 142 @GuardedBy("mLock") 143 private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>(); 144 145 // List of records for hotplug event listener to handle the the caller killed in action. 146 @GuardedBy("mLock") 147 private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords = 148 new ArrayList<>(); 149 150 // List of listeners registered by callers that want to get notified of 151 // device status events. 152 @GuardedBy("mLock") 153 private final ArrayList<IHdmiDeviceEventListener> mDeviceEventListeners = new ArrayList<>(); 154 155 // List of records for device event listener to handle the the caller killed in action. 156 @GuardedBy("mLock") 157 private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords = 158 new ArrayList<>(); 159 160 // List of records for vendor command listener to handle the the caller killed in action. 161 @GuardedBy("mLock") 162 private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords = 163 new ArrayList<>(); 164 165 @GuardedBy("mLock") 166 private IHdmiInputChangeListener mInputChangeListener; 167 168 @GuardedBy("mLock") 169 private InputChangeListenerRecord mInputChangeListenerRecord; 170 171 @GuardedBy("mLock") 172 private IHdmiRecordListener mRecordListener; 173 174 @GuardedBy("mLock") 175 private HdmiRecordListenerRecord mRecordListenerRecord; 176 177 // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol 178 // handling will be disabled and no request will be handled. 179 @GuardedBy("mLock") 180 private boolean mHdmiControlEnabled; 181 182 // Set to true while the service is in normal mode. While set to false, no input change is 183 // allowed. Used for situations where input change can confuse users such as channel auto-scan, 184 // system upgrade, etc., a.k.a. "prohibit mode". 185 @GuardedBy("mLock") 186 private boolean mProhibitMode; 187 188 // List of listeners registered by callers that want to get notified of 189 // system audio mode changes. 190 private final ArrayList<IHdmiSystemAudioModeChangeListener> 191 mSystemAudioModeChangeListeners = new ArrayList<>(); 192 // List of records for system audio mode change to handle the the caller killed in action. 193 private final ArrayList<SystemAudioModeChangeListenerRecord> 194 mSystemAudioModeChangeListenerRecords = new ArrayList<>(); 195 196 // Handler used to run a task in service thread. 197 private final Handler mHandler = new Handler(); 198 199 @Nullable 200 private HdmiCecController mCecController; 201 202 @Nullable 203 private HdmiMhlController mMhlController; 204 205 // HDMI port information. Stored in the unmodifiable list to keep the static information 206 // from being modified. 207 private List<HdmiPortInfo> mPortInfo; 208 209 // Map from path(physical address) to port ID. 210 private UnmodifiableSparseIntArray mPortIdMap; 211 212 // Map from port ID to HdmiPortInfo. 213 private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap; 214 215 private HdmiCecMessageValidator mMessageValidator; 216 217 private final PowerStateReceiver mPowerStateReceiver = new PowerStateReceiver(); 218 219 @ServiceThreadOnly 220 private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; 221 222 @ServiceThreadOnly 223 private boolean mStandbyMessageReceived = false; 224 225 @ServiceThreadOnly 226 private boolean mWakeUpMessageReceived = false; 227 228 public HdmiControlService(Context context) { 229 super(context); 230 mLocalDevices = HdmiUtils.asImmutableList(getContext().getResources().getIntArray( 231 com.android.internal.R.array.config_hdmiCecLogicalDeviceType)); 232 } 233 234 @Override 235 public void onStart() { 236 mIoThread.start(); 237 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; 238 mProhibitMode = false; 239 mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true); 240 241 mCecController = HdmiCecController.create(this); 242 if (mCecController != null) { 243 // TODO: Remove this as soon as OEM's HAL implementation is corrected. 244 mCecController.setOption(HdmiTvClient.OPTION_CEC_ENABLE, 245 HdmiTvClient.ENABLED); 246 247 // TODO: load value for mHdmiControlEnabled from preference. 248 if (mHdmiControlEnabled) { 249 initializeCec(INITIATED_BY_BOOT_UP); 250 } 251 } else { 252 Slog.i(TAG, "Device does not support HDMI-CEC."); 253 } 254 255 mMhlController = HdmiMhlController.create(this); 256 if (mMhlController == null) { 257 Slog.i(TAG, "Device does not support MHL-control."); 258 } 259 initPortInfo(); 260 mMessageValidator = new HdmiCecMessageValidator(this); 261 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); 262 263 // Register broadcast receiver for power state change. 264 if (mCecController != null || mMhlController != null) { 265 IntentFilter filter = new IntentFilter(); 266 filter.addAction(Intent.ACTION_SCREEN_OFF); 267 filter.addAction(Intent.ACTION_SCREEN_ON); 268 getContext().registerReceiver(mPowerStateReceiver, filter); 269 } 270 } 271 272 /** 273 * Called when the initialization of local devices is complete. 274 */ 275 private void onInitializeCecComplete() { 276 if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) { 277 mPowerStatus = HdmiControlManager.POWER_STATUS_ON; 278 } 279 mWakeUpMessageReceived = false; 280 281 if (isTvDevice()) { 282 mCecController.setOption(HdmiTvClient.OPTION_CEC_AUTO_WAKEUP, 283 tv().getAutoWakeup() ? HdmiTvClient.ENABLED : HdmiTvClient.DISABLED); 284 } 285 } 286 287 boolean readBooleanSetting(String key, boolean defVal) { 288 ContentResolver cr = getContext().getContentResolver(); 289 return Global.getInt(cr, key, defVal ? Constants.TRUE : Constants.FALSE) == Constants.TRUE; 290 } 291 292 void writeBooleanSetting(String key, boolean value) { 293 ContentResolver cr = getContext().getContentResolver(); 294 Global.putInt(cr, key, value ? Constants.TRUE : Constants.FALSE); 295 } 296 297 private void initializeCec(int initiatedBy) { 298 mCecController.setOption(HdmiTvClient.OPTION_CEC_SERVICE_CONTROL, 299 HdmiTvClient.ENABLED); 300 initializeLocalDevices(mLocalDevices, initiatedBy); 301 } 302 303 @ServiceThreadOnly 304 private void initializeLocalDevices(final List<Integer> deviceTypes, final int initiatedBy) { 305 assertRunOnServiceThread(); 306 // A container for [Logical Address, Local device info]. 307 final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>(); 308 final int[] finished = new int[1]; 309 clearLocalDevices(); 310 for (int type : deviceTypes) { 311 final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type); 312 localDevice.init(); 313 mCecController.allocateLogicalAddress(type, 314 localDevice.getPreferredAddress(), new AllocateAddressCallback() { 315 @Override 316 public void onAllocated(int deviceType, int logicalAddress) { 317 if (logicalAddress == Constants.ADDR_UNREGISTERED) { 318 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]"); 319 } else { 320 HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType); 321 localDevice.setDeviceInfo(deviceInfo); 322 mCecController.addLocalDevice(deviceType, localDevice); 323 mCecController.addLogicalAddress(logicalAddress); 324 devices.append(logicalAddress, localDevice); 325 } 326 327 // Address allocation completed for all devices. Notify each device. 328 if (deviceTypes.size() == ++finished[0]) { 329 onInitializeCecComplete(); 330 notifyAddressAllocated(devices, initiatedBy); 331 } 332 } 333 }); 334 } 335 } 336 337 @ServiceThreadOnly 338 private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices, int initiatedBy) { 339 assertRunOnServiceThread(); 340 for (int i = 0; i < devices.size(); ++i) { 341 int address = devices.keyAt(i); 342 HdmiCecLocalDevice device = devices.valueAt(i); 343 device.handleAddressAllocated(address, initiatedBy); 344 } 345 } 346 347 // Initialize HDMI port information. Combine the information from CEC and MHL HAL and 348 // keep them in one place. 349 @ServiceThreadOnly 350 private void initPortInfo() { 351 assertRunOnServiceThread(); 352 HdmiPortInfo[] cecPortInfo = null; 353 354 // CEC HAL provides majority of the info while MHL does only MHL support flag for 355 // each port. Return empty array if CEC HAL didn't provide the info. 356 if (mCecController != null) { 357 cecPortInfo = mCecController.getPortInfos(); 358 } 359 if (cecPortInfo == null) { 360 return; 361 } 362 363 SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>(); 364 SparseIntArray portIdMap = new SparseIntArray(); 365 for (HdmiPortInfo info : cecPortInfo) { 366 portIdMap.put(info.getAddress(), info.getId()); 367 portInfoMap.put(info.getId(), info); 368 } 369 mPortIdMap = new UnmodifiableSparseIntArray(portIdMap); 370 mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap); 371 372 if (mMhlController == null) { 373 mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo)); 374 return; 375 } else { 376 HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos(); 377 ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length); 378 for (HdmiPortInfo info : mhlPortInfo) { 379 if (info.isMhlSupported()) { 380 mhlSupportedPorts.add(info.getId()); 381 } 382 } 383 384 // Build HDMI port info list with CEC port info plus MHL supported flag. 385 ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length); 386 for (HdmiPortInfo info : cecPortInfo) { 387 if (mhlSupportedPorts.contains(info.getId())) { 388 result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(), 389 info.isCecSupported(), true, info.isArcSupported())); 390 } else { 391 result.add(info); 392 } 393 } 394 mPortInfo = Collections.unmodifiableList(result); 395 } 396 } 397 398 /** 399 * Returns HDMI port information for the given port id. 400 * 401 * @param portId HDMI port id 402 * @return {@link HdmiPortInfo} for the given port 403 */ 404 HdmiPortInfo getPortInfo(int portId) { 405 return mPortInfoMap.get(portId, null); 406 } 407 408 /** 409 * Returns the routing path (physical address) of the HDMI port for the given 410 * port id. 411 */ 412 int portIdToPath(int portId) { 413 HdmiPortInfo portInfo = getPortInfo(portId); 414 if (portInfo == null) { 415 Slog.e(TAG, "Cannot find the port info: " + portId); 416 return Constants.INVALID_PHYSICAL_ADDRESS; 417 } 418 return portInfo.getAddress(); 419 } 420 421 /** 422 * Returns the id of HDMI port located at the top of the hierarchy of 423 * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance, 424 * the port id to be returned is the ID associated with the port address 425 * 0x1000 (1.0.0.0) which is the topmost path of the given routing path. 426 */ 427 int pathToPortId(int path) { 428 int portAddress = path & Constants.ROUTING_PATH_TOP_MASK; 429 return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID); 430 } 431 432 boolean isValidPortId(int portId) { 433 return getPortInfo(portId) != null; 434 } 435 436 /** 437 * Returns {@link Looper} for IO operation. 438 * 439 * <p>Declared as package-private. 440 */ 441 Looper getIoLooper() { 442 return mIoThread.getLooper(); 443 } 444 445 /** 446 * Returns {@link Looper} of main thread. Use this {@link Looper} instance 447 * for tasks that are running on main service thread. 448 * 449 * <p>Declared as package-private. 450 */ 451 Looper getServiceLooper() { 452 return mHandler.getLooper(); 453 } 454 455 /** 456 * Returns physical address of the device. 457 */ 458 int getPhysicalAddress() { 459 return mCecController.getPhysicalAddress(); 460 } 461 462 /** 463 * Returns vendor id of CEC service. 464 */ 465 int getVendorId() { 466 return mCecController.getVendorId(); 467 } 468 469 @ServiceThreadOnly 470 HdmiDeviceInfo getDeviceInfo(int logicalAddress) { 471 assertRunOnServiceThread(); 472 HdmiCecLocalDeviceTv tv = tv(); 473 if (tv == null) { 474 return null; 475 } 476 return tv.getDeviceInfo(logicalAddress); 477 } 478 479 /** 480 * Returns version of CEC. 481 */ 482 int getCecVersion() { 483 return mCecController.getVersion(); 484 } 485 486 /** 487 * Whether a device of the specified physical address is connected to ARC enabled port. 488 */ 489 boolean isConnectedToArcPort(int physicalAddress) { 490 int portId = mPortIdMap.get(physicalAddress); 491 if (portId != Constants.INVALID_PORT_ID) { 492 return mPortInfoMap.get(portId).isArcSupported(); 493 } 494 return false; 495 } 496 497 void runOnServiceThread(Runnable runnable) { 498 mHandler.post(runnable); 499 } 500 501 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) { 502 mHandler.postAtFrontOfQueue(runnable); 503 } 504 505 private void assertRunOnServiceThread() { 506 if (Looper.myLooper() != mHandler.getLooper()) { 507 throw new IllegalStateException("Should run on service thread."); 508 } 509 } 510 511 /** 512 * Transmit a CEC command to CEC bus. 513 * 514 * @param command CEC command to send out 515 * @param callback interface used to the result of send command 516 */ 517 @ServiceThreadOnly 518 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) { 519 assertRunOnServiceThread(); 520 if (mMessageValidator.isValid(command)) { 521 mCecController.sendCommand(command, callback); 522 } else { 523 Slog.e(TAG, "Invalid message type:" + command); 524 if (callback != null) { 525 callback.onSendCompleted(Constants.SEND_RESULT_FAILURE); 526 } 527 } 528 } 529 530 @ServiceThreadOnly 531 void sendCecCommand(HdmiCecMessage command) { 532 assertRunOnServiceThread(); 533 sendCecCommand(command, null); 534 } 535 536 /** 537 * Send <Feature Abort> command on the given CEC message if possible. 538 * If the aborted message is invalid, then it wont send the message. 539 * @param command original command to be aborted 540 * @param reason reason of feature abort 541 */ 542 @ServiceThreadOnly 543 void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) { 544 assertRunOnServiceThread(); 545 mCecController.maySendFeatureAbortCommand(command, reason); 546 } 547 548 @ServiceThreadOnly 549 boolean handleCecCommand(HdmiCecMessage message) { 550 assertRunOnServiceThread(); 551 if (!mMessageValidator.isValid(message)) { 552 return false; 553 } 554 return dispatchMessageToLocalDevice(message); 555 } 556 557 void setAudioReturnChannel(boolean enabled) { 558 mCecController.setAudioReturnChannel(enabled); 559 } 560 561 @ServiceThreadOnly 562 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) { 563 assertRunOnServiceThread(); 564 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 565 if (device.dispatchMessage(message) 566 && message.getDestination() != Constants.ADDR_BROADCAST) { 567 return true; 568 } 569 } 570 571 if (message.getDestination() != Constants.ADDR_BROADCAST) { 572 Slog.w(TAG, "Unhandled cec command:" + message); 573 } 574 return false; 575 } 576 577 /** 578 * Called when a new hotplug event is issued. 579 * 580 * @param portNo hdmi port number where hot plug event issued. 581 * @param connected whether to be plugged in or not 582 */ 583 @ServiceThreadOnly 584 void onHotplug(int portNo, boolean connected) { 585 assertRunOnServiceThread(); 586 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 587 device.onHotplug(portNo, connected); 588 } 589 announceHotplugEvent(portNo, connected); 590 } 591 592 /** 593 * Poll all remote devices. It sends <Polling Message> to all remote 594 * devices. 595 * 596 * @param callback an interface used to get a list of all remote devices' address 597 * @param sourceAddress a logical address of source device where sends polling message 598 * @param pickStrategy strategy how to pick polling candidates 599 * @param retryCount the number of retry used to send polling message to remote devices 600 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value 601 */ 602 @ServiceThreadOnly 603 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, 604 int retryCount) { 605 assertRunOnServiceThread(); 606 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy), 607 retryCount); 608 } 609 610 private int checkPollStrategy(int pickStrategy) { 611 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK; 612 if (strategy == 0) { 613 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy); 614 } 615 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK; 616 if (iterationStrategy == 0) { 617 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy); 618 } 619 return strategy | iterationStrategy; 620 } 621 622 List<HdmiCecLocalDevice> getAllLocalDevices() { 623 assertRunOnServiceThread(); 624 return mCecController.getLocalDeviceList(); 625 } 626 627 Object getServiceLock() { 628 return mLock; 629 } 630 631 void setAudioStatus(boolean mute, int volume) { 632 AudioManager audioManager = getAudioManager(); 633 boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC); 634 if (mute) { 635 if (!muted) { 636 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true); 637 } 638 } else { 639 if (muted) { 640 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false); 641 } 642 // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing 643 // volume change notification back to hdmi control service. 644 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 645 AudioManager.FLAG_SHOW_UI | 646 AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME); 647 } 648 } 649 650 void announceSystemAudioModeChange(boolean enabled) { 651 for (IHdmiSystemAudioModeChangeListener listener : mSystemAudioModeChangeListeners) { 652 invokeSystemAudioModeChange(listener, enabled); 653 } 654 } 655 656 private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) { 657 // TODO: find better name instead of model name. 658 String displayName = Build.MODEL; 659 return new HdmiDeviceInfo(logicalAddress, 660 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType, 661 getVendorId(), displayName); 662 } 663 664 // Record class that monitors the event of the caller of being killed. Used to clean up 665 // the listener list and record list accordingly. 666 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { 667 private final IHdmiHotplugEventListener mListener; 668 669 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) { 670 mListener = listener; 671 } 672 673 @Override 674 public void binderDied() { 675 synchronized (mLock) { 676 mHotplugEventListenerRecords.remove(this); 677 mHotplugEventListeners.remove(mListener); 678 } 679 } 680 } 681 682 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient { 683 private final IHdmiDeviceEventListener mListener; 684 685 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) { 686 mListener = listener; 687 } 688 689 @Override 690 public void binderDied() { 691 synchronized (mLock) { 692 mDeviceEventListenerRecords.remove(this); 693 mDeviceEventListeners.remove(mListener); 694 } 695 } 696 } 697 698 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient { 699 private final IHdmiSystemAudioModeChangeListener mListener; 700 701 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) { 702 mListener = listener; 703 } 704 705 @Override 706 public void binderDied() { 707 synchronized (mLock) { 708 mSystemAudioModeChangeListenerRecords.remove(this); 709 mSystemAudioModeChangeListeners.remove(mListener); 710 } 711 } 712 } 713 714 class VendorCommandListenerRecord implements IBinder.DeathRecipient { 715 private final IHdmiVendorCommandListener mListener; 716 private final int mDeviceType; 717 718 public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) { 719 mListener = listener; 720 mDeviceType = deviceType; 721 } 722 723 @Override 724 public void binderDied() { 725 synchronized (mLock) { 726 mVendorCommandListenerRecords.remove(this); 727 } 728 } 729 } 730 731 private class HdmiRecordListenerRecord implements IBinder.DeathRecipient { 732 @Override 733 public void binderDied() { 734 synchronized (mLock) { 735 mRecordListener = null; 736 } 737 } 738 } 739 740 private void enforceAccessPermission() { 741 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); 742 } 743 744 private final class BinderService extends IHdmiControlService.Stub { 745 @Override 746 public int[] getSupportedTypes() { 747 enforceAccessPermission(); 748 // mLocalDevices is an unmodifiable list - no lock necesary. 749 int[] localDevices = new int[mLocalDevices.size()]; 750 for (int i = 0; i < localDevices.length; ++i) { 751 localDevices[i] = mLocalDevices.get(i); 752 } 753 return localDevices; 754 } 755 756 @Override 757 public HdmiDeviceInfo getActiveSource() { 758 HdmiCecLocalDeviceTv tv = tv(); 759 if (tv == null) { 760 Slog.w(TAG, "Local tv device not available"); 761 return null; 762 } 763 ActiveSource activeSource = tv.getActiveSource(); 764 if (activeSource.isValid()) { 765 return new HdmiDeviceInfo(activeSource.logicalAddress, 766 activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID, 767 HdmiDeviceInfo.DEVICE_INACTIVE, 0, ""); 768 } 769 int activePath = tv.getActivePath(); 770 if (activePath != HdmiDeviceInfo.PATH_INVALID) { 771 return new HdmiDeviceInfo(activePath, tv.getActivePortId()); 772 } 773 return null; 774 } 775 776 @Override 777 public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) { 778 enforceAccessPermission(); 779 runOnServiceThread(new Runnable() { 780 @Override 781 public void run() { 782 if (callback == null) { 783 Slog.e(TAG, "Callback cannot be null"); 784 return; 785 } 786 HdmiCecLocalDeviceTv tv = tv(); 787 if (tv == null) { 788 Slog.w(TAG, "Local tv device not available"); 789 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 790 return; 791 } 792 tv.deviceSelect(logicalAddress, callback); 793 } 794 }); 795 } 796 797 @Override 798 public void portSelect(final int portId, final IHdmiControlCallback callback) { 799 enforceAccessPermission(); 800 runOnServiceThread(new Runnable() { 801 @Override 802 public void run() { 803 if (callback == null) { 804 Slog.e(TAG, "Callback cannot be null"); 805 return; 806 } 807 HdmiCecLocalDeviceTv tv = tv(); 808 if (tv == null) { 809 Slog.w(TAG, "Local tv device not available"); 810 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 811 return; 812 } 813 tv.doManualPortSwitching(portId, callback); 814 } 815 }); 816 } 817 818 @Override 819 public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) { 820 enforceAccessPermission(); 821 runOnServiceThread(new Runnable() { 822 @Override 823 public void run() { 824 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType); 825 if (localDevice == null) { 826 Slog.w(TAG, "Local device not available"); 827 return; 828 } 829 localDevice.sendKeyEvent(keyCode, isPressed); 830 } 831 }); 832 } 833 834 @Override 835 public void oneTouchPlay(final IHdmiControlCallback callback) { 836 enforceAccessPermission(); 837 runOnServiceThread(new Runnable() { 838 @Override 839 public void run() { 840 HdmiControlService.this.oneTouchPlay(callback); 841 } 842 }); 843 } 844 845 @Override 846 public void queryDisplayStatus(final IHdmiControlCallback callback) { 847 enforceAccessPermission(); 848 runOnServiceThread(new Runnable() { 849 @Override 850 public void run() { 851 HdmiControlService.this.queryDisplayStatus(callback); 852 } 853 }); 854 } 855 856 @Override 857 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 858 enforceAccessPermission(); 859 runOnServiceThread(new Runnable() { 860 @Override 861 public void run() { 862 HdmiControlService.this.addHotplugEventListener(listener); 863 } 864 }); 865 } 866 867 @Override 868 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) { 869 enforceAccessPermission(); 870 runOnServiceThread(new Runnable() { 871 @Override 872 public void run() { 873 HdmiControlService.this.removeHotplugEventListener(listener); 874 } 875 }); 876 } 877 878 @Override 879 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) { 880 enforceAccessPermission(); 881 runOnServiceThread(new Runnable() { 882 @Override 883 public void run() { 884 HdmiControlService.this.addDeviceEventListener(listener); 885 } 886 }); 887 } 888 889 @Override 890 public List<HdmiPortInfo> getPortInfo() { 891 enforceAccessPermission(); 892 return mPortInfo; 893 } 894 895 @Override 896 public boolean canChangeSystemAudioMode() { 897 enforceAccessPermission(); 898 HdmiCecLocalDeviceTv tv = tv(); 899 if (tv == null) { 900 return false; 901 } 902 return tv.hasSystemAudioDevice(); 903 } 904 905 @Override 906 public boolean getSystemAudioMode() { 907 enforceAccessPermission(); 908 HdmiCecLocalDeviceTv tv = tv(); 909 if (tv == null) { 910 return false; 911 } 912 return tv.isSystemAudioActivated(); 913 } 914 915 @Override 916 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) { 917 enforceAccessPermission(); 918 runOnServiceThread(new Runnable() { 919 @Override 920 public void run() { 921 HdmiCecLocalDeviceTv tv = tv(); 922 if (tv == null) { 923 Slog.w(TAG, "Local tv device not available"); 924 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 925 return; 926 } 927 tv.changeSystemAudioMode(enabled, callback); 928 } 929 }); 930 } 931 932 @Override 933 public void addSystemAudioModeChangeListener( 934 final IHdmiSystemAudioModeChangeListener listener) { 935 enforceAccessPermission(); 936 HdmiControlService.this.addSystemAudioModeChangeListner(listener); 937 } 938 939 @Override 940 public void removeSystemAudioModeChangeListener( 941 final IHdmiSystemAudioModeChangeListener listener) { 942 enforceAccessPermission(); 943 HdmiControlService.this.removeSystemAudioModeChangeListener(listener); 944 } 945 946 @Override 947 public void setInputChangeListener(final IHdmiInputChangeListener listener) { 948 enforceAccessPermission(); 949 HdmiControlService.this.setInputChangeListener(listener); 950 } 951 952 @Override 953 public List<HdmiDeviceInfo> getInputDevices() { 954 enforceAccessPermission(); 955 // No need to hold the lock for obtaining TV device as the local device instance 956 // is preserved while the HDMI control is enabled. 957 HdmiCecLocalDeviceTv tv = tv(); 958 if (tv == null) { 959 return Collections.emptyList(); 960 } 961 return tv.getSafeExternalInputs(); 962 } 963 964 @Override 965 public void setControlEnabled(final boolean enabled) { 966 enforceAccessPermission(); 967 runOnServiceThread(new Runnable() { 968 @Override 969 public void run() { 970 handleHdmiControlStatusChanged(enabled); 971 972 } 973 }); 974 } 975 976 @Override 977 public void setSystemAudioVolume(final int oldIndex, final int newIndex, 978 final int maxIndex) { 979 enforceAccessPermission(); 980 runOnServiceThread(new Runnable() { 981 @Override 982 public void run() { 983 HdmiCecLocalDeviceTv tv = tv(); 984 if (tv == null) { 985 Slog.w(TAG, "Local tv device not available"); 986 return; 987 } 988 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex); 989 } 990 }); 991 } 992 993 @Override 994 public void setSystemAudioMute(final boolean mute) { 995 enforceAccessPermission(); 996 runOnServiceThread(new Runnable() { 997 @Override 998 public void run() { 999 HdmiCecLocalDeviceTv tv = tv(); 1000 if (tv == null) { 1001 Slog.w(TAG, "Local tv device not available"); 1002 return; 1003 } 1004 tv.changeMute(mute); 1005 } 1006 }); 1007 } 1008 1009 @Override 1010 public void setArcMode(final boolean enabled) { 1011 enforceAccessPermission(); 1012 runOnServiceThread(new Runnable() { 1013 @Override 1014 public void run() { 1015 HdmiCecLocalDeviceTv tv = tv(); 1016 if (tv == null) { 1017 Slog.w(TAG, "Local tv device not available to change arc mode."); 1018 return; 1019 } 1020 } 1021 }); 1022 } 1023 1024 @Override 1025 public void setOption(final int key, final int value) { 1026 enforceAccessPermission(); 1027 if (!isTvDevice()) { 1028 return; 1029 } 1030 switch (key) { 1031 case HdmiTvClient.OPTION_CEC_AUTO_WAKEUP: 1032 tv().setAutoWakeup(value == HdmiTvClient.ENABLED); 1033 mCecController.setOption(key, value); 1034 break; 1035 case HdmiTvClient.OPTION_CEC_AUTO_DEVICE_OFF: 1036 // No need to pass this option to HAL. 1037 tv().setAutoDeviceOff(value == HdmiTvClient.ENABLED); 1038 break; 1039 case HdmiTvClient.OPTION_MHL_INPUT_SWITCHING: // Fall through 1040 case HdmiTvClient.OPTION_MHL_POWER_CHARGE: 1041 if (mMhlController != null) { 1042 mMhlController.setOption(key, value); 1043 } 1044 break; 1045 } 1046 } 1047 1048 @Override 1049 public void setProhibitMode(final boolean enabled) { 1050 enforceAccessPermission(); 1051 if (!isTvDevice()) { 1052 return; 1053 } 1054 HdmiControlService.this.setProhibitMode(enabled); 1055 } 1056 1057 @Override 1058 public void addVendorCommandListener(final IHdmiVendorCommandListener listener, 1059 final int deviceType) { 1060 enforceAccessPermission(); 1061 runOnServiceThread(new Runnable() { 1062 @Override 1063 public void run() { 1064 HdmiControlService.this.addVendorCommandListener(listener, deviceType); 1065 } 1066 }); 1067 } 1068 1069 @Override 1070 public void sendVendorCommand(final int deviceType, final int targetAddress, 1071 final byte[] params, final boolean hasVendorId) { 1072 enforceAccessPermission(); 1073 runOnServiceThread(new Runnable() { 1074 @Override 1075 public void run() { 1076 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType); 1077 if (device == null) { 1078 Slog.w(TAG, "Local device not available"); 1079 return; 1080 } 1081 if (hasVendorId) { 1082 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId( 1083 device.getDeviceInfo().getLogicalAddress(), targetAddress, 1084 getVendorId(), params)); 1085 } else { 1086 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand( 1087 device.getDeviceInfo().getLogicalAddress(), targetAddress, params)); 1088 } 1089 } 1090 }); 1091 } 1092 1093 @Override 1094 public void setHdmiRecordListener(IHdmiRecordListener listener) { 1095 HdmiControlService.this.setHdmiRecordListener(listener); 1096 } 1097 1098 @Override 1099 public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) { 1100 runOnServiceThread(new Runnable() { 1101 @Override 1102 public void run() { 1103 if (!isTvDevice()) { 1104 Slog.w(TAG, "No TV is available."); 1105 return; 1106 } 1107 tv().startOneTouchRecord(recorderAddress, recordSource); 1108 } 1109 }); 1110 } 1111 1112 @Override 1113 public void stopOneTouchRecord(final int recorderAddress) { 1114 runOnServiceThread(new Runnable() { 1115 @Override 1116 public void run() { 1117 if (!isTvDevice()) { 1118 Slog.w(TAG, "No TV is available."); 1119 return; 1120 } 1121 tv().stopOneTouchRecord(recorderAddress); 1122 } 1123 }); 1124 } 1125 1126 @Override 1127 public void startTimerRecording(final int recorderAddress, final int sourceType, 1128 final byte[] recordSource) { 1129 runOnServiceThread(new Runnable() { 1130 @Override 1131 public void run() { 1132 if (!isTvDevice()) { 1133 Slog.w(TAG, "No TV is available."); 1134 return; 1135 } 1136 tv().startTimerRecording(recorderAddress, sourceType, recordSource); 1137 } 1138 }); 1139 } 1140 1141 @Override 1142 public void clearTimerRecording(final int recorderAddress, final int sourceType, 1143 final byte[] recordSource) { 1144 runOnServiceThread(new Runnable() { 1145 @Override 1146 public void run() { 1147 if (!isTvDevice()) { 1148 Slog.w(TAG, "No TV is available."); 1149 return; 1150 } 1151 tv().clearTimerRecording(recorderAddress, sourceType, recordSource); 1152 } 1153 }); 1154 } 1155 } 1156 1157 @ServiceThreadOnly 1158 private void oneTouchPlay(final IHdmiControlCallback callback) { 1159 assertRunOnServiceThread(); 1160 HdmiCecLocalDevicePlayback source = playback(); 1161 if (source == null) { 1162 Slog.w(TAG, "Local playback device not available"); 1163 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1164 return; 1165 } 1166 source.oneTouchPlay(callback); 1167 } 1168 1169 @ServiceThreadOnly 1170 private void queryDisplayStatus(final IHdmiControlCallback callback) { 1171 assertRunOnServiceThread(); 1172 HdmiCecLocalDevicePlayback source = playback(); 1173 if (source == null) { 1174 Slog.w(TAG, "Local playback device not available"); 1175 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1176 return; 1177 } 1178 source.queryDisplayStatus(callback); 1179 } 1180 1181 private void addHotplugEventListener(IHdmiHotplugEventListener listener) { 1182 HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); 1183 try { 1184 listener.asBinder().linkToDeath(record, 0); 1185 } catch (RemoteException e) { 1186 Slog.w(TAG, "Listener already died"); 1187 return; 1188 } 1189 synchronized (mLock) { 1190 mHotplugEventListenerRecords.add(record); 1191 mHotplugEventListeners.add(listener); 1192 } 1193 } 1194 1195 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) { 1196 synchronized (mLock) { 1197 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 1198 if (record.mListener.asBinder() == listener.asBinder()) { 1199 listener.asBinder().unlinkToDeath(record, 0); 1200 mHotplugEventListenerRecords.remove(record); 1201 break; 1202 } 1203 } 1204 mHotplugEventListeners.remove(listener); 1205 } 1206 } 1207 1208 private void addDeviceEventListener(IHdmiDeviceEventListener listener) { 1209 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener); 1210 try { 1211 listener.asBinder().linkToDeath(record, 0); 1212 } catch (RemoteException e) { 1213 Slog.w(TAG, "Listener already died"); 1214 return; 1215 } 1216 synchronized (mLock) { 1217 mDeviceEventListeners.add(listener); 1218 mDeviceEventListenerRecords.add(record); 1219 } 1220 } 1221 1222 void invokeDeviceEventListeners(HdmiDeviceInfo device, boolean activated) { 1223 synchronized (mLock) { 1224 for (IHdmiDeviceEventListener listener : mDeviceEventListeners) { 1225 try { 1226 listener.onStatusChanged(device, activated); 1227 } catch (RemoteException e) { 1228 Slog.e(TAG, "Failed to report device event:" + e); 1229 } 1230 } 1231 } 1232 } 1233 1234 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) { 1235 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord( 1236 listener); 1237 try { 1238 listener.asBinder().linkToDeath(record, 0); 1239 } catch (RemoteException e) { 1240 Slog.w(TAG, "Listener already died"); 1241 return; 1242 } 1243 synchronized (mLock) { 1244 mSystemAudioModeChangeListeners.add(listener); 1245 mSystemAudioModeChangeListenerRecords.add(record); 1246 } 1247 } 1248 1249 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) { 1250 synchronized (mLock) { 1251 for (SystemAudioModeChangeListenerRecord record : 1252 mSystemAudioModeChangeListenerRecords) { 1253 if (record.mListener.asBinder() == listener) { 1254 listener.asBinder().unlinkToDeath(record, 0); 1255 mSystemAudioModeChangeListenerRecords.remove(record); 1256 break; 1257 } 1258 } 1259 mSystemAudioModeChangeListeners.remove(listener); 1260 } 1261 } 1262 1263 private final class InputChangeListenerRecord implements IBinder.DeathRecipient { 1264 @Override 1265 public void binderDied() { 1266 synchronized (mLock) { 1267 mInputChangeListener = null; 1268 } 1269 } 1270 } 1271 1272 private void setInputChangeListener(IHdmiInputChangeListener listener) { 1273 synchronized (mLock) { 1274 mInputChangeListenerRecord = new InputChangeListenerRecord(); 1275 try { 1276 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0); 1277 } catch (RemoteException e) { 1278 Slog.w(TAG, "Listener already died"); 1279 return; 1280 } 1281 mInputChangeListener = listener; 1282 } 1283 } 1284 1285 void invokeInputChangeListener(HdmiDeviceInfo info) { 1286 synchronized (mLock) { 1287 if (mInputChangeListener != null) { 1288 try { 1289 mInputChangeListener.onChanged(info); 1290 } catch (RemoteException e) { 1291 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e); 1292 } 1293 } 1294 } 1295 } 1296 1297 private void setHdmiRecordListener(IHdmiRecordListener listener) { 1298 synchronized (mLock) { 1299 mRecordListenerRecord = new HdmiRecordListenerRecord(); 1300 try { 1301 listener.asBinder().linkToDeath(mRecordListenerRecord, 0); 1302 } catch (RemoteException e) { 1303 Slog.w(TAG, "Listener already died.", e); 1304 } 1305 mRecordListener = listener; 1306 } 1307 } 1308 1309 byte[] invokeRecordRequestListener(int recorderAddress) { 1310 synchronized (mLock) { 1311 if (mRecordListener != null) { 1312 try { 1313 return mRecordListener.getOneTouchRecordSource(recorderAddress); 1314 } catch (RemoteException e) { 1315 Slog.w(TAG, "Failed to start record.", e); 1316 } 1317 } 1318 return EmptyArray.BYTE; 1319 } 1320 } 1321 1322 void invokeOneTouchRecordResult(int result) { 1323 synchronized (mLock) { 1324 if (mRecordListener != null) { 1325 try { 1326 mRecordListener.onOneTouchRecordResult(result); 1327 } catch (RemoteException e) { 1328 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e); 1329 } 1330 } 1331 } 1332 } 1333 1334 void invokeTimerRecordingResult(int result) { 1335 synchronized (mLock) { 1336 if (mRecordListener != null) { 1337 try { 1338 mRecordListener.onTimerRecordingResult(result); 1339 } catch (RemoteException e) { 1340 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e); 1341 } 1342 } 1343 } 1344 } 1345 1346 void invokeClearTimerRecordingResult(int result) { 1347 synchronized (mLock) { 1348 if (mRecordListener != null) { 1349 try { 1350 mRecordListener.onClearTimerRecordingResult(result); 1351 } catch (RemoteException e) { 1352 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e); 1353 } 1354 } 1355 } 1356 } 1357 1358 private void invokeCallback(IHdmiControlCallback callback, int result) { 1359 try { 1360 callback.onComplete(result); 1361 } catch (RemoteException e) { 1362 Slog.e(TAG, "Invoking callback failed:" + e); 1363 } 1364 } 1365 1366 private void invokeSystemAudioModeChange(IHdmiSystemAudioModeChangeListener listener, 1367 boolean enabled) { 1368 try { 1369 listener.onStatusChanged(enabled); 1370 } catch (RemoteException e) { 1371 Slog.e(TAG, "Invoking callback failed:" + e); 1372 } 1373 } 1374 1375 private void announceHotplugEvent(int portId, boolean connected) { 1376 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected); 1377 synchronized (mLock) { 1378 for (IHdmiHotplugEventListener listener : mHotplugEventListeners) { 1379 invokeHotplugEventListenerLocked(listener, event); 1380 } 1381 } 1382 } 1383 1384 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener, 1385 HdmiHotplugEvent event) { 1386 try { 1387 listener.onReceived(event); 1388 } catch (RemoteException e) { 1389 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e); 1390 } 1391 } 1392 1393 private HdmiCecLocalDeviceTv tv() { 1394 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV); 1395 } 1396 1397 boolean isTvDevice() { 1398 return tv() != null; 1399 } 1400 1401 private HdmiCecLocalDevicePlayback playback() { 1402 return (HdmiCecLocalDevicePlayback) 1403 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK); 1404 } 1405 1406 AudioManager getAudioManager() { 1407 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); 1408 } 1409 1410 boolean isControlEnabled() { 1411 synchronized (mLock) { 1412 return mHdmiControlEnabled; 1413 } 1414 } 1415 1416 int getPowerStatus() { 1417 return mPowerStatus; 1418 } 1419 1420 boolean isPowerOnOrTransient() { 1421 return mPowerStatus == HdmiControlManager.POWER_STATUS_ON 1422 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; 1423 } 1424 1425 boolean isPowerStandbyOrTransient() { 1426 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY 1427 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY; 1428 } 1429 1430 boolean isPowerStandby() { 1431 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY; 1432 } 1433 1434 @ServiceThreadOnly 1435 void wakeUp() { 1436 assertRunOnServiceThread(); 1437 mWakeUpMessageReceived = true; 1438 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); 1439 pm.wakeUp(SystemClock.uptimeMillis()); 1440 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets 1441 // the intent, the sequence will continue at onWakeUp(). 1442 } 1443 1444 @ServiceThreadOnly 1445 void standby() { 1446 assertRunOnServiceThread(); 1447 mStandbyMessageReceived = true; 1448 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); 1449 pm.goToSleep(SystemClock.uptimeMillis()); 1450 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets 1451 // the intent, the sequence will continue at onStandby(). 1452 } 1453 1454 @ServiceThreadOnly 1455 private void onWakeUp() { 1456 assertRunOnServiceThread(); 1457 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; 1458 if (mCecController != null) { 1459 if (mHdmiControlEnabled) { 1460 int startReason = INITIATED_BY_SCREEN_ON; 1461 if (mWakeUpMessageReceived) { 1462 startReason = INITIATED_BY_WAKE_UP_MESSAGE; 1463 } 1464 initializeCec(startReason); 1465 } 1466 } else { 1467 Slog.i(TAG, "Device does not support HDMI-CEC."); 1468 } 1469 // TODO: Initialize MHL local devices. 1470 } 1471 1472 @ServiceThreadOnly 1473 private void onStandby() { 1474 assertRunOnServiceThread(); 1475 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY; 1476 1477 final List<HdmiCecLocalDevice> devices = getAllLocalDevices(); 1478 disableDevices(new PendingActionClearedCallback() { 1479 @Override 1480 public void onCleared(HdmiCecLocalDevice device) { 1481 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType); 1482 devices.remove(device); 1483 if (devices.isEmpty()) { 1484 onStandbyCompleted(); 1485 // We will not clear local devices here, since some OEM/SOC will keep passing 1486 // the received packets until the application processor enters to the sleep 1487 // actually. 1488 } 1489 } 1490 }); 1491 } 1492 1493 private void disableDevices(PendingActionClearedCallback callback) { 1494 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 1495 device.disableDevice(mStandbyMessageReceived, callback); 1496 } 1497 } 1498 1499 @ServiceThreadOnly 1500 private void clearLocalDevices() { 1501 assertRunOnServiceThread(); 1502 if (mCecController == null) { 1503 return; 1504 } 1505 mCecController.clearLogicalAddress(); 1506 mCecController.clearLocalDevices(); 1507 } 1508 1509 @ServiceThreadOnly 1510 private void onStandbyCompleted() { 1511 assertRunOnServiceThread(); 1512 Slog.v(TAG, "onStandbyCompleted"); 1513 1514 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) { 1515 return; 1516 } 1517 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; 1518 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 1519 device.onStandby(mStandbyMessageReceived); 1520 } 1521 mStandbyMessageReceived = false; 1522 mCecController.setOption(HdmiTvClient.OPTION_CEC_SERVICE_CONTROL, HdmiTvClient.DISABLED); 1523 } 1524 1525 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) { 1526 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType); 1527 try { 1528 listener.asBinder().linkToDeath(record, 0); 1529 } catch (RemoteException e) { 1530 Slog.w(TAG, "Listener already died"); 1531 return; 1532 } 1533 synchronized (mLock) { 1534 mVendorCommandListenerRecords.add(record); 1535 } 1536 } 1537 1538 void invokeVendorCommandListeners(int deviceType, int srcAddress, byte[] params, 1539 boolean hasVendorId) { 1540 synchronized (mLock) { 1541 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) { 1542 if (record.mDeviceType != deviceType) { 1543 continue; 1544 } 1545 try { 1546 record.mListener.onReceived(srcAddress, params, hasVendorId); 1547 } catch (RemoteException e) { 1548 Slog.e(TAG, "Failed to notify vendor command reception", e); 1549 } 1550 } 1551 } 1552 } 1553 1554 boolean isProhibitMode() { 1555 synchronized (mLock) { 1556 return mProhibitMode; 1557 } 1558 } 1559 1560 void setProhibitMode(boolean enabled) { 1561 synchronized (mLock) { 1562 mProhibitMode = enabled; 1563 } 1564 } 1565 1566 @ServiceThreadOnly 1567 private void handleHdmiControlStatusChanged(boolean enabled) { 1568 assertRunOnServiceThread(); 1569 1570 int value = enabled ? HdmiTvClient.ENABLED : HdmiTvClient.DISABLED; 1571 mCecController.setOption(HdmiTvClient.OPTION_CEC_ENABLE, value); 1572 if (mMhlController != null) { 1573 mMhlController.setOption(HdmiTvClient.OPTION_MHL_ENABLE, value); 1574 } 1575 1576 synchronized (mLock) { 1577 mHdmiControlEnabled = enabled; 1578 } 1579 1580 if (enabled) { 1581 initializeCec(INITIATED_BY_ENABLE_CEC); 1582 } else { 1583 disableDevices(new PendingActionClearedCallback() { 1584 @Override 1585 public void onCleared(HdmiCecLocalDevice device) { 1586 assertRunOnServiceThread(); 1587 clearLocalDevices(); 1588 } 1589 }); 1590 } 1591 } 1592} 1593