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