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