HdmiControlService.java revision 30c74d9ba60a7208a587ecd6dcdfef1cfbd7fce7
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.HdmiCecDeviceInfo; 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 HdmiCecDeviceInfo 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 HdmiCecDeviceInfo 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 mCecController.sendCommand(command, callback); 521 } 522 523 @ServiceThreadOnly 524 void sendCecCommand(HdmiCecMessage command) { 525 assertRunOnServiceThread(); 526 mCecController.sendCommand(command, null); 527 } 528 529 /** 530 * Send <Feature Abort> command on the given CEC message if possible. 531 * If the aborted message is invalid, then it wont send the message. 532 * @param command original command to be aborted 533 * @param reason reason of feature abort 534 */ 535 @ServiceThreadOnly 536 void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) { 537 assertRunOnServiceThread(); 538 mCecController.maySendFeatureAbortCommand(command, reason); 539 } 540 541 @ServiceThreadOnly 542 boolean handleCecCommand(HdmiCecMessage message) { 543 assertRunOnServiceThread(); 544 if (!mMessageValidator.isValid(message)) { 545 return false; 546 } 547 return dispatchMessageToLocalDevice(message); 548 } 549 550 void setAudioReturnChannel(boolean enabled) { 551 mCecController.setAudioReturnChannel(enabled); 552 } 553 554 @ServiceThreadOnly 555 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) { 556 assertRunOnServiceThread(); 557 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 558 if (device.dispatchMessage(message) 559 && message.getDestination() != Constants.ADDR_BROADCAST) { 560 return true; 561 } 562 } 563 564 if (message.getDestination() != Constants.ADDR_BROADCAST) { 565 Slog.w(TAG, "Unhandled cec command:" + message); 566 } 567 return false; 568 } 569 570 /** 571 * Called when a new hotplug event is issued. 572 * 573 * @param portNo hdmi port number where hot plug event issued. 574 * @param connected whether to be plugged in or not 575 */ 576 @ServiceThreadOnly 577 void onHotplug(int portNo, boolean connected) { 578 assertRunOnServiceThread(); 579 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 580 device.onHotplug(portNo, connected); 581 } 582 announceHotplugEvent(portNo, connected); 583 } 584 585 /** 586 * Poll all remote devices. It sends <Polling Message> to all remote 587 * devices. 588 * 589 * @param callback an interface used to get a list of all remote devices' address 590 * @param sourceAddress a logical address of source device where sends polling message 591 * @param pickStrategy strategy how to pick polling candidates 592 * @param retryCount the number of retry used to send polling message to remote devices 593 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value 594 */ 595 @ServiceThreadOnly 596 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, 597 int retryCount) { 598 assertRunOnServiceThread(); 599 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy), 600 retryCount); 601 } 602 603 private int checkPollStrategy(int pickStrategy) { 604 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK; 605 if (strategy == 0) { 606 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy); 607 } 608 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK; 609 if (iterationStrategy == 0) { 610 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy); 611 } 612 return strategy | iterationStrategy; 613 } 614 615 List<HdmiCecLocalDevice> getAllLocalDevices() { 616 assertRunOnServiceThread(); 617 return mCecController.getLocalDeviceList(); 618 } 619 620 Object getServiceLock() { 621 return mLock; 622 } 623 624 void setAudioStatus(boolean mute, int volume) { 625 AudioManager audioManager = getAudioManager(); 626 boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC); 627 if (mute) { 628 if (!muted) { 629 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true); 630 } 631 } else { 632 if (muted) { 633 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false); 634 } 635 // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing 636 // volume change notification back to hdmi control service. 637 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 638 AudioManager.FLAG_SHOW_UI | 639 AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME); 640 } 641 } 642 643 void announceSystemAudioModeChange(boolean enabled) { 644 for (IHdmiSystemAudioModeChangeListener listener : mSystemAudioModeChangeListeners) { 645 invokeSystemAudioModeChange(listener, enabled); 646 } 647 } 648 649 private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) { 650 // TODO: find better name instead of model name. 651 String displayName = Build.MODEL; 652 return new HdmiCecDeviceInfo(logicalAddress, 653 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType, 654 getVendorId(), displayName); 655 } 656 657 // Record class that monitors the event of the caller of being killed. Used to clean up 658 // the listener list and record list accordingly. 659 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { 660 private final IHdmiHotplugEventListener mListener; 661 662 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) { 663 mListener = listener; 664 } 665 666 @Override 667 public void binderDied() { 668 synchronized (mLock) { 669 mHotplugEventListenerRecords.remove(this); 670 mHotplugEventListeners.remove(mListener); 671 } 672 } 673 } 674 675 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient { 676 private final IHdmiDeviceEventListener mListener; 677 678 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) { 679 mListener = listener; 680 } 681 682 @Override 683 public void binderDied() { 684 synchronized (mLock) { 685 mDeviceEventListenerRecords.remove(this); 686 mDeviceEventListeners.remove(mListener); 687 } 688 } 689 } 690 691 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient { 692 private final IHdmiSystemAudioModeChangeListener mListener; 693 694 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) { 695 mListener = listener; 696 } 697 698 @Override 699 public void binderDied() { 700 synchronized (mLock) { 701 mSystemAudioModeChangeListenerRecords.remove(this); 702 mSystemAudioModeChangeListeners.remove(mListener); 703 } 704 } 705 } 706 707 class VendorCommandListenerRecord implements IBinder.DeathRecipient { 708 private final IHdmiVendorCommandListener mListener; 709 private final int mDeviceType; 710 711 public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) { 712 mListener = listener; 713 mDeviceType = deviceType; 714 } 715 716 @Override 717 public void binderDied() { 718 synchronized (mLock) { 719 mVendorCommandListenerRecords.remove(this); 720 } 721 } 722 } 723 724 private class HdmiRecordListenerRecord implements IBinder.DeathRecipient { 725 @Override 726 public void binderDied() { 727 synchronized (mLock) { 728 mRecordListener = null; 729 } 730 } 731 } 732 733 private void enforceAccessPermission() { 734 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); 735 } 736 737 private final class BinderService extends IHdmiControlService.Stub { 738 @Override 739 public int[] getSupportedTypes() { 740 enforceAccessPermission(); 741 // mLocalDevices is an unmodifiable list - no lock necesary. 742 int[] localDevices = new int[mLocalDevices.size()]; 743 for (int i = 0; i < localDevices.length; ++i) { 744 localDevices[i] = mLocalDevices.get(i); 745 } 746 return localDevices; 747 } 748 749 @Override 750 public HdmiCecDeviceInfo getActiveSource() { 751 HdmiCecLocalDeviceTv tv = tv(); 752 if (tv == null) { 753 Slog.w(TAG, "Local tv device not available"); 754 return null; 755 } 756 ActiveSource activeSource = tv.getActiveSource(); 757 if (activeSource.isValid()) { 758 return new HdmiCecDeviceInfo(activeSource.logicalAddress, 759 activeSource.physicalAddress, HdmiCecDeviceInfo.PORT_INVALID, 760 HdmiCecDeviceInfo.DEVICE_INACTIVE, 0, ""); 761 } 762 int activePath = tv.getActivePath(); 763 if (activePath != HdmiCecDeviceInfo.PATH_INVALID) { 764 return new HdmiCecDeviceInfo(activePath, tv.getActivePortId()); 765 } 766 return null; 767 } 768 769 @Override 770 public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) { 771 enforceAccessPermission(); 772 runOnServiceThread(new Runnable() { 773 @Override 774 public void run() { 775 if (callback == null) { 776 Slog.e(TAG, "Callback cannot be null"); 777 return; 778 } 779 HdmiCecLocalDeviceTv tv = tv(); 780 if (tv == null) { 781 Slog.w(TAG, "Local tv device not available"); 782 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 783 return; 784 } 785 tv.deviceSelect(logicalAddress, callback); 786 } 787 }); 788 } 789 790 @Override 791 public void portSelect(final int portId, final IHdmiControlCallback callback) { 792 enforceAccessPermission(); 793 runOnServiceThread(new Runnable() { 794 @Override 795 public void run() { 796 if (callback == null) { 797 Slog.e(TAG, "Callback cannot be null"); 798 return; 799 } 800 HdmiCecLocalDeviceTv tv = tv(); 801 if (tv == null) { 802 Slog.w(TAG, "Local tv device not available"); 803 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 804 return; 805 } 806 tv.doManualPortSwitching(portId, callback); 807 } 808 }); 809 } 810 811 @Override 812 public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) { 813 enforceAccessPermission(); 814 runOnServiceThread(new Runnable() { 815 @Override 816 public void run() { 817 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType); 818 if (localDevice == null) { 819 Slog.w(TAG, "Local device not available"); 820 return; 821 } 822 localDevice.sendKeyEvent(keyCode, isPressed); 823 } 824 }); 825 } 826 827 @Override 828 public void oneTouchPlay(final IHdmiControlCallback callback) { 829 enforceAccessPermission(); 830 runOnServiceThread(new Runnable() { 831 @Override 832 public void run() { 833 HdmiControlService.this.oneTouchPlay(callback); 834 } 835 }); 836 } 837 838 @Override 839 public void queryDisplayStatus(final IHdmiControlCallback callback) { 840 enforceAccessPermission(); 841 runOnServiceThread(new Runnable() { 842 @Override 843 public void run() { 844 HdmiControlService.this.queryDisplayStatus(callback); 845 } 846 }); 847 } 848 849 @Override 850 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 851 enforceAccessPermission(); 852 runOnServiceThread(new Runnable() { 853 @Override 854 public void run() { 855 HdmiControlService.this.addHotplugEventListener(listener); 856 } 857 }); 858 } 859 860 @Override 861 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) { 862 enforceAccessPermission(); 863 runOnServiceThread(new Runnable() { 864 @Override 865 public void run() { 866 HdmiControlService.this.removeHotplugEventListener(listener); 867 } 868 }); 869 } 870 871 @Override 872 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) { 873 enforceAccessPermission(); 874 runOnServiceThread(new Runnable() { 875 @Override 876 public void run() { 877 HdmiControlService.this.addDeviceEventListener(listener); 878 } 879 }); 880 } 881 882 @Override 883 public List<HdmiPortInfo> getPortInfo() { 884 enforceAccessPermission(); 885 return mPortInfo; 886 } 887 888 @Override 889 public boolean canChangeSystemAudioMode() { 890 enforceAccessPermission(); 891 HdmiCecLocalDeviceTv tv = tv(); 892 if (tv == null) { 893 return false; 894 } 895 return tv.hasSystemAudioDevice(); 896 } 897 898 @Override 899 public boolean getSystemAudioMode() { 900 enforceAccessPermission(); 901 HdmiCecLocalDeviceTv tv = tv(); 902 if (tv == null) { 903 return false; 904 } 905 return tv.isSystemAudioActivated(); 906 } 907 908 @Override 909 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) { 910 enforceAccessPermission(); 911 runOnServiceThread(new Runnable() { 912 @Override 913 public void run() { 914 HdmiCecLocalDeviceTv tv = tv(); 915 if (tv == null) { 916 Slog.w(TAG, "Local tv device not available"); 917 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 918 return; 919 } 920 tv.changeSystemAudioMode(enabled, callback); 921 } 922 }); 923 } 924 925 @Override 926 public void addSystemAudioModeChangeListener( 927 final IHdmiSystemAudioModeChangeListener listener) { 928 enforceAccessPermission(); 929 HdmiControlService.this.addSystemAudioModeChangeListner(listener); 930 } 931 932 @Override 933 public void removeSystemAudioModeChangeListener( 934 final IHdmiSystemAudioModeChangeListener listener) { 935 enforceAccessPermission(); 936 HdmiControlService.this.removeSystemAudioModeChangeListener(listener); 937 } 938 939 @Override 940 public void setInputChangeListener(final IHdmiInputChangeListener listener) { 941 enforceAccessPermission(); 942 HdmiControlService.this.setInputChangeListener(listener); 943 } 944 945 @Override 946 public List<HdmiCecDeviceInfo> getInputDevices() { 947 enforceAccessPermission(); 948 // No need to hold the lock for obtaining TV device as the local device instance 949 // is preserved while the HDMI control is enabled. 950 HdmiCecLocalDeviceTv tv = tv(); 951 if (tv == null) { 952 return Collections.emptyList(); 953 } 954 return tv.getSafeExternalInputs(); 955 } 956 957 @Override 958 public void setControlEnabled(final boolean enabled) { 959 enforceAccessPermission(); 960 runOnServiceThread(new Runnable() { 961 @Override 962 public void run() { 963 handleHdmiControlStatusChanged(enabled); 964 965 } 966 }); 967 } 968 969 @Override 970 public void setSystemAudioVolume(final int oldIndex, final int newIndex, 971 final int maxIndex) { 972 enforceAccessPermission(); 973 runOnServiceThread(new Runnable() { 974 @Override 975 public void run() { 976 HdmiCecLocalDeviceTv tv = tv(); 977 if (tv == null) { 978 Slog.w(TAG, "Local tv device not available"); 979 return; 980 } 981 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex); 982 } 983 }); 984 } 985 986 @Override 987 public void setSystemAudioMute(final boolean mute) { 988 enforceAccessPermission(); 989 runOnServiceThread(new Runnable() { 990 @Override 991 public void run() { 992 HdmiCecLocalDeviceTv tv = tv(); 993 if (tv == null) { 994 Slog.w(TAG, "Local tv device not available"); 995 return; 996 } 997 tv.changeMute(mute); 998 } 999 }); 1000 } 1001 1002 @Override 1003 public void setArcMode(final boolean enabled) { 1004 enforceAccessPermission(); 1005 runOnServiceThread(new Runnable() { 1006 @Override 1007 public void run() { 1008 HdmiCecLocalDeviceTv tv = tv(); 1009 if (tv == null) { 1010 Slog.w(TAG, "Local tv device not available to change arc mode."); 1011 return; 1012 } 1013 } 1014 }); 1015 } 1016 1017 @Override 1018 public void setOption(final int key, final int value) { 1019 enforceAccessPermission(); 1020 if (!isTvDevice()) { 1021 return; 1022 } 1023 switch (key) { 1024 case HdmiTvClient.OPTION_CEC_AUTO_WAKEUP: 1025 tv().setAutoWakeup(value == HdmiTvClient.ENABLED); 1026 mCecController.setOption(key, value); 1027 break; 1028 case HdmiTvClient.OPTION_CEC_AUTO_DEVICE_OFF: 1029 // No need to pass this option to HAL. 1030 tv().setAutoDeviceOff(value == HdmiTvClient.ENABLED); 1031 break; 1032 case HdmiTvClient.OPTION_MHL_INPUT_SWITCHING: // Fall through 1033 case HdmiTvClient.OPTION_MHL_POWER_CHARGE: 1034 if (mMhlController != null) { 1035 mMhlController.setOption(key, value); 1036 } 1037 break; 1038 } 1039 } 1040 1041 @Override 1042 public void setProhibitMode(final boolean enabled) { 1043 enforceAccessPermission(); 1044 if (!isTvDevice()) { 1045 return; 1046 } 1047 HdmiControlService.this.setProhibitMode(enabled); 1048 } 1049 1050 @Override 1051 public void addVendorCommandListener(final IHdmiVendorCommandListener listener, 1052 final int deviceType) { 1053 enforceAccessPermission(); 1054 runOnServiceThread(new Runnable() { 1055 @Override 1056 public void run() { 1057 HdmiControlService.this.addVendorCommandListener(listener, deviceType); 1058 } 1059 }); 1060 } 1061 1062 @Override 1063 public void sendVendorCommand(final int deviceType, final int targetAddress, 1064 final byte[] params, final boolean hasVendorId) { 1065 enforceAccessPermission(); 1066 runOnServiceThread(new Runnable() { 1067 @Override 1068 public void run() { 1069 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType); 1070 if (device == null) { 1071 Slog.w(TAG, "Local device not available"); 1072 return; 1073 } 1074 if (hasVendorId) { 1075 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId( 1076 device.getDeviceInfo().getLogicalAddress(), targetAddress, 1077 getVendorId(), params)); 1078 } else { 1079 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand( 1080 device.getDeviceInfo().getLogicalAddress(), targetAddress, params)); 1081 } 1082 } 1083 }); 1084 } 1085 1086 @Override 1087 public void setHdmiRecordListener(IHdmiRecordListener listener) { 1088 HdmiControlService.this.setHdmiRecordListener(listener); 1089 } 1090 1091 @Override 1092 public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) { 1093 runOnServiceThread(new Runnable() { 1094 @Override 1095 public void run() { 1096 if (!isTvDevice()) { 1097 Slog.w(TAG, "No TV is available."); 1098 return; 1099 } 1100 tv().startOneTouchRecord(recorderAddress, recordSource); 1101 } 1102 }); 1103 } 1104 1105 @Override 1106 public void stopOneTouchRecord(final int recorderAddress) { 1107 runOnServiceThread(new Runnable() { 1108 @Override 1109 public void run() { 1110 if (!isTvDevice()) { 1111 Slog.w(TAG, "No TV is available."); 1112 return; 1113 } 1114 tv().stopOneTouchRecord(recorderAddress); 1115 } 1116 }); 1117 } 1118 1119 @Override 1120 public void startTimerRecording(final int recorderAddress, final int sourceType, 1121 final byte[] recordSource) { 1122 runOnServiceThread(new Runnable() { 1123 @Override 1124 public void run() { 1125 if (!isTvDevice()) { 1126 Slog.w(TAG, "No TV is available."); 1127 return; 1128 } 1129 tv().startTimerRecording(recorderAddress, sourceType, recordSource); 1130 } 1131 }); 1132 } 1133 1134 @Override 1135 public void clearTimerRecording(final int recorderAddress, final int sourceType, 1136 final byte[] recordSource) { 1137 runOnServiceThread(new Runnable() { 1138 @Override 1139 public void run() { 1140 if (!isTvDevice()) { 1141 Slog.w(TAG, "No TV is available."); 1142 return; 1143 } 1144 tv().clearTimerRecording(recorderAddress, sourceType, recordSource); 1145 } 1146 }); 1147 } 1148 } 1149 1150 @ServiceThreadOnly 1151 private void oneTouchPlay(final IHdmiControlCallback callback) { 1152 assertRunOnServiceThread(); 1153 HdmiCecLocalDevicePlayback source = playback(); 1154 if (source == null) { 1155 Slog.w(TAG, "Local playback device not available"); 1156 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1157 return; 1158 } 1159 source.oneTouchPlay(callback); 1160 } 1161 1162 @ServiceThreadOnly 1163 private void queryDisplayStatus(final IHdmiControlCallback callback) { 1164 assertRunOnServiceThread(); 1165 HdmiCecLocalDevicePlayback source = playback(); 1166 if (source == null) { 1167 Slog.w(TAG, "Local playback device not available"); 1168 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1169 return; 1170 } 1171 source.queryDisplayStatus(callback); 1172 } 1173 1174 private void addHotplugEventListener(IHdmiHotplugEventListener listener) { 1175 HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); 1176 try { 1177 listener.asBinder().linkToDeath(record, 0); 1178 } catch (RemoteException e) { 1179 Slog.w(TAG, "Listener already died"); 1180 return; 1181 } 1182 synchronized (mLock) { 1183 mHotplugEventListenerRecords.add(record); 1184 mHotplugEventListeners.add(listener); 1185 } 1186 } 1187 1188 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) { 1189 synchronized (mLock) { 1190 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 1191 if (record.mListener.asBinder() == listener.asBinder()) { 1192 listener.asBinder().unlinkToDeath(record, 0); 1193 mHotplugEventListenerRecords.remove(record); 1194 break; 1195 } 1196 } 1197 mHotplugEventListeners.remove(listener); 1198 } 1199 } 1200 1201 private void addDeviceEventListener(IHdmiDeviceEventListener listener) { 1202 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener); 1203 try { 1204 listener.asBinder().linkToDeath(record, 0); 1205 } catch (RemoteException e) { 1206 Slog.w(TAG, "Listener already died"); 1207 return; 1208 } 1209 synchronized (mLock) { 1210 mDeviceEventListeners.add(listener); 1211 mDeviceEventListenerRecords.add(record); 1212 } 1213 } 1214 1215 void invokeDeviceEventListeners(HdmiCecDeviceInfo device, boolean activated) { 1216 synchronized (mLock) { 1217 for (IHdmiDeviceEventListener listener : mDeviceEventListeners) { 1218 try { 1219 listener.onStatusChanged(device, activated); 1220 } catch (RemoteException e) { 1221 Slog.e(TAG, "Failed to report device event:" + e); 1222 } 1223 } 1224 } 1225 } 1226 1227 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) { 1228 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord( 1229 listener); 1230 try { 1231 listener.asBinder().linkToDeath(record, 0); 1232 } catch (RemoteException e) { 1233 Slog.w(TAG, "Listener already died"); 1234 return; 1235 } 1236 synchronized (mLock) { 1237 mSystemAudioModeChangeListeners.add(listener); 1238 mSystemAudioModeChangeListenerRecords.add(record); 1239 } 1240 } 1241 1242 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) { 1243 synchronized (mLock) { 1244 for (SystemAudioModeChangeListenerRecord record : 1245 mSystemAudioModeChangeListenerRecords) { 1246 if (record.mListener.asBinder() == listener) { 1247 listener.asBinder().unlinkToDeath(record, 0); 1248 mSystemAudioModeChangeListenerRecords.remove(record); 1249 break; 1250 } 1251 } 1252 mSystemAudioModeChangeListeners.remove(listener); 1253 } 1254 } 1255 1256 private final class InputChangeListenerRecord implements IBinder.DeathRecipient { 1257 @Override 1258 public void binderDied() { 1259 synchronized (mLock) { 1260 mInputChangeListener = null; 1261 } 1262 } 1263 } 1264 1265 private void setInputChangeListener(IHdmiInputChangeListener listener) { 1266 synchronized (mLock) { 1267 mInputChangeListenerRecord = new InputChangeListenerRecord(); 1268 try { 1269 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0); 1270 } catch (RemoteException e) { 1271 Slog.w(TAG, "Listener already died"); 1272 return; 1273 } 1274 mInputChangeListener = listener; 1275 } 1276 } 1277 1278 void invokeInputChangeListener(HdmiCecDeviceInfo info) { 1279 synchronized (mLock) { 1280 if (mInputChangeListener != null) { 1281 try { 1282 mInputChangeListener.onChanged(info); 1283 } catch (RemoteException e) { 1284 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e); 1285 } 1286 } 1287 } 1288 } 1289 1290 private void setHdmiRecordListener(IHdmiRecordListener listener) { 1291 synchronized (mLock) { 1292 mRecordListenerRecord = new HdmiRecordListenerRecord(); 1293 try { 1294 listener.asBinder().linkToDeath(mRecordListenerRecord, 0); 1295 } catch (RemoteException e) { 1296 Slog.w(TAG, "Listener already died.", e); 1297 } 1298 mRecordListener = listener; 1299 } 1300 } 1301 1302 byte[] invokeRecordRequestListener(int recorderAddress) { 1303 synchronized (mLock) { 1304 if (mRecordListener != null) { 1305 try { 1306 return mRecordListener.getOneTouchRecordSource(recorderAddress); 1307 } catch (RemoteException e) { 1308 Slog.w(TAG, "Failed to start record.", e); 1309 } 1310 } 1311 return EmptyArray.BYTE; 1312 } 1313 } 1314 1315 void invokeOneTouchRecordResult(int result) { 1316 synchronized (mLock) { 1317 if (mRecordListener != null) { 1318 try { 1319 mRecordListener.onOneTouchRecordResult(result); 1320 } catch (RemoteException e) { 1321 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e); 1322 } 1323 } 1324 } 1325 } 1326 1327 void invokeTimerRecordingResult(int result) { 1328 synchronized (mLock) { 1329 if (mRecordListener != null) { 1330 try { 1331 mRecordListener.onTimerRecordingResult(result); 1332 } catch (RemoteException e) { 1333 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e); 1334 } 1335 } 1336 } 1337 } 1338 1339 void invokeClearTimerRecordingResult(int result) { 1340 synchronized (mLock) { 1341 if (mRecordListener != null) { 1342 try { 1343 mRecordListener.onClearTimerRecordingResult(result); 1344 } catch (RemoteException e) { 1345 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e); 1346 } 1347 } 1348 } 1349 } 1350 1351 private void invokeCallback(IHdmiControlCallback callback, int result) { 1352 try { 1353 callback.onComplete(result); 1354 } catch (RemoteException e) { 1355 Slog.e(TAG, "Invoking callback failed:" + e); 1356 } 1357 } 1358 1359 private void invokeSystemAudioModeChange(IHdmiSystemAudioModeChangeListener listener, 1360 boolean enabled) { 1361 try { 1362 listener.onStatusChanged(enabled); 1363 } catch (RemoteException e) { 1364 Slog.e(TAG, "Invoking callback failed:" + e); 1365 } 1366 } 1367 1368 private void announceHotplugEvent(int portId, boolean connected) { 1369 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected); 1370 synchronized (mLock) { 1371 for (IHdmiHotplugEventListener listener : mHotplugEventListeners) { 1372 invokeHotplugEventListenerLocked(listener, event); 1373 } 1374 } 1375 } 1376 1377 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener, 1378 HdmiHotplugEvent event) { 1379 try { 1380 listener.onReceived(event); 1381 } catch (RemoteException e) { 1382 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e); 1383 } 1384 } 1385 1386 private HdmiCecLocalDeviceTv tv() { 1387 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiCecDeviceInfo.DEVICE_TV); 1388 } 1389 1390 boolean isTvDevice() { 1391 return tv() != null; 1392 } 1393 1394 private HdmiCecLocalDevicePlayback playback() { 1395 return (HdmiCecLocalDevicePlayback) 1396 mCecController.getLocalDevice(HdmiCecDeviceInfo.DEVICE_PLAYBACK); 1397 } 1398 1399 AudioManager getAudioManager() { 1400 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); 1401 } 1402 1403 boolean isControlEnabled() { 1404 synchronized (mLock) { 1405 return mHdmiControlEnabled; 1406 } 1407 } 1408 1409 int getPowerStatus() { 1410 return mPowerStatus; 1411 } 1412 1413 boolean isPowerOnOrTransient() { 1414 return mPowerStatus == HdmiControlManager.POWER_STATUS_ON 1415 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; 1416 } 1417 1418 boolean isPowerStandbyOrTransient() { 1419 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY 1420 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY; 1421 } 1422 1423 boolean isPowerStandby() { 1424 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY; 1425 } 1426 1427 @ServiceThreadOnly 1428 void wakeUp() { 1429 assertRunOnServiceThread(); 1430 mWakeUpMessageReceived = true; 1431 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); 1432 pm.wakeUp(SystemClock.uptimeMillis()); 1433 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets 1434 // the intent, the sequence will continue at onWakeUp(). 1435 } 1436 1437 @ServiceThreadOnly 1438 void standby() { 1439 assertRunOnServiceThread(); 1440 mStandbyMessageReceived = true; 1441 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); 1442 pm.goToSleep(SystemClock.uptimeMillis()); 1443 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets 1444 // the intent, the sequence will continue at onStandby(). 1445 } 1446 1447 @ServiceThreadOnly 1448 private void onWakeUp() { 1449 assertRunOnServiceThread(); 1450 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; 1451 if (mCecController != null) { 1452 if (mHdmiControlEnabled) { 1453 int startReason = INITIATED_BY_SCREEN_ON; 1454 if (mWakeUpMessageReceived) { 1455 startReason = INITIATED_BY_WAKE_UP_MESSAGE; 1456 } 1457 initializeCec(startReason); 1458 } 1459 } else { 1460 Slog.i(TAG, "Device does not support HDMI-CEC."); 1461 } 1462 // TODO: Initialize MHL local devices. 1463 } 1464 1465 @ServiceThreadOnly 1466 private void onStandby() { 1467 assertRunOnServiceThread(); 1468 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY; 1469 1470 final List<HdmiCecLocalDevice> devices = getAllLocalDevices(); 1471 disableDevices(new PendingActionClearedCallback() { 1472 @Override 1473 public void onCleared(HdmiCecLocalDevice device) { 1474 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType); 1475 devices.remove(device); 1476 if (devices.isEmpty()) { 1477 onStandbyCompleted(); 1478 // We will not clear local devices here, since some OEM/SOC will keep passing 1479 // the received packets until the application processor enters to the sleep 1480 // actually. 1481 } 1482 } 1483 }); 1484 } 1485 1486 private void disableDevices(PendingActionClearedCallback callback) { 1487 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 1488 device.disableDevice(mStandbyMessageReceived, callback); 1489 } 1490 } 1491 1492 @ServiceThreadOnly 1493 private void clearLocalDevices() { 1494 assertRunOnServiceThread(); 1495 if (mCecController == null) { 1496 return; 1497 } 1498 mCecController.clearLogicalAddress(); 1499 mCecController.clearLocalDevices(); 1500 } 1501 1502 @ServiceThreadOnly 1503 private void onStandbyCompleted() { 1504 assertRunOnServiceThread(); 1505 Slog.v(TAG, "onStandbyCompleted"); 1506 1507 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) { 1508 return; 1509 } 1510 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; 1511 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 1512 device.onStandby(mStandbyMessageReceived); 1513 } 1514 mStandbyMessageReceived = false; 1515 mCecController.setOption(HdmiTvClient.OPTION_CEC_SERVICE_CONTROL, HdmiTvClient.DISABLED); 1516 } 1517 1518 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) { 1519 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType); 1520 try { 1521 listener.asBinder().linkToDeath(record, 0); 1522 } catch (RemoteException e) { 1523 Slog.w(TAG, "Listener already died"); 1524 return; 1525 } 1526 synchronized (mLock) { 1527 mVendorCommandListenerRecords.add(record); 1528 } 1529 } 1530 1531 void invokeVendorCommandListeners(int deviceType, int srcAddress, byte[] params, 1532 boolean hasVendorId) { 1533 synchronized (mLock) { 1534 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) { 1535 if (record.mDeviceType != deviceType) { 1536 continue; 1537 } 1538 try { 1539 record.mListener.onReceived(srcAddress, params, hasVendorId); 1540 } catch (RemoteException e) { 1541 Slog.e(TAG, "Failed to notify vendor command reception", e); 1542 } 1543 } 1544 } 1545 } 1546 1547 boolean isProhibitMode() { 1548 synchronized (mLock) { 1549 return mProhibitMode; 1550 } 1551 } 1552 1553 void setProhibitMode(boolean enabled) { 1554 synchronized (mLock) { 1555 mProhibitMode = enabled; 1556 } 1557 } 1558 1559 @ServiceThreadOnly 1560 private void handleHdmiControlStatusChanged(boolean enabled) { 1561 assertRunOnServiceThread(); 1562 1563 int value = enabled ? HdmiTvClient.ENABLED : HdmiTvClient.DISABLED; 1564 mCecController.setOption(HdmiTvClient.OPTION_CEC_ENABLE, value); 1565 if (mMhlController != null) { 1566 mMhlController.setOption(HdmiTvClient.OPTION_MHL_ENABLE, value); 1567 } 1568 1569 synchronized (mLock) { 1570 mHdmiControlEnabled = enabled; 1571 } 1572 1573 if (enabled) { 1574 initializeCec(INITIATED_BY_ENABLE_CEC); 1575 } else { 1576 disableDevices(new PendingActionClearedCallback() { 1577 @Override 1578 public void onCleared(HdmiCecLocalDevice device) { 1579 assertRunOnServiceThread(); 1580 clearLocalDevices(); 1581 } 1582 }); 1583 } 1584 } 1585} 1586