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