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