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