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