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