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