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