HdmiControlService.java revision 964c00dd7b270dcf80aea3450bbfc23502965cce
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 return new HdmiDeviceInfo(activePath, tv.getActivePortId()); 1196 } 1197 return null; 1198 } 1199 1200 @Override 1201 public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) { 1202 enforceAccessPermission(); 1203 runOnServiceThread(new Runnable() { 1204 @Override 1205 public void run() { 1206 if (callback == null) { 1207 Slog.e(TAG, "Callback cannot be null"); 1208 return; 1209 } 1210 HdmiCecLocalDeviceTv tv = tv(); 1211 if (tv == null) { 1212 Slog.w(TAG, "Local tv device not available"); 1213 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1214 return; 1215 } 1216 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId); 1217 if (device != null) { 1218 if (device.getPortId() == tv.getActivePortId()) { 1219 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); 1220 return; 1221 } 1222 // Upon selecting MHL device, we send RAP[Content On] to wake up 1223 // the connected mobile device, start routing control to switch ports. 1224 // callback is handled by MHL action. 1225 device.turnOn(callback); 1226 tv.doManualPortSwitching(device.getPortId(), null); 1227 return; 1228 } 1229 tv.deviceSelect(deviceId, callback); 1230 } 1231 }); 1232 } 1233 1234 @Override 1235 public void portSelect(final int portId, final IHdmiControlCallback callback) { 1236 enforceAccessPermission(); 1237 runOnServiceThread(new Runnable() { 1238 @Override 1239 public void run() { 1240 if (callback == null) { 1241 Slog.e(TAG, "Callback cannot be null"); 1242 return; 1243 } 1244 HdmiCecLocalDeviceTv tv = tv(); 1245 if (tv == null) { 1246 Slog.w(TAG, "Local tv device not available"); 1247 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1248 return; 1249 } 1250 tv.doManualPortSwitching(portId, callback); 1251 } 1252 }); 1253 } 1254 1255 @Override 1256 public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) { 1257 enforceAccessPermission(); 1258 runOnServiceThread(new Runnable() { 1259 @Override 1260 public void run() { 1261 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId); 1262 if (device != null) { 1263 device.sendKeyEvent(keyCode, isPressed); 1264 return; 1265 } 1266 if (mCecController != null) { 1267 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType); 1268 if (localDevice == null) { 1269 Slog.w(TAG, "Local device not available"); 1270 return; 1271 } 1272 localDevice.sendKeyEvent(keyCode, isPressed); 1273 } 1274 } 1275 }); 1276 } 1277 1278 @Override 1279 public void oneTouchPlay(final IHdmiControlCallback callback) { 1280 enforceAccessPermission(); 1281 runOnServiceThread(new Runnable() { 1282 @Override 1283 public void run() { 1284 HdmiControlService.this.oneTouchPlay(callback); 1285 } 1286 }); 1287 } 1288 1289 @Override 1290 public void queryDisplayStatus(final IHdmiControlCallback callback) { 1291 enforceAccessPermission(); 1292 runOnServiceThread(new Runnable() { 1293 @Override 1294 public void run() { 1295 HdmiControlService.this.queryDisplayStatus(callback); 1296 } 1297 }); 1298 } 1299 1300 @Override 1301 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 1302 enforceAccessPermission(); 1303 HdmiControlService.this.addHotplugEventListener(listener); 1304 } 1305 1306 @Override 1307 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) { 1308 enforceAccessPermission(); 1309 HdmiControlService.this.removeHotplugEventListener(listener); 1310 } 1311 1312 @Override 1313 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) { 1314 enforceAccessPermission(); 1315 HdmiControlService.this.addDeviceEventListener(listener); 1316 } 1317 1318 @Override 1319 public List<HdmiPortInfo> getPortInfo() { 1320 enforceAccessPermission(); 1321 return HdmiControlService.this.getPortInfo(); 1322 } 1323 1324 @Override 1325 public boolean canChangeSystemAudioMode() { 1326 enforceAccessPermission(); 1327 HdmiCecLocalDeviceTv tv = tv(); 1328 if (tv == null) { 1329 return false; 1330 } 1331 return tv.hasSystemAudioDevice(); 1332 } 1333 1334 @Override 1335 public boolean getSystemAudioMode() { 1336 enforceAccessPermission(); 1337 HdmiCecLocalDeviceTv tv = tv(); 1338 if (tv == null) { 1339 return false; 1340 } 1341 return tv.isSystemAudioActivated(); 1342 } 1343 1344 @Override 1345 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) { 1346 enforceAccessPermission(); 1347 runOnServiceThread(new Runnable() { 1348 @Override 1349 public void run() { 1350 HdmiCecLocalDeviceTv tv = tv(); 1351 if (tv == null) { 1352 Slog.w(TAG, "Local tv device not available"); 1353 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1354 return; 1355 } 1356 tv.changeSystemAudioMode(enabled, callback); 1357 } 1358 }); 1359 } 1360 1361 @Override 1362 public void addSystemAudioModeChangeListener( 1363 final IHdmiSystemAudioModeChangeListener listener) { 1364 enforceAccessPermission(); 1365 HdmiControlService.this.addSystemAudioModeChangeListner(listener); 1366 } 1367 1368 @Override 1369 public void removeSystemAudioModeChangeListener( 1370 final IHdmiSystemAudioModeChangeListener listener) { 1371 enforceAccessPermission(); 1372 HdmiControlService.this.removeSystemAudioModeChangeListener(listener); 1373 } 1374 1375 @Override 1376 public void setInputChangeListener(final IHdmiInputChangeListener listener) { 1377 enforceAccessPermission(); 1378 HdmiControlService.this.setInputChangeListener(listener); 1379 } 1380 1381 @Override 1382 public List<HdmiDeviceInfo> getInputDevices() { 1383 enforceAccessPermission(); 1384 // No need to hold the lock for obtaining TV device as the local device instance 1385 // is preserved while the HDMI control is enabled. 1386 HdmiCecLocalDeviceTv tv = tv(); 1387 synchronized (mLock) { 1388 List<HdmiDeviceInfo> cecDevices = (tv == null) 1389 ? Collections.<HdmiDeviceInfo>emptyList() 1390 : tv.getSafeExternalInputsLocked(); 1391 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked()); 1392 } 1393 } 1394 1395 // Returns all the CEC devices on the bus including system audio, switch, 1396 // even those of reserved type. 1397 @Override 1398 public List<HdmiDeviceInfo> getDeviceList() { 1399 enforceAccessPermission(); 1400 HdmiCecLocalDeviceTv tv = tv(); 1401 synchronized (mLock) { 1402 return (tv == null) 1403 ? Collections.<HdmiDeviceInfo>emptyList() 1404 : tv.getSafeCecDevicesLocked(); 1405 } 1406 } 1407 1408 @Override 1409 public void setSystemAudioVolume(final int oldIndex, final int newIndex, 1410 final int maxIndex) { 1411 enforceAccessPermission(); 1412 runOnServiceThread(new Runnable() { 1413 @Override 1414 public void run() { 1415 HdmiCecLocalDeviceTv tv = tv(); 1416 if (tv == null) { 1417 Slog.w(TAG, "Local tv device not available"); 1418 return; 1419 } 1420 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex); 1421 } 1422 }); 1423 } 1424 1425 @Override 1426 public void setSystemAudioMute(final boolean mute) { 1427 enforceAccessPermission(); 1428 runOnServiceThread(new Runnable() { 1429 @Override 1430 public void run() { 1431 HdmiCecLocalDeviceTv tv = tv(); 1432 if (tv == null) { 1433 Slog.w(TAG, "Local tv device not available"); 1434 return; 1435 } 1436 tv.changeMute(mute); 1437 } 1438 }); 1439 } 1440 1441 @Override 1442 public void setArcMode(final boolean enabled) { 1443 enforceAccessPermission(); 1444 runOnServiceThread(new Runnable() { 1445 @Override 1446 public void run() { 1447 HdmiCecLocalDeviceTv tv = tv(); 1448 if (tv == null) { 1449 Slog.w(TAG, "Local tv device not available to change arc mode."); 1450 return; 1451 } 1452 } 1453 }); 1454 } 1455 1456 @Override 1457 public void setProhibitMode(final boolean enabled) { 1458 enforceAccessPermission(); 1459 if (!isTvDevice()) { 1460 return; 1461 } 1462 HdmiControlService.this.setProhibitMode(enabled); 1463 } 1464 1465 @Override 1466 public void addVendorCommandListener(final IHdmiVendorCommandListener listener, 1467 final int deviceType) { 1468 enforceAccessPermission(); 1469 HdmiControlService.this.addVendorCommandListener(listener, deviceType); 1470 } 1471 1472 @Override 1473 public void sendVendorCommand(final int deviceType, final int targetAddress, 1474 final byte[] params, final boolean hasVendorId) { 1475 enforceAccessPermission(); 1476 runOnServiceThread(new Runnable() { 1477 @Override 1478 public void run() { 1479 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType); 1480 if (device == null) { 1481 Slog.w(TAG, "Local device not available"); 1482 return; 1483 } 1484 if (hasVendorId) { 1485 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId( 1486 device.getDeviceInfo().getLogicalAddress(), targetAddress, 1487 getVendorId(), params)); 1488 } else { 1489 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand( 1490 device.getDeviceInfo().getLogicalAddress(), targetAddress, params)); 1491 } 1492 } 1493 }); 1494 } 1495 1496 @Override 1497 public void sendStandby(final int deviceType, final int deviceId) { 1498 enforceAccessPermission(); 1499 runOnServiceThread(new Runnable() { 1500 @Override 1501 public void run() { 1502 HdmiMhlLocalDeviceStub mhlDevice = mMhlController.getLocalDeviceById(deviceId); 1503 if (mhlDevice != null) { 1504 mhlDevice.sendStandby(); 1505 return; 1506 } 1507 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType); 1508 if (device == null) { 1509 Slog.w(TAG, "Local device not available"); 1510 return; 1511 } 1512 device.sendStandby(deviceId); 1513 } 1514 }); 1515 } 1516 1517 @Override 1518 public void setHdmiRecordListener(IHdmiRecordListener listener) { 1519 enforceAccessPermission(); 1520 HdmiControlService.this.setHdmiRecordListener(listener); 1521 } 1522 1523 @Override 1524 public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) { 1525 enforceAccessPermission(); 1526 runOnServiceThread(new Runnable() { 1527 @Override 1528 public void run() { 1529 if (!isTvDeviceEnabled()) { 1530 Slog.w(TAG, "TV device is not enabled."); 1531 return; 1532 } 1533 tv().startOneTouchRecord(recorderAddress, recordSource); 1534 } 1535 }); 1536 } 1537 1538 @Override 1539 public void stopOneTouchRecord(final int recorderAddress) { 1540 enforceAccessPermission(); 1541 runOnServiceThread(new Runnable() { 1542 @Override 1543 public void run() { 1544 if (!isTvDeviceEnabled()) { 1545 Slog.w(TAG, "TV device is not enabled."); 1546 return; 1547 } 1548 tv().stopOneTouchRecord(recorderAddress); 1549 } 1550 }); 1551 } 1552 1553 @Override 1554 public void startTimerRecording(final int recorderAddress, final int sourceType, 1555 final byte[] recordSource) { 1556 enforceAccessPermission(); 1557 runOnServiceThread(new Runnable() { 1558 @Override 1559 public void run() { 1560 if (!isTvDeviceEnabled()) { 1561 Slog.w(TAG, "TV device is not enabled."); 1562 return; 1563 } 1564 tv().startTimerRecording(recorderAddress, sourceType, recordSource); 1565 } 1566 }); 1567 } 1568 1569 @Override 1570 public void clearTimerRecording(final int recorderAddress, final int sourceType, 1571 final byte[] recordSource) { 1572 enforceAccessPermission(); 1573 runOnServiceThread(new Runnable() { 1574 @Override 1575 public void run() { 1576 if (!isTvDeviceEnabled()) { 1577 Slog.w(TAG, "TV device is not enabled."); 1578 return; 1579 } 1580 tv().clearTimerRecording(recorderAddress, sourceType, recordSource); 1581 } 1582 }); 1583 } 1584 1585 @Override 1586 public void sendMhlVendorCommand(final int portId, final int offset, final int length, 1587 final byte[] data) { 1588 enforceAccessPermission(); 1589 runOnServiceThread(new Runnable() { 1590 @Override 1591 public void run() { 1592 if (!isControlEnabled()) { 1593 Slog.w(TAG, "Hdmi control is disabled."); 1594 return ; 1595 } 1596 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); 1597 if (device == null) { 1598 Slog.w(TAG, "Invalid port id:" + portId); 1599 return; 1600 } 1601 mMhlController.sendVendorCommand(portId, offset, length, data); 1602 } 1603 }); 1604 } 1605 1606 @Override 1607 public void addHdmiMhlVendorCommandListener( 1608 IHdmiMhlVendorCommandListener listener) { 1609 enforceAccessPermission(); 1610 HdmiControlService.this.addHdmiMhlVendorCommandListener(listener); 1611 } 1612 1613 @Override 1614 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) { 1615 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); 1616 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); 1617 1618 pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled); 1619 pw.println("mProhibitMode: " + mProhibitMode); 1620 if (mCecController != null) { 1621 pw.println("mCecController: "); 1622 pw.increaseIndent(); 1623 mCecController.dump(pw); 1624 pw.decreaseIndent(); 1625 } 1626 1627 pw.println("mMhlController: "); 1628 pw.increaseIndent(); 1629 mMhlController.dump(pw); 1630 pw.decreaseIndent(); 1631 1632 pw.println("mPortInfo: "); 1633 pw.increaseIndent(); 1634 for (HdmiPortInfo hdmiPortInfo : mPortInfo) { 1635 pw.println("- " + hdmiPortInfo); 1636 } 1637 pw.decreaseIndent(); 1638 pw.println("mPowerStatus: " + mPowerStatus); 1639 } 1640 } 1641 1642 @ServiceThreadOnly 1643 private void oneTouchPlay(final IHdmiControlCallback callback) { 1644 assertRunOnServiceThread(); 1645 HdmiCecLocalDevicePlayback source = playback(); 1646 if (source == null) { 1647 Slog.w(TAG, "Local playback device not available"); 1648 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1649 return; 1650 } 1651 source.oneTouchPlay(callback); 1652 } 1653 1654 @ServiceThreadOnly 1655 private void queryDisplayStatus(final IHdmiControlCallback callback) { 1656 assertRunOnServiceThread(); 1657 HdmiCecLocalDevicePlayback source = playback(); 1658 if (source == null) { 1659 Slog.w(TAG, "Local playback device not available"); 1660 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1661 return; 1662 } 1663 source.queryDisplayStatus(callback); 1664 } 1665 1666 private void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 1667 final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); 1668 try { 1669 listener.asBinder().linkToDeath(record, 0); 1670 } catch (RemoteException e) { 1671 Slog.w(TAG, "Listener already died"); 1672 return; 1673 } 1674 synchronized (mLock) { 1675 mHotplugEventListenerRecords.add(record); 1676 } 1677 1678 // Inform the listener of the initial state of each HDMI port by generating 1679 // hotplug events. 1680 runOnServiceThread(new Runnable() { 1681 @Override 1682 public void run() { 1683 synchronized (mLock) { 1684 if (!mHotplugEventListenerRecords.contains(record)) return; 1685 } 1686 for (HdmiPortInfo port : mPortInfo) { 1687 HdmiHotplugEvent event = new HdmiHotplugEvent(port.getId(), 1688 mCecController.isConnected(port.getId())); 1689 synchronized (mLock) { 1690 invokeHotplugEventListenerLocked(listener, event); 1691 } 1692 } 1693 } 1694 }); 1695 } 1696 1697 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) { 1698 synchronized (mLock) { 1699 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 1700 if (record.mListener.asBinder() == listener.asBinder()) { 1701 listener.asBinder().unlinkToDeath(record, 0); 1702 mHotplugEventListenerRecords.remove(record); 1703 break; 1704 } 1705 } 1706 } 1707 } 1708 1709 private void addDeviceEventListener(IHdmiDeviceEventListener listener) { 1710 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener); 1711 try { 1712 listener.asBinder().linkToDeath(record, 0); 1713 } catch (RemoteException e) { 1714 Slog.w(TAG, "Listener already died"); 1715 return; 1716 } 1717 synchronized (mLock) { 1718 mDeviceEventListenerRecords.add(record); 1719 } 1720 } 1721 1722 void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) { 1723 synchronized (mLock) { 1724 for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) { 1725 try { 1726 record.mListener.onStatusChanged(device, status); 1727 } catch (RemoteException e) { 1728 Slog.e(TAG, "Failed to report device event:" + e); 1729 } 1730 } 1731 } 1732 } 1733 1734 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) { 1735 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord( 1736 listener); 1737 try { 1738 listener.asBinder().linkToDeath(record, 0); 1739 } catch (RemoteException e) { 1740 Slog.w(TAG, "Listener already died"); 1741 return; 1742 } 1743 synchronized (mLock) { 1744 mSystemAudioModeChangeListenerRecords.add(record); 1745 } 1746 } 1747 1748 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) { 1749 synchronized (mLock) { 1750 for (SystemAudioModeChangeListenerRecord record : 1751 mSystemAudioModeChangeListenerRecords) { 1752 if (record.mListener.asBinder() == listener) { 1753 listener.asBinder().unlinkToDeath(record, 0); 1754 mSystemAudioModeChangeListenerRecords.remove(record); 1755 break; 1756 } 1757 } 1758 } 1759 } 1760 1761 private final class InputChangeListenerRecord implements IBinder.DeathRecipient { 1762 private final IHdmiInputChangeListener mListener; 1763 1764 public InputChangeListenerRecord(IHdmiInputChangeListener listener) { 1765 mListener = listener; 1766 } 1767 1768 @Override 1769 public void binderDied() { 1770 synchronized (mLock) { 1771 mInputChangeListenerRecord = null; 1772 } 1773 } 1774 } 1775 1776 private void setInputChangeListener(IHdmiInputChangeListener listener) { 1777 synchronized (mLock) { 1778 mInputChangeListenerRecord = new InputChangeListenerRecord(listener); 1779 try { 1780 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0); 1781 } catch (RemoteException e) { 1782 Slog.w(TAG, "Listener already died"); 1783 return; 1784 } 1785 } 1786 } 1787 1788 void invokeInputChangeListener(HdmiDeviceInfo info) { 1789 synchronized (mLock) { 1790 if (mInputChangeListenerRecord != null) { 1791 try { 1792 mInputChangeListenerRecord.mListener.onChanged(info); 1793 } catch (RemoteException e) { 1794 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e); 1795 } 1796 } 1797 } 1798 } 1799 1800 private void setHdmiRecordListener(IHdmiRecordListener listener) { 1801 synchronized (mLock) { 1802 mRecordListenerRecord = new HdmiRecordListenerRecord(listener); 1803 try { 1804 listener.asBinder().linkToDeath(mRecordListenerRecord, 0); 1805 } catch (RemoteException e) { 1806 Slog.w(TAG, "Listener already died.", e); 1807 } 1808 } 1809 } 1810 1811 byte[] invokeRecordRequestListener(int recorderAddress) { 1812 synchronized (mLock) { 1813 if (mRecordListenerRecord != null) { 1814 try { 1815 return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress); 1816 } catch (RemoteException e) { 1817 Slog.w(TAG, "Failed to start record.", e); 1818 } 1819 } 1820 return EmptyArray.BYTE; 1821 } 1822 } 1823 1824 void invokeOneTouchRecordResult(int recorderAddress, int result) { 1825 synchronized (mLock) { 1826 if (mRecordListenerRecord != null) { 1827 try { 1828 mRecordListenerRecord.mListener.onOneTouchRecordResult(recorderAddress, result); 1829 } catch (RemoteException e) { 1830 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e); 1831 } 1832 } 1833 } 1834 } 1835 1836 void invokeTimerRecordingResult(int recorderAddress, int result) { 1837 synchronized (mLock) { 1838 if (mRecordListenerRecord != null) { 1839 try { 1840 mRecordListenerRecord.mListener.onTimerRecordingResult(recorderAddress, result); 1841 } catch (RemoteException e) { 1842 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e); 1843 } 1844 } 1845 } 1846 } 1847 1848 void invokeClearTimerRecordingResult(int recorderAddress, int result) { 1849 synchronized (mLock) { 1850 if (mRecordListenerRecord != null) { 1851 try { 1852 mRecordListenerRecord.mListener.onClearTimerRecordingResult(recorderAddress, 1853 result); 1854 } catch (RemoteException e) { 1855 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e); 1856 } 1857 } 1858 } 1859 } 1860 1861 private void invokeCallback(IHdmiControlCallback callback, int result) { 1862 try { 1863 callback.onComplete(result); 1864 } catch (RemoteException e) { 1865 Slog.e(TAG, "Invoking callback failed:" + e); 1866 } 1867 } 1868 1869 private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener, 1870 boolean enabled) { 1871 try { 1872 listener.onStatusChanged(enabled); 1873 } catch (RemoteException e) { 1874 Slog.e(TAG, "Invoking callback failed:" + e); 1875 } 1876 } 1877 1878 private void announceHotplugEvent(int portId, boolean connected) { 1879 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected); 1880 synchronized (mLock) { 1881 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 1882 invokeHotplugEventListenerLocked(record.mListener, event); 1883 } 1884 } 1885 } 1886 1887 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener, 1888 HdmiHotplugEvent event) { 1889 try { 1890 listener.onReceived(event); 1891 } catch (RemoteException e) { 1892 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e); 1893 } 1894 } 1895 1896 private HdmiCecLocalDeviceTv tv() { 1897 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV); 1898 } 1899 1900 boolean isTvDevice() { 1901 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV); 1902 } 1903 1904 boolean isTvDeviceEnabled() { 1905 return isTvDevice() && tv() != null; 1906 } 1907 1908 private HdmiCecLocalDevicePlayback playback() { 1909 return (HdmiCecLocalDevicePlayback) 1910 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK); 1911 } 1912 1913 AudioManager getAudioManager() { 1914 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); 1915 } 1916 1917 boolean isControlEnabled() { 1918 synchronized (mLock) { 1919 return mHdmiControlEnabled; 1920 } 1921 } 1922 1923 @ServiceThreadOnly 1924 int getPowerStatus() { 1925 assertRunOnServiceThread(); 1926 return mPowerStatus; 1927 } 1928 1929 @ServiceThreadOnly 1930 boolean isPowerOnOrTransient() { 1931 assertRunOnServiceThread(); 1932 return mPowerStatus == HdmiControlManager.POWER_STATUS_ON 1933 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; 1934 } 1935 1936 @ServiceThreadOnly 1937 boolean isPowerStandbyOrTransient() { 1938 assertRunOnServiceThread(); 1939 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY 1940 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY; 1941 } 1942 1943 @ServiceThreadOnly 1944 boolean isPowerStandby() { 1945 assertRunOnServiceThread(); 1946 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY; 1947 } 1948 1949 @ServiceThreadOnly 1950 void wakeUp() { 1951 assertRunOnServiceThread(); 1952 mWakeUpMessageReceived = true; 1953 mPowerManager.wakeUp(SystemClock.uptimeMillis()); 1954 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets 1955 // the intent, the sequence will continue at onWakeUp(). 1956 } 1957 1958 @ServiceThreadOnly 1959 void standby() { 1960 assertRunOnServiceThread(); 1961 mStandbyMessageReceived = true; 1962 mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0); 1963 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets 1964 // the intent, the sequence will continue at onStandby(). 1965 } 1966 1967 @ServiceThreadOnly 1968 private void onWakeUp() { 1969 assertRunOnServiceThread(); 1970 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; 1971 if (mCecController != null) { 1972 if (mHdmiControlEnabled) { 1973 int startReason = INITIATED_BY_SCREEN_ON; 1974 if (mWakeUpMessageReceived) { 1975 startReason = INITIATED_BY_WAKE_UP_MESSAGE; 1976 } 1977 initializeCec(startReason); 1978 } 1979 } else { 1980 Slog.i(TAG, "Device does not support HDMI-CEC."); 1981 } 1982 // TODO: Initialize MHL local devices. 1983 } 1984 1985 @ServiceThreadOnly 1986 private void onStandby() { 1987 assertRunOnServiceThread(); 1988 if (!canGoToStandby()) return; 1989 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY; 1990 invokeVendorCommandListenersOnControlStateChanged(false, 1991 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY); 1992 1993 final List<HdmiCecLocalDevice> devices = getAllLocalDevices(); 1994 disableDevices(new PendingActionClearedCallback() { 1995 @Override 1996 public void onCleared(HdmiCecLocalDevice device) { 1997 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType); 1998 devices.remove(device); 1999 if (devices.isEmpty()) { 2000 onStandbyCompleted(); 2001 // We will not clear local devices here, since some OEM/SOC will keep passing 2002 // the received packets until the application processor enters to the sleep 2003 // actually. 2004 } 2005 } 2006 }); 2007 } 2008 2009 private boolean canGoToStandby() { 2010 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 2011 if (!device.canGoToStandby()) return false; 2012 } 2013 return true; 2014 } 2015 2016 @ServiceThreadOnly 2017 private void onLanguageChanged(String language) { 2018 assertRunOnServiceThread(); 2019 mLanguage = language; 2020 2021 if (isTvDeviceEnabled()) { 2022 tv().broadcastMenuLanguage(language); 2023 } 2024 } 2025 2026 @ServiceThreadOnly 2027 String getLanguage() { 2028 assertRunOnServiceThread(); 2029 return mLanguage; 2030 } 2031 2032 private void disableDevices(PendingActionClearedCallback callback) { 2033 if (mCecController != null) { 2034 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 2035 device.disableDevice(mStandbyMessageReceived, callback); 2036 } 2037 } 2038 2039 mMhlController.clearAllLocalDevices(); 2040 } 2041 2042 @ServiceThreadOnly 2043 private void clearLocalDevices() { 2044 assertRunOnServiceThread(); 2045 if (mCecController == null) { 2046 return; 2047 } 2048 mCecController.clearLogicalAddress(); 2049 mCecController.clearLocalDevices(); 2050 } 2051 2052 @ServiceThreadOnly 2053 private void onStandbyCompleted() { 2054 assertRunOnServiceThread(); 2055 Slog.v(TAG, "onStandbyCompleted"); 2056 2057 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) { 2058 return; 2059 } 2060 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; 2061 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 2062 device.onStandby(mStandbyMessageReceived); 2063 } 2064 mStandbyMessageReceived = false; 2065 mAddressAllocated = false; 2066 mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, DISABLED); 2067 } 2068 2069 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) { 2070 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType); 2071 try { 2072 listener.asBinder().linkToDeath(record, 0); 2073 } catch (RemoteException e) { 2074 Slog.w(TAG, "Listener already died"); 2075 return; 2076 } 2077 synchronized (mLock) { 2078 mVendorCommandListenerRecords.add(record); 2079 } 2080 } 2081 2082 boolean invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress, 2083 byte[] params, boolean hasVendorId) { 2084 synchronized (mLock) { 2085 if (mVendorCommandListenerRecords.isEmpty()) { 2086 return false; 2087 } 2088 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) { 2089 if (record.mDeviceType != deviceType) { 2090 continue; 2091 } 2092 try { 2093 record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId); 2094 } catch (RemoteException e) { 2095 Slog.e(TAG, "Failed to notify vendor command reception", e); 2096 } 2097 } 2098 return true; 2099 } 2100 } 2101 2102 boolean invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason) { 2103 synchronized (mLock) { 2104 if (mVendorCommandListenerRecords.isEmpty()) { 2105 return false; 2106 } 2107 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) { 2108 try { 2109 record.mListener.onControlStateChanged(enabled, reason); 2110 } catch (RemoteException e) { 2111 Slog.e(TAG, "Failed to notify control-state-changed to vendor handler", e); 2112 } 2113 } 2114 return true; 2115 } 2116 } 2117 2118 private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) { 2119 HdmiMhlVendorCommandListenerRecord record = 2120 new HdmiMhlVendorCommandListenerRecord(listener); 2121 try { 2122 listener.asBinder().linkToDeath(record, 0); 2123 } catch (RemoteException e) { 2124 Slog.w(TAG, "Listener already died."); 2125 return; 2126 } 2127 2128 synchronized (mLock) { 2129 mMhlVendorCommandListenerRecords.add(record); 2130 } 2131 } 2132 2133 void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) { 2134 synchronized (mLock) { 2135 for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) { 2136 try { 2137 record.mListener.onReceived(portId, offest, length, data); 2138 } catch (RemoteException e) { 2139 Slog.e(TAG, "Failed to notify MHL vendor command", e); 2140 } 2141 } 2142 } 2143 } 2144 2145 boolean isProhibitMode() { 2146 synchronized (mLock) { 2147 return mProhibitMode; 2148 } 2149 } 2150 2151 void setProhibitMode(boolean enabled) { 2152 synchronized (mLock) { 2153 mProhibitMode = enabled; 2154 } 2155 } 2156 2157 @ServiceThreadOnly 2158 void setCecOption(int key, int value) { 2159 assertRunOnServiceThread(); 2160 mCecController.setOption(key, value); 2161 } 2162 2163 @ServiceThreadOnly 2164 void setControlEnabled(boolean enabled) { 2165 assertRunOnServiceThread(); 2166 2167 synchronized (mLock) { 2168 mHdmiControlEnabled = enabled; 2169 } 2170 2171 if (enabled) { 2172 enableHdmiControlService(); 2173 return; 2174 } 2175 // Call the vendor handler before the service is disabled. 2176 invokeVendorCommandListenersOnControlStateChanged(false, 2177 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING); 2178 // Post the remained tasks in the service thread again to give the vendor-issued-tasks 2179 // a chance to run. 2180 runOnServiceThread(new Runnable() { 2181 @Override 2182 public void run() { 2183 disableHdmiControlService(); 2184 } 2185 }); 2186 return; 2187 } 2188 2189 @ServiceThreadOnly 2190 private void enableHdmiControlService() { 2191 mCecController.setOption(OPTION_CEC_ENABLE, ENABLED); 2192 mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED); 2193 2194 initializeCec(INITIATED_BY_ENABLE_CEC); 2195 } 2196 2197 @ServiceThreadOnly 2198 private void disableHdmiControlService() { 2199 disableDevices(new PendingActionClearedCallback() { 2200 @Override 2201 public void onCleared(HdmiCecLocalDevice device) { 2202 assertRunOnServiceThread(); 2203 mCecController.flush(new Runnable() { 2204 @Override 2205 public void run() { 2206 mCecController.setOption(OPTION_CEC_ENABLE, DISABLED); 2207 mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED); 2208 clearLocalDevices(); 2209 } 2210 }); 2211 } 2212 }); 2213 } 2214 2215 @ServiceThreadOnly 2216 void setActivePortId(int portId) { 2217 assertRunOnServiceThread(); 2218 mActivePortId = portId; 2219 2220 // Resets last input for MHL, which stays valid only after the MHL device was selected, 2221 // and no further switching is done. 2222 setLastInputForMhl(Constants.INVALID_PORT_ID); 2223 } 2224 2225 @ServiceThreadOnly 2226 void setLastInputForMhl(int portId) { 2227 assertRunOnServiceThread(); 2228 mLastInputMhl = portId; 2229 } 2230 2231 @ServiceThreadOnly 2232 int getLastInputForMhl() { 2233 assertRunOnServiceThread(); 2234 return mLastInputMhl; 2235 } 2236 2237 /** 2238 * Performs input change, routing control for MHL device. 2239 * 2240 * @param portId MHL port, or the last port to go back to if {@code contentOn} is false 2241 * @param contentOn {@code true} if RAP data is content on; otherwise false 2242 */ 2243 @ServiceThreadOnly 2244 void changeInputForMhl(int portId, boolean contentOn) { 2245 assertRunOnServiceThread(); 2246 if (tv() == null) return; 2247 final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID; 2248 tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() { 2249 @Override 2250 public void onComplete(int result) throws RemoteException { 2251 // Keep the last input to switch back later when RAP[ContentOff] is received. 2252 // This effectively sets the port to invalid one if the switching is for 2253 // RAP[ContentOff]. 2254 setLastInputForMhl(lastInput); 2255 } 2256 }); 2257 2258 // MHL device is always directly connected to the port. Update the active port ID to avoid 2259 // unnecessary post-routing control task. 2260 tv().setActivePortId(portId); 2261 2262 // The port is either the MHL-enabled port where the mobile device is connected, or 2263 // the last port to go back to when turnoff command is received. Note that the last port 2264 // may not be the MHL-enabled one. In this case the device info to be passed to 2265 // input change listener should be the one describing the corresponding HDMI port. 2266 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); 2267 HdmiDeviceInfo info = (device != null) ? device.getInfo() : mPortDeviceMap.get(portId); 2268 invokeInputChangeListener(info); 2269 } 2270 2271 void setMhlInputChangeEnabled(boolean enabled) { 2272 mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled)); 2273 2274 synchronized (mLock) { 2275 mMhlInputChangeEnabled = enabled; 2276 } 2277 } 2278 2279 boolean isMhlInputChangeEnabled() { 2280 synchronized (mLock) { 2281 return mMhlInputChangeEnabled; 2282 } 2283 } 2284 2285 @ServiceThreadOnly 2286 void displayOsd(int messageId) { 2287 assertRunOnServiceThread(); 2288 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE); 2289 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId); 2290 getContext().sendBroadcastAsUser(intent, UserHandle.ALL, 2291 HdmiControlService.PERMISSION); 2292 } 2293 2294 @ServiceThreadOnly 2295 void displayOsd(int messageId, int extra) { 2296 assertRunOnServiceThread(); 2297 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE); 2298 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId); 2299 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra); 2300 getContext().sendBroadcastAsUser(intent, UserHandle.ALL, 2301 HdmiControlService.PERMISSION); 2302 } 2303} 2304