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