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