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