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