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