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