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 */ onSendCompleted(int error)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 */ onPollingFinished(List<Integer> ackedAddress)138 void onPollingFinished(List<Integer> ackedAddress); 139 } 140 141 private class HdmiControlBroadcastReceiver extends BroadcastReceiver { 142 @ServiceThreadOnly 143 @Override onReceive(Context context, Intent intent)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 getMenuLanguage()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 bufferMessage(HdmiCecMessage message)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 processMessages()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 bufferActiveSource(HdmiCecMessage message)334 private void bufferActiveSource(HdmiCecMessage message) { 335 if (!replaceMessageIfBuffered(message, Constants.MESSAGE_ACTIVE_SOURCE)) { 336 mBuffer.add(message); 337 } 338 } 339 bufferImageOrTextViewOn(HdmiCecMessage message)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 replaceMessageIfBuffered(HdmiCecMessage message, int opcode)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 HdmiControlService(Context context)364 public HdmiControlService(Context context) { 365 super(context); 366 mLocalDevices = getIntList(SystemProperties.get(Constants.PROPERTY_DEVICE_TYPE)); 367 mSettingsObserver = new SettingsObserver(mHandler); 368 } 369 getIntList(String string)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 onStart()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 onBootPhase(int phase)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 getTvInputManager()436 TvInputManager getTvInputManager() { 437 return mTvInputManager; 438 } 439 registerTvInputCallback(TvInputCallback callback)440 void registerTvInputCallback(TvInputCallback callback) { 441 if (mTvInputManager == null) return; 442 mTvInputManager.registerCallback(callback, mHandler); 443 } 444 unregisterTvInputCallback(TvInputCallback callback)445 void unregisterTvInputCallback(TvInputCallback callback) { 446 if (mTvInputManager == null) return; 447 mTvInputManager.unregisterCallback(callback); 448 } 449 getPowerManager()450 PowerManager getPowerManager() { 451 return mPowerManager; 452 } 453 454 /** 455 * Called when the initialization of local devices is complete. 456 */ onInitializeCecComplete(int initiatedBy)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 registerContentObserver()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 { SettingsObserver(Handler handler)501 public SettingsObserver(Handler handler) { 502 super(handler); 503 } 504 505 // onChange is set up to run in service thread. 506 @Override onChange(boolean selfChange, Uri uri)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 toInt(boolean enabled)544 private static int toInt(boolean enabled) { 545 return enabled ? ENABLED : DISABLED; 546 } 547 readBooleanSetting(String key, boolean defVal)548 boolean readBooleanSetting(String key, boolean defVal) { 549 ContentResolver cr = getContext().getContentResolver(); 550 return Global.getInt(cr, key, toInt(defVal)) == ENABLED; 551 } 552 writeBooleanSetting(String key, boolean value)553 void writeBooleanSetting(String key, boolean value) { 554 ContentResolver cr = getContext().getContentResolver(); 555 Global.putInt(cr, key, toInt(value)); 556 } 557 initializeCec(int initiatedBy)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 initializeLocalDevices(final int initiatedBy)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 allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices, final int initiatedBy)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 notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy)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 isAddressAllocated()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 initPortInfo()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 getPortInfo()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 */ getPortInfo(int portId)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 */ portIdToPath(int portId)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 */ pathToPortId(int path)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 isValidPortId(int portId)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 */ getIoLooper()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 */ getServiceLooper()760 Looper getServiceLooper() { 761 return mHandler.getLooper(); 762 } 763 764 /** 765 * Returns physical address of the device. 766 */ getPhysicalAddress()767 int getPhysicalAddress() { 768 return mCecController.getPhysicalAddress(); 769 } 770 771 /** 772 * Returns vendor id of CEC service. 773 */ getVendorId()774 int getVendorId() { 775 return mCecController.getVendorId(); 776 } 777 778 @ServiceThreadOnly getDeviceInfo(int logicalAddress)779 HdmiDeviceInfo getDeviceInfo(int logicalAddress) { 780 assertRunOnServiceThread(); 781 return tv() == null ? null : tv().getCecDeviceInfo(logicalAddress); 782 } 783 784 @ServiceThreadOnly getDeviceInfoByPort(int port)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 */ getCecVersion()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 */ isConnectedToArcPort(int physicalAddress)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 isConnected(int portId)813 boolean isConnected(int portId) { 814 assertRunOnServiceThread(); 815 return mCecController.isConnected(portId); 816 } 817 runOnServiceThread(Runnable runnable)818 void runOnServiceThread(Runnable runnable) { 819 mHandler.post(runnable); 820 } 821 runOnServiceThreadAtFrontOfQueue(Runnable runnable)822 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) { 823 mHandler.postAtFrontOfQueue(runnable); 824 } 825 assertRunOnServiceThread()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 sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback)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 sendCecCommand(HdmiCecMessage command)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 maySendFeatureAbortCommand(HdmiCecMessage command, int reason)864 void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) { 865 assertRunOnServiceThread(); 866 mCecController.maySendFeatureAbortCommand(command, reason); 867 } 868 869 @ServiceThreadOnly handleCecCommand(HdmiCecMessage message)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 enableAudioReturnChannel(int portId, boolean enabled)888 void enableAudioReturnChannel(int portId, boolean enabled) { 889 mCecController.enableAudioReturnChannel(portId, enabled); 890 } 891 892 @ServiceThreadOnly dispatchMessageToLocalDevice(HdmiCecMessage message)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 onHotplug(int portId, boolean connected)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 pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, int retryCount)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 checkPollStrategy(int pickStrategy)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 getAllLocalDevices()967 List<HdmiCecLocalDevice> getAllLocalDevices() { 968 assertRunOnServiceThread(); 969 return mCecController.getLocalDeviceList(); 970 } 971 getServiceLock()972 Object getServiceLock() { 973 return mLock; 974 } 975 setAudioStatus(boolean mute, int volume)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 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 993 AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME); 994 } 995 } 996 announceSystemAudioModeChange(boolean enabled)997 void announceSystemAudioModeChange(boolean enabled) { 998 synchronized (mLock) { 999 for (SystemAudioModeChangeListenerRecord record : 1000 mSystemAudioModeChangeListenerRecords) { 1001 invokeSystemAudioModeChangeLocked(record.mListener, enabled); 1002 } 1003 } 1004 } 1005 createDeviceInfo(int logicalAddress, int deviceType, int powerStatus)1006 private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) { 1007 // TODO: find better name instead of model name. 1008 String displayName = Build.MODEL; 1009 return new HdmiDeviceInfo(logicalAddress, 1010 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType, 1011 getVendorId(), displayName); 1012 } 1013 1014 @ServiceThreadOnly handleMhlHotplugEvent(int portId, boolean connected)1015 void handleMhlHotplugEvent(int portId, boolean connected) { 1016 assertRunOnServiceThread(); 1017 // Hotplug event is used to add/remove MHL devices as TV input. 1018 if (connected) { 1019 HdmiMhlLocalDeviceStub newDevice = new HdmiMhlLocalDeviceStub(this, portId); 1020 HdmiMhlLocalDeviceStub oldDevice = mMhlController.addLocalDevice(newDevice); 1021 if (oldDevice != null) { 1022 oldDevice.onDeviceRemoved(); 1023 Slog.i(TAG, "Old device of port " + portId + " is removed"); 1024 } 1025 invokeDeviceEventListeners(newDevice.getInfo(), DEVICE_EVENT_ADD_DEVICE); 1026 updateSafeMhlInput(); 1027 } else { 1028 HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId); 1029 if (device != null) { 1030 device.onDeviceRemoved(); 1031 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE); 1032 updateSafeMhlInput(); 1033 } else { 1034 Slog.w(TAG, "No device to remove:[portId=" + portId); 1035 } 1036 } 1037 announceHotplugEvent(portId, connected); 1038 } 1039 1040 @ServiceThreadOnly handleMhlBusModeChanged(int portId, int busmode)1041 void handleMhlBusModeChanged(int portId, int busmode) { 1042 assertRunOnServiceThread(); 1043 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); 1044 if (device != null) { 1045 device.setBusMode(busmode); 1046 } else { 1047 Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId + 1048 ", busmode:" + busmode + "]"); 1049 } 1050 } 1051 1052 @ServiceThreadOnly handleMhlBusOvercurrent(int portId, boolean on)1053 void handleMhlBusOvercurrent(int portId, boolean on) { 1054 assertRunOnServiceThread(); 1055 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); 1056 if (device != null) { 1057 device.onBusOvercurrentDetected(on); 1058 } else { 1059 Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]"); 1060 } 1061 } 1062 1063 @ServiceThreadOnly handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId)1064 void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) { 1065 assertRunOnServiceThread(); 1066 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); 1067 1068 if (device != null) { 1069 device.setDeviceStatusChange(adopterId, deviceId); 1070 } else { 1071 Slog.w(TAG, "No mhl device exists for device status event[portId:" 1072 + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]"); 1073 } 1074 } 1075 1076 @ServiceThreadOnly updateSafeMhlInput()1077 private void updateSafeMhlInput() { 1078 assertRunOnServiceThread(); 1079 List<HdmiDeviceInfo> inputs = Collections.emptyList(); 1080 SparseArray<HdmiMhlLocalDeviceStub> devices = mMhlController.getAllLocalDevices(); 1081 for (int i = 0; i < devices.size(); ++i) { 1082 HdmiMhlLocalDeviceStub device = devices.valueAt(i); 1083 HdmiDeviceInfo info = device.getInfo(); 1084 if (info != null) { 1085 if (inputs.isEmpty()) { 1086 inputs = new ArrayList<>(); 1087 } 1088 inputs.add(device.getInfo()); 1089 } 1090 } 1091 synchronized (mLock) { 1092 mMhlDevices = inputs; 1093 } 1094 } 1095 getMhlDevicesLocked()1096 private List<HdmiDeviceInfo> getMhlDevicesLocked() { 1097 return mMhlDevices; 1098 } 1099 1100 private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient { 1101 private final IHdmiMhlVendorCommandListener mListener; 1102 HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener)1103 public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) { 1104 mListener = listener; 1105 } 1106 1107 @Override binderDied()1108 public void binderDied() { 1109 mMhlVendorCommandListenerRecords.remove(this); 1110 } 1111 } 1112 1113 // Record class that monitors the event of the caller of being killed. Used to clean up 1114 // the listener list and record list accordingly. 1115 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { 1116 private final IHdmiHotplugEventListener mListener; 1117 HotplugEventListenerRecord(IHdmiHotplugEventListener listener)1118 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) { 1119 mListener = listener; 1120 } 1121 1122 @Override binderDied()1123 public void binderDied() { 1124 synchronized (mLock) { 1125 mHotplugEventListenerRecords.remove(this); 1126 } 1127 } 1128 1129 @Override equals(Object obj)1130 public boolean equals(Object obj) { 1131 if (!(obj instanceof HotplugEventListenerRecord)) return false; 1132 if (obj == this) return true; 1133 HotplugEventListenerRecord other = (HotplugEventListenerRecord) obj; 1134 return other.mListener == this.mListener; 1135 } 1136 1137 @Override hashCode()1138 public int hashCode() { 1139 return mListener.hashCode(); 1140 } 1141 } 1142 1143 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient { 1144 private final IHdmiDeviceEventListener mListener; 1145 DeviceEventListenerRecord(IHdmiDeviceEventListener listener)1146 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) { 1147 mListener = listener; 1148 } 1149 1150 @Override binderDied()1151 public void binderDied() { 1152 synchronized (mLock) { 1153 mDeviceEventListenerRecords.remove(this); 1154 } 1155 } 1156 } 1157 1158 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient { 1159 private final IHdmiSystemAudioModeChangeListener mListener; 1160 SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener)1161 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) { 1162 mListener = listener; 1163 } 1164 1165 @Override binderDied()1166 public void binderDied() { 1167 synchronized (mLock) { 1168 mSystemAudioModeChangeListenerRecords.remove(this); 1169 } 1170 } 1171 } 1172 1173 class VendorCommandListenerRecord implements IBinder.DeathRecipient { 1174 private final IHdmiVendorCommandListener mListener; 1175 private final int mDeviceType; 1176 VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType)1177 public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) { 1178 mListener = listener; 1179 mDeviceType = deviceType; 1180 } 1181 1182 @Override binderDied()1183 public void binderDied() { 1184 synchronized (mLock) { 1185 mVendorCommandListenerRecords.remove(this); 1186 } 1187 } 1188 } 1189 1190 private class HdmiRecordListenerRecord implements IBinder.DeathRecipient { 1191 private final IHdmiRecordListener mListener; 1192 HdmiRecordListenerRecord(IHdmiRecordListener listener)1193 public HdmiRecordListenerRecord(IHdmiRecordListener listener) { 1194 mListener = listener; 1195 } 1196 1197 @Override binderDied()1198 public void binderDied() { 1199 synchronized (mLock) { 1200 if (mRecordListenerRecord == this) { 1201 mRecordListenerRecord = null; 1202 } 1203 } 1204 } 1205 } 1206 enforceAccessPermission()1207 private void enforceAccessPermission() { 1208 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); 1209 } 1210 1211 private final class BinderService extends IHdmiControlService.Stub { 1212 @Override getSupportedTypes()1213 public int[] getSupportedTypes() { 1214 enforceAccessPermission(); 1215 // mLocalDevices is an unmodifiable list - no lock necesary. 1216 int[] localDevices = new int[mLocalDevices.size()]; 1217 for (int i = 0; i < localDevices.length; ++i) { 1218 localDevices[i] = mLocalDevices.get(i); 1219 } 1220 return localDevices; 1221 } 1222 1223 @Override getActiveSource()1224 public HdmiDeviceInfo getActiveSource() { 1225 enforceAccessPermission(); 1226 HdmiCecLocalDeviceTv tv = tv(); 1227 if (tv == null) { 1228 Slog.w(TAG, "Local tv device not available"); 1229 return null; 1230 } 1231 ActiveSource activeSource = tv.getActiveSource(); 1232 if (activeSource.isValid()) { 1233 return new HdmiDeviceInfo(activeSource.logicalAddress, 1234 activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID, 1235 HdmiDeviceInfo.DEVICE_INACTIVE, 0, ""); 1236 } 1237 int activePath = tv.getActivePath(); 1238 if (activePath != HdmiDeviceInfo.PATH_INVALID) { 1239 HdmiDeviceInfo info = tv.getSafeDeviceInfoByPath(activePath); 1240 return (info != null) ? info : new HdmiDeviceInfo(activePath, tv.getActivePortId()); 1241 } 1242 return null; 1243 } 1244 1245 @Override deviceSelect(final int deviceId, final IHdmiControlCallback callback)1246 public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) { 1247 enforceAccessPermission(); 1248 runOnServiceThread(new Runnable() { 1249 @Override 1250 public void run() { 1251 if (callback == null) { 1252 Slog.e(TAG, "Callback cannot be null"); 1253 return; 1254 } 1255 HdmiCecLocalDeviceTv tv = tv(); 1256 if (tv == null) { 1257 if (!mAddressAllocated) { 1258 mSelectRequestBuffer.set(SelectRequestBuffer.newDeviceSelect( 1259 HdmiControlService.this, deviceId, callback)); 1260 return; 1261 } 1262 Slog.w(TAG, "Local tv device not available"); 1263 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1264 return; 1265 } 1266 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId); 1267 if (device != null) { 1268 if (device.getPortId() == tv.getActivePortId()) { 1269 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); 1270 return; 1271 } 1272 // Upon selecting MHL device, we send RAP[Content On] to wake up 1273 // the connected mobile device, start routing control to switch ports. 1274 // callback is handled by MHL action. 1275 device.turnOn(callback); 1276 tv.doManualPortSwitching(device.getPortId(), null); 1277 return; 1278 } 1279 tv.deviceSelect(deviceId, callback); 1280 } 1281 }); 1282 } 1283 1284 @Override portSelect(final int portId, final IHdmiControlCallback callback)1285 public void portSelect(final int portId, final IHdmiControlCallback callback) { 1286 enforceAccessPermission(); 1287 runOnServiceThread(new Runnable() { 1288 @Override 1289 public void run() { 1290 if (callback == null) { 1291 Slog.e(TAG, "Callback cannot be null"); 1292 return; 1293 } 1294 HdmiCecLocalDeviceTv tv = tv(); 1295 if (tv == null) { 1296 if (!mAddressAllocated) { 1297 mSelectRequestBuffer.set(SelectRequestBuffer.newPortSelect( 1298 HdmiControlService.this, portId, callback)); 1299 return; 1300 } 1301 Slog.w(TAG, "Local tv device not available"); 1302 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1303 return; 1304 } 1305 tv.doManualPortSwitching(portId, callback); 1306 } 1307 }); 1308 } 1309 1310 @Override sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed)1311 public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) { 1312 enforceAccessPermission(); 1313 runOnServiceThread(new Runnable() { 1314 @Override 1315 public void run() { 1316 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId); 1317 if (device != null) { 1318 device.sendKeyEvent(keyCode, isPressed); 1319 return; 1320 } 1321 if (mCecController != null) { 1322 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType); 1323 if (localDevice == null) { 1324 Slog.w(TAG, "Local device not available"); 1325 return; 1326 } 1327 localDevice.sendKeyEvent(keyCode, isPressed); 1328 } 1329 } 1330 }); 1331 } 1332 1333 @Override oneTouchPlay(final IHdmiControlCallback callback)1334 public void oneTouchPlay(final IHdmiControlCallback callback) { 1335 enforceAccessPermission(); 1336 runOnServiceThread(new Runnable() { 1337 @Override 1338 public void run() { 1339 HdmiControlService.this.oneTouchPlay(callback); 1340 } 1341 }); 1342 } 1343 1344 @Override queryDisplayStatus(final IHdmiControlCallback callback)1345 public void queryDisplayStatus(final IHdmiControlCallback callback) { 1346 enforceAccessPermission(); 1347 runOnServiceThread(new Runnable() { 1348 @Override 1349 public void run() { 1350 HdmiControlService.this.queryDisplayStatus(callback); 1351 } 1352 }); 1353 } 1354 1355 @Override addHotplugEventListener(final IHdmiHotplugEventListener listener)1356 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 1357 enforceAccessPermission(); 1358 HdmiControlService.this.addHotplugEventListener(listener); 1359 } 1360 1361 @Override removeHotplugEventListener(final IHdmiHotplugEventListener listener)1362 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) { 1363 enforceAccessPermission(); 1364 HdmiControlService.this.removeHotplugEventListener(listener); 1365 } 1366 1367 @Override addDeviceEventListener(final IHdmiDeviceEventListener listener)1368 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) { 1369 enforceAccessPermission(); 1370 HdmiControlService.this.addDeviceEventListener(listener); 1371 } 1372 1373 @Override getPortInfo()1374 public List<HdmiPortInfo> getPortInfo() { 1375 enforceAccessPermission(); 1376 return HdmiControlService.this.getPortInfo(); 1377 } 1378 1379 @Override canChangeSystemAudioMode()1380 public boolean canChangeSystemAudioMode() { 1381 enforceAccessPermission(); 1382 HdmiCecLocalDeviceTv tv = tv(); 1383 if (tv == null) { 1384 return false; 1385 } 1386 return tv.hasSystemAudioDevice(); 1387 } 1388 1389 @Override getSystemAudioMode()1390 public boolean getSystemAudioMode() { 1391 enforceAccessPermission(); 1392 HdmiCecLocalDeviceTv tv = tv(); 1393 if (tv == null) { 1394 return false; 1395 } 1396 return tv.isSystemAudioActivated(); 1397 } 1398 1399 @Override setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback)1400 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) { 1401 enforceAccessPermission(); 1402 runOnServiceThread(new Runnable() { 1403 @Override 1404 public void run() { 1405 HdmiCecLocalDeviceTv tv = tv(); 1406 if (tv == null) { 1407 Slog.w(TAG, "Local tv device not available"); 1408 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1409 return; 1410 } 1411 tv.changeSystemAudioMode(enabled, callback); 1412 } 1413 }); 1414 } 1415 1416 @Override addSystemAudioModeChangeListener( final IHdmiSystemAudioModeChangeListener listener)1417 public void addSystemAudioModeChangeListener( 1418 final IHdmiSystemAudioModeChangeListener listener) { 1419 enforceAccessPermission(); 1420 HdmiControlService.this.addSystemAudioModeChangeListner(listener); 1421 } 1422 1423 @Override removeSystemAudioModeChangeListener( final IHdmiSystemAudioModeChangeListener listener)1424 public void removeSystemAudioModeChangeListener( 1425 final IHdmiSystemAudioModeChangeListener listener) { 1426 enforceAccessPermission(); 1427 HdmiControlService.this.removeSystemAudioModeChangeListener(listener); 1428 } 1429 1430 @Override setInputChangeListener(final IHdmiInputChangeListener listener)1431 public void setInputChangeListener(final IHdmiInputChangeListener listener) { 1432 enforceAccessPermission(); 1433 HdmiControlService.this.setInputChangeListener(listener); 1434 } 1435 1436 @Override getInputDevices()1437 public List<HdmiDeviceInfo> getInputDevices() { 1438 enforceAccessPermission(); 1439 // No need to hold the lock for obtaining TV device as the local device instance 1440 // is preserved while the HDMI control is enabled. 1441 HdmiCecLocalDeviceTv tv = tv(); 1442 synchronized (mLock) { 1443 List<HdmiDeviceInfo> cecDevices = (tv == null) 1444 ? Collections.<HdmiDeviceInfo>emptyList() 1445 : tv.getSafeExternalInputsLocked(); 1446 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked()); 1447 } 1448 } 1449 1450 // Returns all the CEC devices on the bus including system audio, switch, 1451 // even those of reserved type. 1452 @Override getDeviceList()1453 public List<HdmiDeviceInfo> getDeviceList() { 1454 enforceAccessPermission(); 1455 HdmiCecLocalDeviceTv tv = tv(); 1456 synchronized (mLock) { 1457 return (tv == null) 1458 ? Collections.<HdmiDeviceInfo>emptyList() 1459 : tv.getSafeCecDevicesLocked(); 1460 } 1461 } 1462 1463 @Override setSystemAudioVolume(final int oldIndex, final int newIndex, final int maxIndex)1464 public void setSystemAudioVolume(final int oldIndex, final int newIndex, 1465 final int maxIndex) { 1466 enforceAccessPermission(); 1467 runOnServiceThread(new Runnable() { 1468 @Override 1469 public void run() { 1470 HdmiCecLocalDeviceTv tv = tv(); 1471 if (tv == null) { 1472 Slog.w(TAG, "Local tv device not available"); 1473 return; 1474 } 1475 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex); 1476 } 1477 }); 1478 } 1479 1480 @Override setSystemAudioMute(final boolean mute)1481 public void setSystemAudioMute(final boolean mute) { 1482 enforceAccessPermission(); 1483 runOnServiceThread(new Runnable() { 1484 @Override 1485 public void run() { 1486 HdmiCecLocalDeviceTv tv = tv(); 1487 if (tv == null) { 1488 Slog.w(TAG, "Local tv device not available"); 1489 return; 1490 } 1491 tv.changeMute(mute); 1492 } 1493 }); 1494 } 1495 1496 @Override setArcMode(final boolean enabled)1497 public void setArcMode(final boolean enabled) { 1498 enforceAccessPermission(); 1499 runOnServiceThread(new Runnable() { 1500 @Override 1501 public void run() { 1502 HdmiCecLocalDeviceTv tv = tv(); 1503 if (tv == null) { 1504 Slog.w(TAG, "Local tv device not available to change arc mode."); 1505 return; 1506 } 1507 } 1508 }); 1509 } 1510 1511 @Override setProhibitMode(final boolean enabled)1512 public void setProhibitMode(final boolean enabled) { 1513 enforceAccessPermission(); 1514 if (!isTvDevice()) { 1515 return; 1516 } 1517 HdmiControlService.this.setProhibitMode(enabled); 1518 } 1519 1520 @Override addVendorCommandListener(final IHdmiVendorCommandListener listener, final int deviceType)1521 public void addVendorCommandListener(final IHdmiVendorCommandListener listener, 1522 final int deviceType) { 1523 enforceAccessPermission(); 1524 HdmiControlService.this.addVendorCommandListener(listener, deviceType); 1525 } 1526 1527 @Override sendVendorCommand(final int deviceType, final int targetAddress, final byte[] params, final boolean hasVendorId)1528 public void sendVendorCommand(final int deviceType, final int targetAddress, 1529 final byte[] params, final boolean hasVendorId) { 1530 enforceAccessPermission(); 1531 runOnServiceThread(new Runnable() { 1532 @Override 1533 public void run() { 1534 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType); 1535 if (device == null) { 1536 Slog.w(TAG, "Local device not available"); 1537 return; 1538 } 1539 if (hasVendorId) { 1540 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId( 1541 device.getDeviceInfo().getLogicalAddress(), targetAddress, 1542 getVendorId(), params)); 1543 } else { 1544 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand( 1545 device.getDeviceInfo().getLogicalAddress(), targetAddress, params)); 1546 } 1547 } 1548 }); 1549 } 1550 1551 @Override sendStandby(final int deviceType, final int deviceId)1552 public void sendStandby(final int deviceType, final int deviceId) { 1553 enforceAccessPermission(); 1554 runOnServiceThread(new Runnable() { 1555 @Override 1556 public void run() { 1557 HdmiMhlLocalDeviceStub mhlDevice = mMhlController.getLocalDeviceById(deviceId); 1558 if (mhlDevice != null) { 1559 mhlDevice.sendStandby(); 1560 return; 1561 } 1562 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType); 1563 if (device == null) { 1564 Slog.w(TAG, "Local device not available"); 1565 return; 1566 } 1567 device.sendStandby(deviceId); 1568 } 1569 }); 1570 } 1571 1572 @Override setHdmiRecordListener(IHdmiRecordListener listener)1573 public void setHdmiRecordListener(IHdmiRecordListener listener) { 1574 enforceAccessPermission(); 1575 HdmiControlService.this.setHdmiRecordListener(listener); 1576 } 1577 1578 @Override startOneTouchRecord(final int recorderAddress, final byte[] recordSource)1579 public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) { 1580 enforceAccessPermission(); 1581 runOnServiceThread(new Runnable() { 1582 @Override 1583 public void run() { 1584 if (!isTvDeviceEnabled()) { 1585 Slog.w(TAG, "TV device is not enabled."); 1586 return; 1587 } 1588 tv().startOneTouchRecord(recorderAddress, recordSource); 1589 } 1590 }); 1591 } 1592 1593 @Override stopOneTouchRecord(final int recorderAddress)1594 public void stopOneTouchRecord(final int recorderAddress) { 1595 enforceAccessPermission(); 1596 runOnServiceThread(new Runnable() { 1597 @Override 1598 public void run() { 1599 if (!isTvDeviceEnabled()) { 1600 Slog.w(TAG, "TV device is not enabled."); 1601 return; 1602 } 1603 tv().stopOneTouchRecord(recorderAddress); 1604 } 1605 }); 1606 } 1607 1608 @Override startTimerRecording(final int recorderAddress, final int sourceType, final byte[] recordSource)1609 public void startTimerRecording(final int recorderAddress, final int sourceType, 1610 final byte[] recordSource) { 1611 enforceAccessPermission(); 1612 runOnServiceThread(new Runnable() { 1613 @Override 1614 public void run() { 1615 if (!isTvDeviceEnabled()) { 1616 Slog.w(TAG, "TV device is not enabled."); 1617 return; 1618 } 1619 tv().startTimerRecording(recorderAddress, sourceType, recordSource); 1620 } 1621 }); 1622 } 1623 1624 @Override clearTimerRecording(final int recorderAddress, final int sourceType, final byte[] recordSource)1625 public void clearTimerRecording(final int recorderAddress, final int sourceType, 1626 final byte[] recordSource) { 1627 enforceAccessPermission(); 1628 runOnServiceThread(new Runnable() { 1629 @Override 1630 public void run() { 1631 if (!isTvDeviceEnabled()) { 1632 Slog.w(TAG, "TV device is not enabled."); 1633 return; 1634 } 1635 tv().clearTimerRecording(recorderAddress, sourceType, recordSource); 1636 } 1637 }); 1638 } 1639 1640 @Override sendMhlVendorCommand(final int portId, final int offset, final int length, final byte[] data)1641 public void sendMhlVendorCommand(final int portId, final int offset, final int length, 1642 final byte[] data) { 1643 enforceAccessPermission(); 1644 runOnServiceThread(new Runnable() { 1645 @Override 1646 public void run() { 1647 if (!isControlEnabled()) { 1648 Slog.w(TAG, "Hdmi control is disabled."); 1649 return ; 1650 } 1651 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); 1652 if (device == null) { 1653 Slog.w(TAG, "Invalid port id:" + portId); 1654 return; 1655 } 1656 mMhlController.sendVendorCommand(portId, offset, length, data); 1657 } 1658 }); 1659 } 1660 1661 @Override addHdmiMhlVendorCommandListener( IHdmiMhlVendorCommandListener listener)1662 public void addHdmiMhlVendorCommandListener( 1663 IHdmiMhlVendorCommandListener listener) { 1664 enforceAccessPermission(); 1665 HdmiControlService.this.addHdmiMhlVendorCommandListener(listener); 1666 } 1667 1668 @Override setStandbyMode(final boolean isStandbyModeOn)1669 public void setStandbyMode(final boolean isStandbyModeOn) { 1670 enforceAccessPermission(); 1671 runOnServiceThread(new Runnable() { 1672 @Override 1673 public void run() { 1674 HdmiControlService.this.setStandbyMode(isStandbyModeOn); 1675 } 1676 }); 1677 } 1678 1679 @Override dump(FileDescriptor fd, final PrintWriter writer, String[] args)1680 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) { 1681 if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) return; 1682 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); 1683 1684 pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled); 1685 pw.println("mProhibitMode: " + mProhibitMode); 1686 if (mCecController != null) { 1687 pw.println("mCecController: "); 1688 pw.increaseIndent(); 1689 mCecController.dump(pw); 1690 pw.decreaseIndent(); 1691 } 1692 1693 pw.println("mMhlController: "); 1694 pw.increaseIndent(); 1695 mMhlController.dump(pw); 1696 pw.decreaseIndent(); 1697 1698 pw.println("mPortInfo: "); 1699 pw.increaseIndent(); 1700 for (HdmiPortInfo hdmiPortInfo : mPortInfo) { 1701 pw.println("- " + hdmiPortInfo); 1702 } 1703 pw.decreaseIndent(); 1704 pw.println("mPowerStatus: " + mPowerStatus); 1705 } 1706 } 1707 1708 @ServiceThreadOnly oneTouchPlay(final IHdmiControlCallback callback)1709 private void oneTouchPlay(final IHdmiControlCallback callback) { 1710 assertRunOnServiceThread(); 1711 HdmiCecLocalDevicePlayback source = playback(); 1712 if (source == null) { 1713 Slog.w(TAG, "Local playback device not available"); 1714 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1715 return; 1716 } 1717 source.oneTouchPlay(callback); 1718 } 1719 1720 @ServiceThreadOnly queryDisplayStatus(final IHdmiControlCallback callback)1721 private void queryDisplayStatus(final IHdmiControlCallback callback) { 1722 assertRunOnServiceThread(); 1723 HdmiCecLocalDevicePlayback source = playback(); 1724 if (source == null) { 1725 Slog.w(TAG, "Local playback device not available"); 1726 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1727 return; 1728 } 1729 source.queryDisplayStatus(callback); 1730 } 1731 addHotplugEventListener(final IHdmiHotplugEventListener listener)1732 private void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 1733 final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); 1734 try { 1735 listener.asBinder().linkToDeath(record, 0); 1736 } catch (RemoteException e) { 1737 Slog.w(TAG, "Listener already died"); 1738 return; 1739 } 1740 synchronized (mLock) { 1741 mHotplugEventListenerRecords.add(record); 1742 } 1743 1744 // Inform the listener of the initial state of each HDMI port by generating 1745 // hotplug events. 1746 runOnServiceThread(new Runnable() { 1747 @Override 1748 public void run() { 1749 synchronized (mLock) { 1750 if (!mHotplugEventListenerRecords.contains(record)) return; 1751 } 1752 for (HdmiPortInfo port : mPortInfo) { 1753 HdmiHotplugEvent event = new HdmiHotplugEvent(port.getId(), 1754 mCecController.isConnected(port.getId())); 1755 synchronized (mLock) { 1756 invokeHotplugEventListenerLocked(listener, event); 1757 } 1758 } 1759 } 1760 }); 1761 } 1762 removeHotplugEventListener(IHdmiHotplugEventListener listener)1763 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) { 1764 synchronized (mLock) { 1765 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 1766 if (record.mListener.asBinder() == listener.asBinder()) { 1767 listener.asBinder().unlinkToDeath(record, 0); 1768 mHotplugEventListenerRecords.remove(record); 1769 break; 1770 } 1771 } 1772 } 1773 } 1774 addDeviceEventListener(IHdmiDeviceEventListener listener)1775 private void addDeviceEventListener(IHdmiDeviceEventListener listener) { 1776 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener); 1777 try { 1778 listener.asBinder().linkToDeath(record, 0); 1779 } catch (RemoteException e) { 1780 Slog.w(TAG, "Listener already died"); 1781 return; 1782 } 1783 synchronized (mLock) { 1784 mDeviceEventListenerRecords.add(record); 1785 } 1786 } 1787 invokeDeviceEventListeners(HdmiDeviceInfo device, int status)1788 void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) { 1789 synchronized (mLock) { 1790 for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) { 1791 try { 1792 record.mListener.onStatusChanged(device, status); 1793 } catch (RemoteException e) { 1794 Slog.e(TAG, "Failed to report device event:" + e); 1795 } 1796 } 1797 } 1798 } 1799 addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener)1800 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) { 1801 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord( 1802 listener); 1803 try { 1804 listener.asBinder().linkToDeath(record, 0); 1805 } catch (RemoteException e) { 1806 Slog.w(TAG, "Listener already died"); 1807 return; 1808 } 1809 synchronized (mLock) { 1810 mSystemAudioModeChangeListenerRecords.add(record); 1811 } 1812 } 1813 removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener)1814 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) { 1815 synchronized (mLock) { 1816 for (SystemAudioModeChangeListenerRecord record : 1817 mSystemAudioModeChangeListenerRecords) { 1818 if (record.mListener.asBinder() == listener) { 1819 listener.asBinder().unlinkToDeath(record, 0); 1820 mSystemAudioModeChangeListenerRecords.remove(record); 1821 break; 1822 } 1823 } 1824 } 1825 } 1826 1827 private final class InputChangeListenerRecord implements IBinder.DeathRecipient { 1828 private final IHdmiInputChangeListener mListener; 1829 InputChangeListenerRecord(IHdmiInputChangeListener listener)1830 public InputChangeListenerRecord(IHdmiInputChangeListener listener) { 1831 mListener = listener; 1832 } 1833 1834 @Override binderDied()1835 public void binderDied() { 1836 synchronized (mLock) { 1837 if (mInputChangeListenerRecord == this) { 1838 mInputChangeListenerRecord = null; 1839 } 1840 } 1841 } 1842 } 1843 setInputChangeListener(IHdmiInputChangeListener listener)1844 private void setInputChangeListener(IHdmiInputChangeListener listener) { 1845 synchronized (mLock) { 1846 mInputChangeListenerRecord = new InputChangeListenerRecord(listener); 1847 try { 1848 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0); 1849 } catch (RemoteException e) { 1850 Slog.w(TAG, "Listener already died"); 1851 return; 1852 } 1853 } 1854 } 1855 invokeInputChangeListener(HdmiDeviceInfo info)1856 void invokeInputChangeListener(HdmiDeviceInfo info) { 1857 synchronized (mLock) { 1858 if (mInputChangeListenerRecord != null) { 1859 try { 1860 mInputChangeListenerRecord.mListener.onChanged(info); 1861 } catch (RemoteException e) { 1862 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e); 1863 } 1864 } 1865 } 1866 } 1867 setHdmiRecordListener(IHdmiRecordListener listener)1868 private void setHdmiRecordListener(IHdmiRecordListener listener) { 1869 synchronized (mLock) { 1870 mRecordListenerRecord = new HdmiRecordListenerRecord(listener); 1871 try { 1872 listener.asBinder().linkToDeath(mRecordListenerRecord, 0); 1873 } catch (RemoteException e) { 1874 Slog.w(TAG, "Listener already died.", e); 1875 } 1876 } 1877 } 1878 invokeRecordRequestListener(int recorderAddress)1879 byte[] invokeRecordRequestListener(int recorderAddress) { 1880 synchronized (mLock) { 1881 if (mRecordListenerRecord != null) { 1882 try { 1883 return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress); 1884 } catch (RemoteException e) { 1885 Slog.w(TAG, "Failed to start record.", e); 1886 } 1887 } 1888 return EmptyArray.BYTE; 1889 } 1890 } 1891 invokeOneTouchRecordResult(int recorderAddress, int result)1892 void invokeOneTouchRecordResult(int recorderAddress, int result) { 1893 synchronized (mLock) { 1894 if (mRecordListenerRecord != null) { 1895 try { 1896 mRecordListenerRecord.mListener.onOneTouchRecordResult(recorderAddress, result); 1897 } catch (RemoteException e) { 1898 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e); 1899 } 1900 } 1901 } 1902 } 1903 invokeTimerRecordingResult(int recorderAddress, int result)1904 void invokeTimerRecordingResult(int recorderAddress, int result) { 1905 synchronized (mLock) { 1906 if (mRecordListenerRecord != null) { 1907 try { 1908 mRecordListenerRecord.mListener.onTimerRecordingResult(recorderAddress, result); 1909 } catch (RemoteException e) { 1910 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e); 1911 } 1912 } 1913 } 1914 } 1915 invokeClearTimerRecordingResult(int recorderAddress, int result)1916 void invokeClearTimerRecordingResult(int recorderAddress, int result) { 1917 synchronized (mLock) { 1918 if (mRecordListenerRecord != null) { 1919 try { 1920 mRecordListenerRecord.mListener.onClearTimerRecordingResult(recorderAddress, 1921 result); 1922 } catch (RemoteException e) { 1923 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e); 1924 } 1925 } 1926 } 1927 } 1928 invokeCallback(IHdmiControlCallback callback, int result)1929 private void invokeCallback(IHdmiControlCallback callback, int result) { 1930 try { 1931 callback.onComplete(result); 1932 } catch (RemoteException e) { 1933 Slog.e(TAG, "Invoking callback failed:" + e); 1934 } 1935 } 1936 invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener, boolean enabled)1937 private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener, 1938 boolean enabled) { 1939 try { 1940 listener.onStatusChanged(enabled); 1941 } catch (RemoteException e) { 1942 Slog.e(TAG, "Invoking callback failed:" + e); 1943 } 1944 } 1945 announceHotplugEvent(int portId, boolean connected)1946 private void announceHotplugEvent(int portId, boolean connected) { 1947 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected); 1948 synchronized (mLock) { 1949 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 1950 invokeHotplugEventListenerLocked(record.mListener, event); 1951 } 1952 } 1953 } 1954 invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener, HdmiHotplugEvent event)1955 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener, 1956 HdmiHotplugEvent event) { 1957 try { 1958 listener.onReceived(event); 1959 } catch (RemoteException e) { 1960 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e); 1961 } 1962 } 1963 tv()1964 public HdmiCecLocalDeviceTv tv() { 1965 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV); 1966 } 1967 isTvDevice()1968 boolean isTvDevice() { 1969 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV); 1970 } 1971 isTvDeviceEnabled()1972 boolean isTvDeviceEnabled() { 1973 return isTvDevice() && tv() != null; 1974 } 1975 playback()1976 private HdmiCecLocalDevicePlayback playback() { 1977 return (HdmiCecLocalDevicePlayback) 1978 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK); 1979 } 1980 getAudioManager()1981 AudioManager getAudioManager() { 1982 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); 1983 } 1984 isControlEnabled()1985 boolean isControlEnabled() { 1986 synchronized (mLock) { 1987 return mHdmiControlEnabled; 1988 } 1989 } 1990 1991 @ServiceThreadOnly getPowerStatus()1992 int getPowerStatus() { 1993 assertRunOnServiceThread(); 1994 return mPowerStatus; 1995 } 1996 1997 @ServiceThreadOnly isPowerOnOrTransient()1998 boolean isPowerOnOrTransient() { 1999 assertRunOnServiceThread(); 2000 return mPowerStatus == HdmiControlManager.POWER_STATUS_ON 2001 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; 2002 } 2003 2004 @ServiceThreadOnly isPowerStandbyOrTransient()2005 boolean isPowerStandbyOrTransient() { 2006 assertRunOnServiceThread(); 2007 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY 2008 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY; 2009 } 2010 2011 @ServiceThreadOnly isPowerStandby()2012 boolean isPowerStandby() { 2013 assertRunOnServiceThread(); 2014 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY; 2015 } 2016 2017 @ServiceThreadOnly wakeUp()2018 void wakeUp() { 2019 assertRunOnServiceThread(); 2020 mWakeUpMessageReceived = true; 2021 mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.server.hdmi:WAKE"); 2022 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets 2023 // the intent, the sequence will continue at onWakeUp(). 2024 } 2025 2026 @ServiceThreadOnly standby()2027 void standby() { 2028 assertRunOnServiceThread(); 2029 if (!canGoToStandby()) { 2030 return; 2031 } 2032 mStandbyMessageReceived = true; 2033 mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0); 2034 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets 2035 // the intent, the sequence will continue at onStandby(). 2036 } 2037 isWakeUpMessageReceived()2038 boolean isWakeUpMessageReceived() { 2039 return mWakeUpMessageReceived; 2040 } 2041 2042 @ServiceThreadOnly onWakeUp()2043 private void onWakeUp() { 2044 assertRunOnServiceThread(); 2045 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; 2046 if (mCecController != null) { 2047 if (mHdmiControlEnabled) { 2048 int startReason = INITIATED_BY_SCREEN_ON; 2049 if (mWakeUpMessageReceived) { 2050 startReason = INITIATED_BY_WAKE_UP_MESSAGE; 2051 } 2052 initializeCec(startReason); 2053 } 2054 } else { 2055 Slog.i(TAG, "Device does not support HDMI-CEC."); 2056 } 2057 // TODO: Initialize MHL local devices. 2058 } 2059 2060 @ServiceThreadOnly onStandby(final int standbyAction)2061 private void onStandby(final int standbyAction) { 2062 assertRunOnServiceThread(); 2063 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY; 2064 invokeVendorCommandListenersOnControlStateChanged(false, 2065 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY); 2066 if (!canGoToStandby()) { 2067 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; 2068 return; 2069 } 2070 2071 final List<HdmiCecLocalDevice> devices = getAllLocalDevices(); 2072 disableDevices(new PendingActionClearedCallback() { 2073 @Override 2074 public void onCleared(HdmiCecLocalDevice device) { 2075 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType); 2076 devices.remove(device); 2077 if (devices.isEmpty()) { 2078 onStandbyCompleted(standbyAction); 2079 // We will not clear local devices here, since some OEM/SOC will keep passing 2080 // the received packets until the application processor enters to the sleep 2081 // actually. 2082 } 2083 } 2084 }); 2085 } 2086 canGoToStandby()2087 private boolean canGoToStandby() { 2088 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 2089 if (!device.canGoToStandby()) return false; 2090 } 2091 return true; 2092 } 2093 2094 @ServiceThreadOnly onLanguageChanged(String language)2095 private void onLanguageChanged(String language) { 2096 assertRunOnServiceThread(); 2097 mLanguage = language; 2098 2099 if (isTvDeviceEnabled()) { 2100 tv().broadcastMenuLanguage(language); 2101 mCecController.setLanguage(language); 2102 } 2103 } 2104 2105 @ServiceThreadOnly getLanguage()2106 String getLanguage() { 2107 assertRunOnServiceThread(); 2108 return mLanguage; 2109 } 2110 disableDevices(PendingActionClearedCallback callback)2111 private void disableDevices(PendingActionClearedCallback callback) { 2112 if (mCecController != null) { 2113 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 2114 device.disableDevice(mStandbyMessageReceived, callback); 2115 } 2116 } 2117 2118 mMhlController.clearAllLocalDevices(); 2119 } 2120 2121 @ServiceThreadOnly clearLocalDevices()2122 private void clearLocalDevices() { 2123 assertRunOnServiceThread(); 2124 if (mCecController == null) { 2125 return; 2126 } 2127 mCecController.clearLogicalAddress(); 2128 mCecController.clearLocalDevices(); 2129 } 2130 2131 @ServiceThreadOnly onStandbyCompleted(int standbyAction)2132 private void onStandbyCompleted(int standbyAction) { 2133 assertRunOnServiceThread(); 2134 Slog.v(TAG, "onStandbyCompleted"); 2135 2136 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) { 2137 return; 2138 } 2139 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; 2140 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 2141 device.onStandby(mStandbyMessageReceived, standbyAction); 2142 } 2143 mStandbyMessageReceived = false; 2144 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false); 2145 mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED); 2146 } 2147 addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType)2148 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) { 2149 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType); 2150 try { 2151 listener.asBinder().linkToDeath(record, 0); 2152 } catch (RemoteException e) { 2153 Slog.w(TAG, "Listener already died"); 2154 return; 2155 } 2156 synchronized (mLock) { 2157 mVendorCommandListenerRecords.add(record); 2158 } 2159 } 2160 invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress, byte[] params, boolean hasVendorId)2161 boolean invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress, 2162 byte[] params, boolean hasVendorId) { 2163 synchronized (mLock) { 2164 if (mVendorCommandListenerRecords.isEmpty()) { 2165 return false; 2166 } 2167 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) { 2168 if (record.mDeviceType != deviceType) { 2169 continue; 2170 } 2171 try { 2172 record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId); 2173 } catch (RemoteException e) { 2174 Slog.e(TAG, "Failed to notify vendor command reception", e); 2175 } 2176 } 2177 return true; 2178 } 2179 } 2180 invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason)2181 boolean invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason) { 2182 synchronized (mLock) { 2183 if (mVendorCommandListenerRecords.isEmpty()) { 2184 return false; 2185 } 2186 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) { 2187 try { 2188 record.mListener.onControlStateChanged(enabled, reason); 2189 } catch (RemoteException e) { 2190 Slog.e(TAG, "Failed to notify control-state-changed to vendor handler", e); 2191 } 2192 } 2193 return true; 2194 } 2195 } 2196 addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener)2197 private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) { 2198 HdmiMhlVendorCommandListenerRecord record = 2199 new HdmiMhlVendorCommandListenerRecord(listener); 2200 try { 2201 listener.asBinder().linkToDeath(record, 0); 2202 } catch (RemoteException e) { 2203 Slog.w(TAG, "Listener already died."); 2204 return; 2205 } 2206 2207 synchronized (mLock) { 2208 mMhlVendorCommandListenerRecords.add(record); 2209 } 2210 } 2211 invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data)2212 void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) { 2213 synchronized (mLock) { 2214 for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) { 2215 try { 2216 record.mListener.onReceived(portId, offest, length, data); 2217 } catch (RemoteException e) { 2218 Slog.e(TAG, "Failed to notify MHL vendor command", e); 2219 } 2220 } 2221 } 2222 } 2223 setStandbyMode(boolean isStandbyModeOn)2224 void setStandbyMode(boolean isStandbyModeOn) { 2225 assertRunOnServiceThread(); 2226 if (isPowerOnOrTransient() && isStandbyModeOn) { 2227 mPowerManager.goToSleep(SystemClock.uptimeMillis(), 2228 PowerManager.GO_TO_SLEEP_REASON_HDMI, 0); 2229 if (playback() != null) { 2230 playback().sendStandby(0 /* unused */); 2231 } 2232 } else if (isPowerStandbyOrTransient() && !isStandbyModeOn) { 2233 mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.server.hdmi:WAKE"); 2234 if (playback() != null) { 2235 oneTouchPlay(new IHdmiControlCallback.Stub() { 2236 @Override 2237 public void onComplete(int result) { 2238 if (result != HdmiControlManager.RESULT_SUCCESS) { 2239 Slog.w(TAG, "Failed to complete 'one touch play'. result=" + result); 2240 } 2241 } 2242 }); 2243 } 2244 } 2245 } 2246 isProhibitMode()2247 boolean isProhibitMode() { 2248 synchronized (mLock) { 2249 return mProhibitMode; 2250 } 2251 } 2252 setProhibitMode(boolean enabled)2253 void setProhibitMode(boolean enabled) { 2254 synchronized (mLock) { 2255 mProhibitMode = enabled; 2256 } 2257 } 2258 2259 @ServiceThreadOnly setCecOption(int key, boolean value)2260 void setCecOption(int key, boolean value) { 2261 assertRunOnServiceThread(); 2262 mCecController.setOption(key, value); 2263 } 2264 2265 @ServiceThreadOnly setControlEnabled(boolean enabled)2266 void setControlEnabled(boolean enabled) { 2267 assertRunOnServiceThread(); 2268 2269 synchronized (mLock) { 2270 mHdmiControlEnabled = enabled; 2271 } 2272 2273 if (enabled) { 2274 enableHdmiControlService(); 2275 return; 2276 } 2277 // Call the vendor handler before the service is disabled. 2278 invokeVendorCommandListenersOnControlStateChanged(false, 2279 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING); 2280 // Post the remained tasks in the service thread again to give the vendor-issued-tasks 2281 // a chance to run. 2282 runOnServiceThread(new Runnable() { 2283 @Override 2284 public void run() { 2285 disableHdmiControlService(); 2286 } 2287 }); 2288 return; 2289 } 2290 2291 @ServiceThreadOnly enableHdmiControlService()2292 private void enableHdmiControlService() { 2293 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true); 2294 mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED); 2295 2296 initializeCec(INITIATED_BY_ENABLE_CEC); 2297 } 2298 2299 @ServiceThreadOnly disableHdmiControlService()2300 private void disableHdmiControlService() { 2301 disableDevices(new PendingActionClearedCallback() { 2302 @Override 2303 public void onCleared(HdmiCecLocalDevice device) { 2304 assertRunOnServiceThread(); 2305 mCecController.flush(new Runnable() { 2306 @Override 2307 public void run() { 2308 mCecController.setOption(OptionKey.ENABLE_CEC, false); 2309 mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED); 2310 clearLocalDevices(); 2311 } 2312 }); 2313 } 2314 }); 2315 } 2316 2317 @ServiceThreadOnly setActivePortId(int portId)2318 void setActivePortId(int portId) { 2319 assertRunOnServiceThread(); 2320 mActivePortId = portId; 2321 2322 // Resets last input for MHL, which stays valid only after the MHL device was selected, 2323 // and no further switching is done. 2324 setLastInputForMhl(Constants.INVALID_PORT_ID); 2325 } 2326 2327 @ServiceThreadOnly setLastInputForMhl(int portId)2328 void setLastInputForMhl(int portId) { 2329 assertRunOnServiceThread(); 2330 mLastInputMhl = portId; 2331 } 2332 2333 @ServiceThreadOnly getLastInputForMhl()2334 int getLastInputForMhl() { 2335 assertRunOnServiceThread(); 2336 return mLastInputMhl; 2337 } 2338 2339 /** 2340 * Performs input change, routing control for MHL device. 2341 * 2342 * @param portId MHL port, or the last port to go back to if {@code contentOn} is false 2343 * @param contentOn {@code true} if RAP data is content on; otherwise false 2344 */ 2345 @ServiceThreadOnly changeInputForMhl(int portId, boolean contentOn)2346 void changeInputForMhl(int portId, boolean contentOn) { 2347 assertRunOnServiceThread(); 2348 if (tv() == null) return; 2349 final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID; 2350 if (portId != Constants.INVALID_PORT_ID) { 2351 tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() { 2352 @Override 2353 public void onComplete(int result) throws RemoteException { 2354 // Keep the last input to switch back later when RAP[ContentOff] is received. 2355 // This effectively sets the port to invalid one if the switching is for 2356 // RAP[ContentOff]. 2357 setLastInputForMhl(lastInput); 2358 } 2359 }); 2360 } 2361 // MHL device is always directly connected to the port. Update the active port ID to avoid 2362 // unnecessary post-routing control task. 2363 tv().setActivePortId(portId); 2364 2365 // The port is either the MHL-enabled port where the mobile device is connected, or 2366 // the last port to go back to when turnoff command is received. Note that the last port 2367 // may not be the MHL-enabled one. In this case the device info to be passed to 2368 // input change listener should be the one describing the corresponding HDMI port. 2369 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); 2370 HdmiDeviceInfo info = (device != null) ? device.getInfo() 2371 : mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE); 2372 invokeInputChangeListener(info); 2373 } 2374 setMhlInputChangeEnabled(boolean enabled)2375 void setMhlInputChangeEnabled(boolean enabled) { 2376 mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled)); 2377 2378 synchronized (mLock) { 2379 mMhlInputChangeEnabled = enabled; 2380 } 2381 } 2382 isMhlInputChangeEnabled()2383 boolean isMhlInputChangeEnabled() { 2384 synchronized (mLock) { 2385 return mMhlInputChangeEnabled; 2386 } 2387 } 2388 2389 @ServiceThreadOnly displayOsd(int messageId)2390 void displayOsd(int messageId) { 2391 assertRunOnServiceThread(); 2392 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE); 2393 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId); 2394 getContext().sendBroadcastAsUser(intent, UserHandle.ALL, 2395 HdmiControlService.PERMISSION); 2396 } 2397 2398 @ServiceThreadOnly displayOsd(int messageId, int extra)2399 void displayOsd(int messageId, int extra) { 2400 assertRunOnServiceThread(); 2401 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE); 2402 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId); 2403 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra); 2404 getContext().sendBroadcastAsUser(intent, UserHandle.ALL, 2405 HdmiControlService.PERMISSION); 2406 } 2407 } 2408