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