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