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 22 import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED; 23 import static com.android.server.hdmi.Constants.DISABLED; 24 import static com.android.server.hdmi.Constants.ENABLED; 25 import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE; 26 import static com.android.server.hdmi.Constants.OPTION_MHL_INPUT_SWITCHING; 27 import static com.android.server.hdmi.Constants.OPTION_MHL_POWER_CHARGE; 28 import static com.android.server.hdmi.Constants.OPTION_MHL_SERVICE_CONTROL; 29 import static com.android.server.power.ShutdownThread.SHUTDOWN_ACTION_PROPERTY; 30 31 import android.annotation.Nullable; 32 import android.content.BroadcastReceiver; 33 import android.content.ContentResolver; 34 import android.content.Context; 35 import android.content.Intent; 36 import android.content.IntentFilter; 37 import android.database.ContentObserver; 38 import android.hardware.hdmi.HdmiControlManager; 39 import android.hardware.hdmi.HdmiDeviceInfo; 40 import android.hardware.hdmi.HdmiHotplugEvent; 41 import android.hardware.hdmi.HdmiPortInfo; 42 import android.hardware.hdmi.IHdmiControlCallback; 43 import android.hardware.hdmi.IHdmiControlService; 44 import android.hardware.hdmi.IHdmiControlStatusChangeListener; 45 import android.hardware.hdmi.IHdmiDeviceEventListener; 46 import android.hardware.hdmi.IHdmiHotplugEventListener; 47 import android.hardware.hdmi.IHdmiInputChangeListener; 48 import android.hardware.hdmi.IHdmiMhlVendorCommandListener; 49 import android.hardware.hdmi.IHdmiRecordListener; 50 import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener; 51 import android.hardware.hdmi.IHdmiVendorCommandListener; 52 import android.hardware.tv.cec.V1_0.OptionKey; 53 import android.hardware.tv.cec.V1_0.SendMessageResult; 54 import android.media.AudioManager; 55 import android.media.tv.TvInputManager; 56 import android.media.tv.TvInputManager.TvInputCallback; 57 import android.net.Uri; 58 import android.os.Build; 59 import android.os.Handler; 60 import android.os.HandlerThread; 61 import android.os.IBinder; 62 import android.os.Looper; 63 import android.os.PowerManager; 64 import android.os.RemoteException; 65 import android.os.SystemClock; 66 import android.os.SystemProperties; 67 import android.os.UserHandle; 68 import android.provider.Settings.Global; 69 import android.sysprop.HdmiProperties; 70 import android.text.TextUtils; 71 import android.util.ArraySet; 72 import android.util.Slog; 73 import android.util.SparseArray; 74 import android.util.SparseIntArray; 75 76 import com.android.internal.annotations.GuardedBy; 77 import com.android.internal.annotations.VisibleForTesting; 78 import com.android.internal.util.DumpUtils; 79 import com.android.internal.util.IndentingPrintWriter; 80 import com.android.server.SystemService; 81 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; 82 import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; 83 import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource; 84 import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback; 85 86 import libcore.util.EmptyArray; 87 88 import java.io.FileDescriptor; 89 import java.io.PrintWriter; 90 import java.util.ArrayList; 91 import java.util.Arrays; 92 import java.util.Collections; 93 import java.util.HashMap; 94 import java.util.List; 95 import java.util.Locale; 96 import java.util.Map; 97 import java.util.Objects; 98 import java.util.stream.Collectors; 99 100 /** 101 * Provides a service for sending and processing HDMI control messages, 102 * HDMI-CEC and MHL control command, and providing the information on both standard. 103 */ 104 public class HdmiControlService extends SystemService { 105 private static final String TAG = "HdmiControlService"; 106 private final Locale HONG_KONG = new Locale("zh", "HK"); 107 private final Locale MACAU = new Locale("zh", "MO"); 108 109 private static final Map<String, String> mTerminologyToBibliographicMap; 110 static { 111 mTerminologyToBibliographicMap = new HashMap<>(); 112 // NOTE: (TERMINOLOGY_CODE, BIBLIOGRAPHIC_CODE) 113 mTerminologyToBibliographicMap.put("sqi", "alb"); // Albanian 114 mTerminologyToBibliographicMap.put("hye", "arm"); // Armenian 115 mTerminologyToBibliographicMap.put("eus", "baq"); // Basque 116 mTerminologyToBibliographicMap.put("mya", "bur"); // Burmese 117 mTerminologyToBibliographicMap.put("ces", "cze"); // Czech 118 mTerminologyToBibliographicMap.put("nld", "dut"); // Dutch 119 mTerminologyToBibliographicMap.put("kat", "geo"); // Georgian 120 mTerminologyToBibliographicMap.put("deu", "ger"); // German 121 mTerminologyToBibliographicMap.put("ell", "gre"); // Greek 122 mTerminologyToBibliographicMap.put("fra", "fre"); // French 123 mTerminologyToBibliographicMap.put("isl", "ice"); // Icelandic 124 mTerminologyToBibliographicMap.put("mkd", "mac"); // Macedonian 125 mTerminologyToBibliographicMap.put("mri", "mao"); // Maori 126 mTerminologyToBibliographicMap.put("msa", "may"); // Malay 127 mTerminologyToBibliographicMap.put("fas", "per"); // Persian 128 mTerminologyToBibliographicMap.put("ron", "rum"); // Romanian 129 mTerminologyToBibliographicMap.put("slk", "slo"); // Slovak 130 mTerminologyToBibliographicMap.put("bod", "tib"); // Tibetan 131 mTerminologyToBibliographicMap.put("cym", "wel"); // Welsh 132 } 133 134 static final String PERMISSION = "android.permission.HDMI_CEC"; 135 136 // The reason code to initiate initializeCec(). 137 static final int INITIATED_BY_ENABLE_CEC = 0; 138 static final int INITIATED_BY_BOOT_UP = 1; 139 static final int INITIATED_BY_SCREEN_ON = 2; 140 static final int INITIATED_BY_WAKE_UP_MESSAGE = 3; 141 static final int INITIATED_BY_HOTPLUG = 4; 142 143 // The reason code representing the intent action that drives the standby 144 // procedure. The procedure starts either by Intent.ACTION_SCREEN_OFF or 145 // Intent.ACTION_SHUTDOWN. 146 static final int STANDBY_SCREEN_OFF = 0; 147 static final int STANDBY_SHUTDOWN = 1; 148 149 // Logical address of the active source. 150 @GuardedBy("mLock") 151 protected final ActiveSource mActiveSource = new ActiveSource(); 152 153 // Whether System Audio Mode is activated or not. 154 @GuardedBy("mLock") 155 private boolean mSystemAudioActivated = false; 156 157 /** 158 * Interface to report send result. 159 */ 160 interface SendMessageCallback { 161 /** 162 * Called when {@link HdmiControlService#sendCecCommand} is completed. 163 * 164 * @param error result of send request. 165 * <ul> 166 * <li>{@link SendMessageResult#SUCCESS} 167 * <li>{@link SendMessageResult#NACK} 168 * <li>{@link SendMessageResult#BUSY} 169 * <li>{@link SendMessageResult#FAIL} 170 * </ul> 171 */ onSendCompleted(int error)172 void onSendCompleted(int error); 173 } 174 175 /** 176 * Interface to get a list of available logical devices. 177 */ 178 interface DevicePollingCallback { 179 /** 180 * Called when device polling is finished. 181 * 182 * @param ackedAddress a list of logical addresses of available devices 183 */ onPollingFinished(List<Integer> ackedAddress)184 void onPollingFinished(List<Integer> ackedAddress); 185 } 186 187 private class HdmiControlBroadcastReceiver extends BroadcastReceiver { 188 @ServiceThreadOnly 189 @Override onReceive(Context context, Intent intent)190 public void onReceive(Context context, Intent intent) { 191 assertRunOnServiceThread(); 192 boolean isReboot = SystemProperties.get(SHUTDOWN_ACTION_PROPERTY).contains("1"); 193 switch (intent.getAction()) { 194 case Intent.ACTION_SCREEN_OFF: 195 if (isPowerOnOrTransient() && !isReboot) { 196 onStandby(STANDBY_SCREEN_OFF); 197 } 198 break; 199 case Intent.ACTION_SCREEN_ON: 200 if (isPowerStandbyOrTransient()) { 201 onWakeUp(); 202 } 203 break; 204 case Intent.ACTION_CONFIGURATION_CHANGED: 205 String language = getMenuLanguage(); 206 if (!mLanguage.equals(language)) { 207 onLanguageChanged(language); 208 } 209 break; 210 case Intent.ACTION_SHUTDOWN: 211 if (isPowerOnOrTransient() && !isReboot) { 212 onStandby(STANDBY_SHUTDOWN); 213 } 214 break; 215 } 216 } 217 getMenuLanguage()218 private String getMenuLanguage() { 219 Locale locale = Locale.getDefault(); 220 if (locale.equals(Locale.TAIWAN) || locale.equals(HONG_KONG) || locale.equals(MACAU)) { 221 // Android always returns "zho" for all Chinese variants. 222 // Use "bibliographic" code defined in CEC639-2 for traditional 223 // Chinese used in Taiwan/Hong Kong/Macau. 224 return "chi"; 225 } else { 226 String language = locale.getISO3Language(); 227 228 // locale.getISO3Language() returns terminology code and need to 229 // send it as bibliographic code instead since the Bibliographic 230 // codes of ISO/FDIS 639-2 shall be used. 231 // NOTE: Chinese also has terminology/bibliographic code "zho" and "chi" 232 // But, as it depends on the locale, is not handled here. 233 if (mTerminologyToBibliographicMap.containsKey(language)) { 234 language = mTerminologyToBibliographicMap.get(language); 235 } 236 237 return language; 238 } 239 } 240 } 241 242 // A thread to handle synchronous IO of CEC and MHL control service. 243 // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms) 244 // and sparse call it shares a thread to handle IO operations. 245 private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread"); 246 247 // Used to synchronize the access to the service. 248 private final Object mLock = new Object(); 249 250 // Type of logical devices hosted in the system. Stored in the unmodifiable list. 251 private final List<Integer> mLocalDevices; 252 253 // List of records for HDMI control status change listener for death monitoring. 254 @GuardedBy("mLock") 255 private final ArrayList<HdmiControlStatusChangeListenerRecord> 256 mHdmiControlStatusChangeListenerRecords = new ArrayList<>(); 257 258 // List of records for hotplug event listener to handle the the caller killed in action. 259 @GuardedBy("mLock") 260 private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords = 261 new ArrayList<>(); 262 263 // List of records for device event listener to handle the caller killed in action. 264 @GuardedBy("mLock") 265 private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords = 266 new ArrayList<>(); 267 268 // List of records for vendor command listener to handle the caller killed in action. 269 @GuardedBy("mLock") 270 private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords = 271 new ArrayList<>(); 272 273 @GuardedBy("mLock") 274 private InputChangeListenerRecord mInputChangeListenerRecord; 275 276 @GuardedBy("mLock") 277 private HdmiRecordListenerRecord mRecordListenerRecord; 278 279 // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol 280 // handling will be disabled and no request will be handled. 281 @GuardedBy("mLock") 282 private boolean mHdmiControlEnabled; 283 284 // Set to true while the service is in normal mode. While set to false, no input change is 285 // allowed. Used for situations where input change can confuse users such as channel auto-scan, 286 // system upgrade, etc., a.k.a. "prohibit mode". 287 @GuardedBy("mLock") 288 private boolean mProhibitMode; 289 290 // List of records for system audio mode change to handle the the caller killed in action. 291 private final ArrayList<SystemAudioModeChangeListenerRecord> 292 mSystemAudioModeChangeListenerRecords = new ArrayList<>(); 293 294 // Handler used to run a task in service thread. 295 private final Handler mHandler = new Handler(); 296 297 private final SettingsObserver mSettingsObserver; 298 299 private final HdmiControlBroadcastReceiver 300 mHdmiControlBroadcastReceiver = new HdmiControlBroadcastReceiver(); 301 302 @Nullable 303 private HdmiCecController mCecController; 304 305 // HDMI port information. Stored in the unmodifiable list to keep the static information 306 // from being modified. 307 private List<HdmiPortInfo> mPortInfo; 308 309 // Map from path(physical address) to port ID. 310 private UnmodifiableSparseIntArray mPortIdMap; 311 312 // Map from port ID to HdmiPortInfo. 313 private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap; 314 315 // Map from port ID to HdmiDeviceInfo. 316 private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap; 317 318 private HdmiCecMessageValidator mMessageValidator; 319 320 @ServiceThreadOnly 321 private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; 322 323 @ServiceThreadOnly 324 private String mLanguage = Locale.getDefault().getISO3Language(); 325 326 @ServiceThreadOnly 327 private boolean mStandbyMessageReceived = false; 328 329 @ServiceThreadOnly 330 private boolean mWakeUpMessageReceived = false; 331 332 @ServiceThreadOnly 333 private int mActivePortId = Constants.INVALID_PORT_ID; 334 335 // Set to true while the input change by MHL is allowed. 336 @GuardedBy("mLock") 337 private boolean mMhlInputChangeEnabled; 338 339 // List of records for MHL Vendor command listener to handle the caller killed in action. 340 @GuardedBy("mLock") 341 private final ArrayList<HdmiMhlVendorCommandListenerRecord> 342 mMhlVendorCommandListenerRecords = new ArrayList<>(); 343 344 @GuardedBy("mLock") 345 private List<HdmiDeviceInfo> mMhlDevices; 346 347 @Nullable 348 private HdmiMhlControllerStub mMhlController; 349 350 @Nullable 351 private TvInputManager mTvInputManager; 352 353 @Nullable 354 private PowerManager mPowerManager; 355 356 @Nullable 357 private Looper mIoLooper; 358 359 // Thread safe physical address 360 @GuardedBy("mLock") 361 private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS; 362 363 // Last input port before switching to the MHL port. Should switch back to this port 364 // when the mobile device sends the request one touch play with off. 365 // Gets invalidated if we go to other port/input. 366 @ServiceThreadOnly 367 private int mLastInputMhl = Constants.INVALID_PORT_ID; 368 369 // Set to true if the logical address allocation is completed. 370 private boolean mAddressAllocated = false; 371 372 // Buffer for processing the incoming cec messages while allocating logical addresses. 373 private final class CecMessageBuffer { 374 private List<HdmiCecMessage> mBuffer = new ArrayList<>(); 375 bufferMessage(HdmiCecMessage message)376 public boolean bufferMessage(HdmiCecMessage message) { 377 switch (message.getOpcode()) { 378 case Constants.MESSAGE_ACTIVE_SOURCE: 379 bufferActiveSource(message); 380 return true; 381 case Constants.MESSAGE_IMAGE_VIEW_ON: 382 case Constants.MESSAGE_TEXT_VIEW_ON: 383 bufferImageOrTextViewOn(message); 384 return true; 385 // Add here if new message that needs to buffer 386 default: 387 // Do not need to buffer messages other than above 388 return false; 389 } 390 } 391 processMessages()392 public void processMessages() { 393 for (final HdmiCecMessage message : mBuffer) { 394 runOnServiceThread(new Runnable() { 395 @Override 396 public void run() { 397 handleCecCommand(message); 398 } 399 }); 400 } 401 mBuffer.clear(); 402 } 403 bufferActiveSource(HdmiCecMessage message)404 private void bufferActiveSource(HdmiCecMessage message) { 405 if (!replaceMessageIfBuffered(message, Constants.MESSAGE_ACTIVE_SOURCE)) { 406 mBuffer.add(message); 407 } 408 } 409 bufferImageOrTextViewOn(HdmiCecMessage message)410 private void bufferImageOrTextViewOn(HdmiCecMessage message) { 411 if (!replaceMessageIfBuffered(message, Constants.MESSAGE_IMAGE_VIEW_ON) && 412 !replaceMessageIfBuffered(message, Constants.MESSAGE_TEXT_VIEW_ON)) { 413 mBuffer.add(message); 414 } 415 } 416 417 // Returns true if the message is replaced replaceMessageIfBuffered(HdmiCecMessage message, int opcode)418 private boolean replaceMessageIfBuffered(HdmiCecMessage message, int opcode) { 419 for (int i = 0; i < mBuffer.size(); i++) { 420 HdmiCecMessage bufferedMessage = mBuffer.get(i); 421 if (bufferedMessage.getOpcode() == opcode) { 422 mBuffer.set(i, message); 423 return true; 424 } 425 } 426 return false; 427 } 428 } 429 430 private final CecMessageBuffer mCecMessageBuffer = new CecMessageBuffer(); 431 432 private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer(); 433 HdmiControlService(Context context)434 public HdmiControlService(Context context) { 435 super(context); 436 List<Integer> deviceTypes = HdmiProperties.device_type(); 437 if (deviceTypes.contains(null)) { 438 Slog.w(TAG, "Error parsing ro.hdmi.device.type: " + SystemProperties.get( 439 "ro.hdmi.device_type")); 440 deviceTypes = deviceTypes.stream().filter(Objects::nonNull).collect( 441 Collectors.toList()); 442 } 443 mLocalDevices = deviceTypes; 444 mSettingsObserver = new SettingsObserver(mHandler); 445 } 446 getIntList(String string)447 protected static List<Integer> getIntList(String string) { 448 ArrayList<Integer> list = new ArrayList<>(); 449 TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(','); 450 splitter.setString(string); 451 for (String item : splitter) { 452 try { 453 list.add(Integer.parseInt(item)); 454 } catch (NumberFormatException e) { 455 Slog.w(TAG, "Can't parseInt: " + item); 456 } 457 } 458 return Collections.unmodifiableList(list); 459 } 460 461 @Override onStart()462 public void onStart() { 463 if (mIoLooper == null) { 464 mIoThread.start(); 465 mIoLooper = mIoThread.getLooper(); 466 } 467 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; 468 mProhibitMode = false; 469 mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true); 470 mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true); 471 472 if (mCecController == null) { 473 mCecController = HdmiCecController.create(this); 474 } 475 if (mCecController != null) { 476 if (mHdmiControlEnabled) { 477 initializeCec(INITIATED_BY_BOOT_UP); 478 } else { 479 mCecController.setOption(OptionKey.ENABLE_CEC, false); 480 } 481 } else { 482 Slog.i(TAG, "Device does not support HDMI-CEC."); 483 return; 484 } 485 if (mMhlController == null) { 486 mMhlController = HdmiMhlControllerStub.create(this); 487 } 488 if (!mMhlController.isReady()) { 489 Slog.i(TAG, "Device does not support MHL-control."); 490 } 491 mMhlDevices = Collections.emptyList(); 492 493 initPortInfo(); 494 if (mMessageValidator == null) { 495 mMessageValidator = new HdmiCecMessageValidator(this); 496 } 497 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); 498 499 if (mCecController != null) { 500 // Register broadcast receiver for power state change. 501 IntentFilter filter = new IntentFilter(); 502 filter.addAction(Intent.ACTION_SCREEN_OFF); 503 filter.addAction(Intent.ACTION_SCREEN_ON); 504 filter.addAction(Intent.ACTION_SHUTDOWN); 505 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); 506 getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter); 507 508 // Register ContentObserver to monitor the settings change. 509 registerContentObserver(); 510 } 511 mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED); 512 } 513 514 @VisibleForTesting setCecController(HdmiCecController cecController)515 void setCecController(HdmiCecController cecController) { 516 mCecController = cecController; 517 } 518 519 @VisibleForTesting setHdmiMhlController(HdmiMhlControllerStub hdmiMhlController)520 void setHdmiMhlController(HdmiMhlControllerStub hdmiMhlController) { 521 mMhlController = hdmiMhlController; 522 } 523 524 @Override onBootPhase(int phase)525 public void onBootPhase(int phase) { 526 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { 527 mTvInputManager = (TvInputManager) getContext().getSystemService( 528 Context.TV_INPUT_SERVICE); 529 mPowerManager = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); 530 } 531 } 532 getTvInputManager()533 TvInputManager getTvInputManager() { 534 return mTvInputManager; 535 } 536 registerTvInputCallback(TvInputCallback callback)537 void registerTvInputCallback(TvInputCallback callback) { 538 if (mTvInputManager == null) return; 539 mTvInputManager.registerCallback(callback, mHandler); 540 } 541 unregisterTvInputCallback(TvInputCallback callback)542 void unregisterTvInputCallback(TvInputCallback callback) { 543 if (mTvInputManager == null) return; 544 mTvInputManager.unregisterCallback(callback); 545 } 546 getPowerManager()547 PowerManager getPowerManager() { 548 return mPowerManager; 549 } 550 551 /** 552 * Called when the initialization of local devices is complete. 553 */ onInitializeCecComplete(int initiatedBy)554 private void onInitializeCecComplete(int initiatedBy) { 555 if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) { 556 mPowerStatus = HdmiControlManager.POWER_STATUS_ON; 557 } 558 mWakeUpMessageReceived = false; 559 560 if (isTvDeviceEnabled()) { 561 mCecController.setOption(OptionKey.WAKEUP, tv().getAutoWakeup()); 562 } 563 int reason = -1; 564 switch (initiatedBy) { 565 case INITIATED_BY_BOOT_UP: 566 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_START; 567 break; 568 case INITIATED_BY_ENABLE_CEC: 569 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING; 570 break; 571 case INITIATED_BY_SCREEN_ON: 572 case INITIATED_BY_WAKE_UP_MESSAGE: 573 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_WAKEUP; 574 break; 575 } 576 if (reason != -1) { 577 invokeVendorCommandListenersOnControlStateChanged(true, reason); 578 announceHdmiControlStatusChange(true); 579 } 580 } 581 registerContentObserver()582 private void registerContentObserver() { 583 ContentResolver resolver = getContext().getContentResolver(); 584 String[] settings = new String[] { 585 Global.HDMI_CONTROL_ENABLED, 586 Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, 587 Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, 588 Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, 589 Global.MHL_INPUT_SWITCHING_ENABLED, 590 Global.MHL_POWER_CHARGE_ENABLED, 591 Global.HDMI_CEC_SWITCH_ENABLED, 592 Global.DEVICE_NAME 593 }; 594 for (String s : settings) { 595 resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver, 596 UserHandle.USER_ALL); 597 } 598 } 599 600 private class SettingsObserver extends ContentObserver { SettingsObserver(Handler handler)601 public SettingsObserver(Handler handler) { 602 super(handler); 603 } 604 605 // onChange is set up to run in service thread. 606 @Override onChange(boolean selfChange, Uri uri)607 public void onChange(boolean selfChange, Uri uri) { 608 String option = uri.getLastPathSegment(); 609 boolean enabled = readBooleanSetting(option, true); 610 switch (option) { 611 case Global.HDMI_CONTROL_ENABLED: 612 setControlEnabled(enabled); 613 break; 614 case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED: 615 if (isTvDeviceEnabled()) { 616 tv().setAutoWakeup(enabled); 617 } 618 setCecOption(OptionKey.WAKEUP, enabled); 619 break; 620 case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED: 621 for (int type : mLocalDevices) { 622 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type); 623 if (localDevice != null) { 624 localDevice.setAutoDeviceOff(enabled); 625 } 626 } 627 // No need to propagate to HAL. 628 break; 629 case Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED: 630 if (isTvDeviceEnabled()) { 631 tv().setSystemAudioControlFeatureEnabled(enabled); 632 } 633 if (isAudioSystemDevice()) { 634 if (audioSystem() == null) { 635 Slog.e(TAG, "Audio System device has not registered yet." 636 + " Can't turn system audio mode on."); 637 break; 638 } 639 audioSystem().onSystemAduioControlFeatureSupportChanged(enabled); 640 } 641 break; 642 case Global.HDMI_CEC_SWITCH_ENABLED: 643 if (isAudioSystemDevice()) { 644 if (audioSystem() == null) { 645 Slog.w(TAG, "Switch device has not registered yet." 646 + " Can't turn routing on."); 647 break; 648 } 649 audioSystem().setRoutingControlFeatureEnables(enabled); 650 } 651 break; 652 case Global.MHL_INPUT_SWITCHING_ENABLED: 653 setMhlInputChangeEnabled(enabled); 654 break; 655 case Global.MHL_POWER_CHARGE_ENABLED: 656 mMhlController.setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled)); 657 break; 658 case Global.DEVICE_NAME: 659 String deviceName = readStringSetting(option, Build.MODEL); 660 setDisplayName(deviceName); 661 break; 662 } 663 } 664 } 665 toInt(boolean enabled)666 private static int toInt(boolean enabled) { 667 return enabled ? ENABLED : DISABLED; 668 } 669 670 @VisibleForTesting readBooleanSetting(String key, boolean defVal)671 boolean readBooleanSetting(String key, boolean defVal) { 672 ContentResolver cr = getContext().getContentResolver(); 673 return Global.getInt(cr, key, toInt(defVal)) == ENABLED; 674 } 675 writeBooleanSetting(String key, boolean value)676 void writeBooleanSetting(String key, boolean value) { 677 ContentResolver cr = getContext().getContentResolver(); 678 Global.putInt(cr, key, toInt(value)); 679 } 680 writeStringSystemProperty(String key, String value)681 void writeStringSystemProperty(String key, String value) { 682 SystemProperties.set(key, value); 683 } 684 685 @VisibleForTesting readBooleanSystemProperty(String key, boolean defVal)686 boolean readBooleanSystemProperty(String key, boolean defVal) { 687 return SystemProperties.getBoolean(key, defVal); 688 } 689 readStringSetting(String key, String defVal)690 String readStringSetting(String key, String defVal) { 691 ContentResolver cr = getContext().getContentResolver(); 692 String content = Global.getString(cr, key); 693 if (TextUtils.isEmpty(content)) { 694 return defVal; 695 } 696 return content; 697 } 698 initializeCec(int initiatedBy)699 private void initializeCec(int initiatedBy) { 700 mAddressAllocated = false; 701 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true); 702 mCecController.setLanguage(mLanguage); 703 initializeLocalDevices(initiatedBy); 704 } 705 706 @ServiceThreadOnly initializeLocalDevices(final int initiatedBy)707 private void initializeLocalDevices(final int initiatedBy) { 708 assertRunOnServiceThread(); 709 // A container for [Device type, Local device info]. 710 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>(); 711 for (int type : mLocalDevices) { 712 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type); 713 if (localDevice == null) { 714 localDevice = HdmiCecLocalDevice.create(this, type); 715 } 716 localDevice.init(); 717 localDevices.add(localDevice); 718 } 719 // It's now safe to flush existing local devices from mCecController since they were 720 // already moved to 'localDevices'. 721 clearLocalDevices(); 722 allocateLogicalAddress(localDevices, initiatedBy); 723 } 724 725 @ServiceThreadOnly 726 @VisibleForTesting allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices, final int initiatedBy)727 protected void allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices, 728 final int initiatedBy) { 729 assertRunOnServiceThread(); 730 mCecController.clearLogicalAddress(); 731 final ArrayList<HdmiCecLocalDevice> allocatedDevices = new ArrayList<>(); 732 final int[] finished = new int[1]; 733 mAddressAllocated = allocatingDevices.isEmpty(); 734 735 // For TV device, select request can be invoked while address allocation or device 736 // discovery is in progress. Initialize the request here at the start of allocation, 737 // and process the collected requests later when the allocation and device discovery 738 // is all completed. 739 mSelectRequestBuffer.clear(); 740 741 for (final HdmiCecLocalDevice localDevice : allocatingDevices) { 742 mCecController.allocateLogicalAddress(localDevice.getType(), 743 localDevice.getPreferredAddress(), new AllocateAddressCallback() { 744 @Override 745 public void onAllocated(int deviceType, int logicalAddress) { 746 if (logicalAddress == Constants.ADDR_UNREGISTERED) { 747 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]"); 748 } else { 749 // Set POWER_STATUS_ON to all local devices because they share lifetime 750 // with system. 751 HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType, 752 HdmiControlManager.POWER_STATUS_ON); 753 localDevice.setDeviceInfo(deviceInfo); 754 mCecController.addLocalDevice(deviceType, localDevice); 755 mCecController.addLogicalAddress(logicalAddress); 756 allocatedDevices.add(localDevice); 757 } 758 759 // Address allocation completed for all devices. Notify each device. 760 if (allocatingDevices.size() == ++finished[0]) { 761 mAddressAllocated = true; 762 if (initiatedBy != INITIATED_BY_HOTPLUG) { 763 // In case of the hotplug we don't call onInitializeCecComplete() 764 // since we reallocate the logical address only. 765 onInitializeCecComplete(initiatedBy); 766 } 767 notifyAddressAllocated(allocatedDevices, initiatedBy); 768 mCecMessageBuffer.processMessages(); 769 } 770 } 771 }); 772 } 773 } 774 775 @ServiceThreadOnly notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy)776 private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) { 777 assertRunOnServiceThread(); 778 for (HdmiCecLocalDevice device : devices) { 779 int address = device.getDeviceInfo().getLogicalAddress(); 780 device.handleAddressAllocated(address, initiatedBy); 781 } 782 if (isTvDeviceEnabled()) { 783 tv().setSelectRequestBuffer(mSelectRequestBuffer); 784 } 785 } 786 isAddressAllocated()787 boolean isAddressAllocated() { 788 return mAddressAllocated; 789 } 790 791 // Initialize HDMI port information. Combine the information from CEC and MHL HAL and 792 // keep them in one place. 793 @ServiceThreadOnly 794 @VisibleForTesting initPortInfo()795 protected void initPortInfo() { 796 assertRunOnServiceThread(); 797 HdmiPortInfo[] cecPortInfo = null; 798 799 synchronized (mLock) { 800 mPhysicalAddress = getPhysicalAddress(); 801 } 802 803 // CEC HAL provides majority of the info while MHL does only MHL support flag for 804 // each port. Return empty array if CEC HAL didn't provide the info. 805 if (mCecController != null) { 806 cecPortInfo = mCecController.getPortInfos(); 807 } 808 if (cecPortInfo == null) { 809 return; 810 } 811 812 SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>(); 813 SparseIntArray portIdMap = new SparseIntArray(); 814 SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>(); 815 for (HdmiPortInfo info : cecPortInfo) { 816 portIdMap.put(info.getAddress(), info.getId()); 817 portInfoMap.put(info.getId(), info); 818 portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId())); 819 } 820 mPortIdMap = new UnmodifiableSparseIntArray(portIdMap); 821 mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap); 822 mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap); 823 824 if (mMhlController == null) { 825 return; 826 } 827 HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos(); 828 ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length); 829 for (HdmiPortInfo info : mhlPortInfo) { 830 if (info.isMhlSupported()) { 831 mhlSupportedPorts.add(info.getId()); 832 } 833 } 834 835 // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use 836 // cec port info if we do not have have port that supports MHL. 837 if (mhlSupportedPorts.isEmpty()) { 838 mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo)); 839 return; 840 } 841 ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length); 842 for (HdmiPortInfo info : cecPortInfo) { 843 if (mhlSupportedPorts.contains(info.getId())) { 844 result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(), 845 info.isCecSupported(), true, info.isArcSupported())); 846 } else { 847 result.add(info); 848 } 849 } 850 mPortInfo = Collections.unmodifiableList(result); 851 } 852 getPortInfo()853 List<HdmiPortInfo> getPortInfo() { 854 return mPortInfo; 855 } 856 857 /** 858 * Returns HDMI port information for the given port id. 859 * 860 * @param portId HDMI port id 861 * @return {@link HdmiPortInfo} for the given port 862 */ getPortInfo(int portId)863 HdmiPortInfo getPortInfo(int portId) { 864 return mPortInfoMap.get(portId, null); 865 } 866 867 /** 868 * Returns the routing path (physical address) of the HDMI port for the given 869 * port id. 870 */ portIdToPath(int portId)871 int portIdToPath(int portId) { 872 HdmiPortInfo portInfo = getPortInfo(portId); 873 if (portInfo == null) { 874 Slog.e(TAG, "Cannot find the port info: " + portId); 875 return Constants.INVALID_PHYSICAL_ADDRESS; 876 } 877 return portInfo.getAddress(); 878 } 879 880 /** 881 * Returns the id of HDMI port located at the current device that runs this method. 882 * 883 * For TV with physical address 0x0000, target device 0x1120, we want port physical address 884 * 0x1000 to get the correct port id from {@link #mPortIdMap}. For device with Physical Address 885 * 0x2000, target device 0x2420, we want port address 0x24000 to get the port id. 886 * 887 * <p>Return {@link Constants#INVALID_PORT_ID} if target device does not connect to. 888 * 889 * @param path the target device's physical address. 890 * @return the id of the port that the target device eventually connects to 891 * on the current device. 892 */ pathToPortId(int path)893 int pathToPortId(int path) { 894 int mask = 0xF000; 895 int finalMask = 0xF000; 896 int physicalAddress; 897 synchronized (mLock) { 898 physicalAddress = mPhysicalAddress; 899 } 900 int maskedAddress = physicalAddress; 901 902 while (maskedAddress != 0) { 903 maskedAddress = physicalAddress & mask; 904 finalMask |= mask; 905 mask >>= 4; 906 } 907 908 int portAddress = path & finalMask; 909 return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID); 910 } 911 isValidPortId(int portId)912 boolean isValidPortId(int portId) { 913 return getPortInfo(portId) != null; 914 } 915 916 /** 917 * Returns {@link Looper} for IO operation. 918 * 919 * <p>Declared as package-private. 920 */ 921 @Nullable getIoLooper()922 Looper getIoLooper() { 923 return mIoLooper; 924 } 925 926 @VisibleForTesting setIoLooper(Looper ioLooper)927 void setIoLooper(Looper ioLooper) { 928 mIoLooper = ioLooper; 929 } 930 931 @VisibleForTesting setMessageValidator(HdmiCecMessageValidator messageValidator)932 void setMessageValidator(HdmiCecMessageValidator messageValidator) { 933 mMessageValidator = messageValidator; 934 } 935 936 /** 937 * Returns {@link Looper} of main thread. Use this {@link Looper} instance 938 * for tasks that are running on main service thread. 939 * 940 * <p>Declared as package-private. 941 */ getServiceLooper()942 Looper getServiceLooper() { 943 return mHandler.getLooper(); 944 } 945 946 /** 947 * Returns physical address of the device. 948 */ getPhysicalAddress()949 int getPhysicalAddress() { 950 return mCecController.getPhysicalAddress(); 951 } 952 953 /** 954 * Returns vendor id of CEC service. 955 */ getVendorId()956 int getVendorId() { 957 return mCecController.getVendorId(); 958 } 959 960 @ServiceThreadOnly getDeviceInfo(int logicalAddress)961 HdmiDeviceInfo getDeviceInfo(int logicalAddress) { 962 assertRunOnServiceThread(); 963 return tv() == null ? null : tv().getCecDeviceInfo(logicalAddress); 964 } 965 966 @ServiceThreadOnly getDeviceInfoByPort(int port)967 HdmiDeviceInfo getDeviceInfoByPort(int port) { 968 assertRunOnServiceThread(); 969 HdmiMhlLocalDeviceStub info = mMhlController.getLocalDevice(port); 970 if (info != null) { 971 return info.getInfo(); 972 } 973 return null; 974 } 975 976 /** 977 * Returns version of CEC. 978 */ getCecVersion()979 int getCecVersion() { 980 return mCecController.getVersion(); 981 } 982 983 /** 984 * Whether a device of the specified physical address is connected to ARC enabled port. 985 */ isConnectedToArcPort(int physicalAddress)986 boolean isConnectedToArcPort(int physicalAddress) { 987 int portId = pathToPortId(physicalAddress); 988 if (portId != Constants.INVALID_PORT_ID) { 989 return mPortInfoMap.get(portId).isArcSupported(); 990 } 991 return false; 992 } 993 994 @ServiceThreadOnly isConnected(int portId)995 boolean isConnected(int portId) { 996 assertRunOnServiceThread(); 997 return mCecController.isConnected(portId); 998 } 999 runOnServiceThread(Runnable runnable)1000 void runOnServiceThread(Runnable runnable) { 1001 mHandler.post(runnable); 1002 } 1003 runOnServiceThreadAtFrontOfQueue(Runnable runnable)1004 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) { 1005 mHandler.postAtFrontOfQueue(runnable); 1006 } 1007 assertRunOnServiceThread()1008 private void assertRunOnServiceThread() { 1009 if (Looper.myLooper() != mHandler.getLooper()) { 1010 throw new IllegalStateException("Should run on service thread."); 1011 } 1012 } 1013 1014 /** 1015 * Transmit a CEC command to CEC bus. 1016 * 1017 * @param command CEC command to send out 1018 * @param callback interface used to the result of send command 1019 */ 1020 @ServiceThreadOnly sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback)1021 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) { 1022 assertRunOnServiceThread(); 1023 if (mMessageValidator.isValid(command) == HdmiCecMessageValidator.OK) { 1024 mCecController.sendCommand(command, callback); 1025 } else { 1026 HdmiLogger.error("Invalid message type:" + command); 1027 if (callback != null) { 1028 callback.onSendCompleted(SendMessageResult.FAIL); 1029 } 1030 } 1031 } 1032 1033 @ServiceThreadOnly sendCecCommand(HdmiCecMessage command)1034 void sendCecCommand(HdmiCecMessage command) { 1035 assertRunOnServiceThread(); 1036 sendCecCommand(command, null); 1037 } 1038 1039 /** 1040 * Send <Feature Abort> command on the given CEC message if possible. 1041 * If the aborted message is invalid, then it wont send the message. 1042 * @param command original command to be aborted 1043 * @param reason reason of feature abort 1044 */ 1045 @ServiceThreadOnly maySendFeatureAbortCommand(HdmiCecMessage command, int reason)1046 void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) { 1047 assertRunOnServiceThread(); 1048 mCecController.maySendFeatureAbortCommand(command, reason); 1049 } 1050 1051 @ServiceThreadOnly handleCecCommand(HdmiCecMessage message)1052 boolean handleCecCommand(HdmiCecMessage message) { 1053 assertRunOnServiceThread(); 1054 int errorCode = mMessageValidator.isValid(message); 1055 if (errorCode != HdmiCecMessageValidator.OK) { 1056 // We'll not response on the messages with the invalid source or destination 1057 // or with parameter length shorter than specified in the standard. 1058 if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) { 1059 maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND); 1060 } 1061 return true; 1062 } 1063 1064 if (dispatchMessageToLocalDevice(message)) { 1065 return true; 1066 } 1067 1068 return (!mAddressAllocated) ? mCecMessageBuffer.bufferMessage(message) : false; 1069 } 1070 enableAudioReturnChannel(int portId, boolean enabled)1071 void enableAudioReturnChannel(int portId, boolean enabled) { 1072 mCecController.enableAudioReturnChannel(portId, enabled); 1073 } 1074 1075 @ServiceThreadOnly dispatchMessageToLocalDevice(HdmiCecMessage message)1076 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) { 1077 assertRunOnServiceThread(); 1078 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 1079 if (device.dispatchMessage(message) 1080 && message.getDestination() != Constants.ADDR_BROADCAST) { 1081 return true; 1082 } 1083 } 1084 1085 if (message.getDestination() != Constants.ADDR_BROADCAST) { 1086 HdmiLogger.warning("Unhandled cec command:" + message); 1087 } 1088 return false; 1089 } 1090 1091 /** 1092 * Called when a new hotplug event is issued. 1093 * 1094 * @param portId hdmi port number where hot plug event issued. 1095 * @param connected whether to be plugged in or not 1096 */ 1097 @ServiceThreadOnly onHotplug(int portId, boolean connected)1098 void onHotplug(int portId, boolean connected) { 1099 assertRunOnServiceThread(); 1100 1101 if (connected && !isTvDevice() 1102 && getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) { 1103 if (isSwitchDevice()) { 1104 initPortInfo(); 1105 HdmiLogger.debug("initPortInfo for switch device when onHotplug from tx."); 1106 } 1107 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>(); 1108 for (int type : mLocalDevices) { 1109 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type); 1110 if (localDevice == null) { 1111 localDevice = HdmiCecLocalDevice.create(this, type); 1112 localDevice.init(); 1113 } 1114 localDevices.add(localDevice); 1115 } 1116 allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG); 1117 } 1118 1119 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 1120 device.onHotplug(portId, connected); 1121 } 1122 announceHotplugEvent(portId, connected); 1123 } 1124 1125 /** 1126 * Poll all remote devices. It sends <Polling Message> to all remote 1127 * devices. 1128 * 1129 * @param callback an interface used to get a list of all remote devices' address 1130 * @param sourceAddress a logical address of source device where sends polling message 1131 * @param pickStrategy strategy how to pick polling candidates 1132 * @param retryCount the number of retry used to send polling message to remote devices 1133 * @throws IllegalArgumentException if {@code pickStrategy} is invalid value 1134 */ 1135 @ServiceThreadOnly pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, int retryCount)1136 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, 1137 int retryCount) { 1138 assertRunOnServiceThread(); 1139 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy), 1140 retryCount); 1141 } 1142 checkPollStrategy(int pickStrategy)1143 private int checkPollStrategy(int pickStrategy) { 1144 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK; 1145 if (strategy == 0) { 1146 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy); 1147 } 1148 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK; 1149 if (iterationStrategy == 0) { 1150 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy); 1151 } 1152 return strategy | iterationStrategy; 1153 } 1154 getAllLocalDevices()1155 List<HdmiCecLocalDevice> getAllLocalDevices() { 1156 assertRunOnServiceThread(); 1157 return mCecController.getLocalDeviceList(); 1158 } 1159 getServiceLock()1160 Object getServiceLock() { 1161 return mLock; 1162 } 1163 setAudioStatus(boolean mute, int volume)1164 void setAudioStatus(boolean mute, int volume) { 1165 if (!isTvDeviceEnabled() || !tv().isSystemAudioActivated()) { 1166 return; 1167 } 1168 AudioManager audioManager = getAudioManager(); 1169 boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC); 1170 if (mute) { 1171 if (!muted) { 1172 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true); 1173 } 1174 } else { 1175 if (muted) { 1176 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false); 1177 } 1178 // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing 1179 // volume change notification back to hdmi control service. 1180 int flag = AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME; 1181 if (0 <= volume && volume <= 100) { 1182 Slog.i(TAG, "volume: " + volume); 1183 flag |= AudioManager.FLAG_SHOW_UI; 1184 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, flag); 1185 } 1186 } 1187 } 1188 announceSystemAudioModeChange(boolean enabled)1189 void announceSystemAudioModeChange(boolean enabled) { 1190 synchronized (mLock) { 1191 for (SystemAudioModeChangeListenerRecord record : 1192 mSystemAudioModeChangeListenerRecords) { 1193 invokeSystemAudioModeChangeLocked(record.mListener, enabled); 1194 } 1195 } 1196 } 1197 createDeviceInfo(int logicalAddress, int deviceType, int powerStatus)1198 private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) { 1199 String displayName = readStringSetting(Global.DEVICE_NAME, Build.MODEL); 1200 return new HdmiDeviceInfo(logicalAddress, 1201 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType, 1202 getVendorId(), displayName, powerStatus); 1203 } 1204 1205 // Set the display name in HdmiDeviceInfo of the current devices to content provided by 1206 // Global.DEVICE_NAME. Only set and broadcast if the new name is different. setDisplayName(String newDisplayName)1207 private void setDisplayName(String newDisplayName) { 1208 for (HdmiCecLocalDevice device : getAllLocalDevices()) { 1209 HdmiDeviceInfo deviceInfo = device.getDeviceInfo(); 1210 if (deviceInfo.getDisplayName().equals(newDisplayName)) { 1211 continue; 1212 } 1213 device.setDeviceInfo(new HdmiDeviceInfo( 1214 deviceInfo.getLogicalAddress(), deviceInfo.getPhysicalAddress(), 1215 deviceInfo.getPortId(), deviceInfo.getDeviceType(), deviceInfo.getVendorId(), 1216 newDisplayName, deviceInfo.getDevicePowerStatus())); 1217 sendCecCommand(HdmiCecMessageBuilder.buildSetOsdNameCommand( 1218 device.mAddress, Constants.ADDR_TV, newDisplayName)); 1219 } 1220 } 1221 1222 @ServiceThreadOnly handleMhlHotplugEvent(int portId, boolean connected)1223 void handleMhlHotplugEvent(int portId, boolean connected) { 1224 assertRunOnServiceThread(); 1225 // Hotplug event is used to add/remove MHL devices as TV input. 1226 if (connected) { 1227 HdmiMhlLocalDeviceStub newDevice = new HdmiMhlLocalDeviceStub(this, portId); 1228 HdmiMhlLocalDeviceStub oldDevice = mMhlController.addLocalDevice(newDevice); 1229 if (oldDevice != null) { 1230 oldDevice.onDeviceRemoved(); 1231 Slog.i(TAG, "Old device of port " + portId + " is removed"); 1232 } 1233 invokeDeviceEventListeners(newDevice.getInfo(), DEVICE_EVENT_ADD_DEVICE); 1234 updateSafeMhlInput(); 1235 } else { 1236 HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId); 1237 if (device != null) { 1238 device.onDeviceRemoved(); 1239 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE); 1240 updateSafeMhlInput(); 1241 } else { 1242 Slog.w(TAG, "No device to remove:[portId=" + portId); 1243 } 1244 } 1245 announceHotplugEvent(portId, connected); 1246 } 1247 1248 @ServiceThreadOnly handleMhlBusModeChanged(int portId, int busmode)1249 void handleMhlBusModeChanged(int portId, int busmode) { 1250 assertRunOnServiceThread(); 1251 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); 1252 if (device != null) { 1253 device.setBusMode(busmode); 1254 } else { 1255 Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId + 1256 ", busmode:" + busmode + "]"); 1257 } 1258 } 1259 1260 @ServiceThreadOnly handleMhlBusOvercurrent(int portId, boolean on)1261 void handleMhlBusOvercurrent(int portId, boolean on) { 1262 assertRunOnServiceThread(); 1263 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); 1264 if (device != null) { 1265 device.onBusOvercurrentDetected(on); 1266 } else { 1267 Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]"); 1268 } 1269 } 1270 1271 @ServiceThreadOnly handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId)1272 void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) { 1273 assertRunOnServiceThread(); 1274 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); 1275 1276 if (device != null) { 1277 device.setDeviceStatusChange(adopterId, deviceId); 1278 } else { 1279 Slog.w(TAG, "No mhl device exists for device status event[portId:" 1280 + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]"); 1281 } 1282 } 1283 1284 @ServiceThreadOnly updateSafeMhlInput()1285 private void updateSafeMhlInput() { 1286 assertRunOnServiceThread(); 1287 List<HdmiDeviceInfo> inputs = Collections.emptyList(); 1288 SparseArray<HdmiMhlLocalDeviceStub> devices = mMhlController.getAllLocalDevices(); 1289 for (int i = 0; i < devices.size(); ++i) { 1290 HdmiMhlLocalDeviceStub device = devices.valueAt(i); 1291 HdmiDeviceInfo info = device.getInfo(); 1292 if (info != null) { 1293 if (inputs.isEmpty()) { 1294 inputs = new ArrayList<>(); 1295 } 1296 inputs.add(device.getInfo()); 1297 } 1298 } 1299 synchronized (mLock) { 1300 mMhlDevices = inputs; 1301 } 1302 } 1303 1304 @GuardedBy("mLock") getMhlDevicesLocked()1305 private List<HdmiDeviceInfo> getMhlDevicesLocked() { 1306 return mMhlDevices; 1307 } 1308 1309 private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient { 1310 private final IHdmiMhlVendorCommandListener mListener; 1311 HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener)1312 public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) { 1313 mListener = listener; 1314 } 1315 1316 @Override binderDied()1317 public void binderDied() { 1318 mMhlVendorCommandListenerRecords.remove(this); 1319 } 1320 } 1321 1322 // Record class that monitors the event of the caller of being killed. Used to clean up 1323 // the listener list and record list accordingly. 1324 private final class HdmiControlStatusChangeListenerRecord implements IBinder.DeathRecipient { 1325 private final IHdmiControlStatusChangeListener mListener; 1326 HdmiControlStatusChangeListenerRecord(IHdmiControlStatusChangeListener listener)1327 HdmiControlStatusChangeListenerRecord(IHdmiControlStatusChangeListener listener) { 1328 mListener = listener; 1329 } 1330 1331 @Override binderDied()1332 public void binderDied() { 1333 synchronized (mLock) { 1334 mHdmiControlStatusChangeListenerRecords.remove(this); 1335 } 1336 } 1337 1338 @Override equals(Object obj)1339 public boolean equals(Object obj) { 1340 if (!(obj instanceof HdmiControlStatusChangeListenerRecord)) return false; 1341 if (obj == this) return true; 1342 HdmiControlStatusChangeListenerRecord other = 1343 (HdmiControlStatusChangeListenerRecord) obj; 1344 return other.mListener == this.mListener; 1345 } 1346 1347 @Override hashCode()1348 public int hashCode() { 1349 return mListener.hashCode(); 1350 } 1351 } 1352 1353 // Record class that monitors the event of the caller of being killed. Used to clean up 1354 // the listener list and record list accordingly. 1355 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { 1356 private final IHdmiHotplugEventListener mListener; 1357 HotplugEventListenerRecord(IHdmiHotplugEventListener listener)1358 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) { 1359 mListener = listener; 1360 } 1361 1362 @Override binderDied()1363 public void binderDied() { 1364 synchronized (mLock) { 1365 mHotplugEventListenerRecords.remove(this); 1366 } 1367 } 1368 1369 @Override equals(Object obj)1370 public boolean equals(Object obj) { 1371 if (!(obj instanceof HotplugEventListenerRecord)) return false; 1372 if (obj == this) return true; 1373 HotplugEventListenerRecord other = (HotplugEventListenerRecord) obj; 1374 return other.mListener == this.mListener; 1375 } 1376 1377 @Override hashCode()1378 public int hashCode() { 1379 return mListener.hashCode(); 1380 } 1381 } 1382 1383 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient { 1384 private final IHdmiDeviceEventListener mListener; 1385 DeviceEventListenerRecord(IHdmiDeviceEventListener listener)1386 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) { 1387 mListener = listener; 1388 } 1389 1390 @Override binderDied()1391 public void binderDied() { 1392 synchronized (mLock) { 1393 mDeviceEventListenerRecords.remove(this); 1394 } 1395 } 1396 } 1397 1398 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient { 1399 private final IHdmiSystemAudioModeChangeListener mListener; 1400 SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener)1401 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) { 1402 mListener = listener; 1403 } 1404 1405 @Override binderDied()1406 public void binderDied() { 1407 synchronized (mLock) { 1408 mSystemAudioModeChangeListenerRecords.remove(this); 1409 } 1410 } 1411 } 1412 1413 class VendorCommandListenerRecord implements IBinder.DeathRecipient { 1414 private final IHdmiVendorCommandListener mListener; 1415 private final int mDeviceType; 1416 VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType)1417 public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) { 1418 mListener = listener; 1419 mDeviceType = deviceType; 1420 } 1421 1422 @Override binderDied()1423 public void binderDied() { 1424 synchronized (mLock) { 1425 mVendorCommandListenerRecords.remove(this); 1426 } 1427 } 1428 } 1429 1430 private class HdmiRecordListenerRecord implements IBinder.DeathRecipient { 1431 private final IHdmiRecordListener mListener; 1432 HdmiRecordListenerRecord(IHdmiRecordListener listener)1433 public HdmiRecordListenerRecord(IHdmiRecordListener listener) { 1434 mListener = listener; 1435 } 1436 1437 @Override binderDied()1438 public void binderDied() { 1439 synchronized (mLock) { 1440 if (mRecordListenerRecord == this) { 1441 mRecordListenerRecord = null; 1442 } 1443 } 1444 } 1445 } 1446 enforceAccessPermission()1447 private void enforceAccessPermission() { 1448 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); 1449 } 1450 1451 private final class BinderService extends IHdmiControlService.Stub { 1452 @Override getSupportedTypes()1453 public int[] getSupportedTypes() { 1454 enforceAccessPermission(); 1455 // mLocalDevices is an unmodifiable list - no lock necesary. 1456 int[] localDevices = new int[mLocalDevices.size()]; 1457 for (int i = 0; i < localDevices.length; ++i) { 1458 localDevices[i] = mLocalDevices.get(i); 1459 } 1460 return localDevices; 1461 } 1462 1463 @Override 1464 @Nullable getActiveSource()1465 public HdmiDeviceInfo getActiveSource() { 1466 enforceAccessPermission(); 1467 HdmiCecLocalDeviceTv tv = tv(); 1468 if (tv == null) { 1469 if (isTvDevice()) { 1470 Slog.e(TAG, "Local tv device not available."); 1471 return null; 1472 } 1473 if (isPlaybackDevice()) { 1474 // if playback device itself is the active source, 1475 // return its own device info. 1476 if (playback() != null && playback().mIsActiveSource) { 1477 return playback().getDeviceInfo(); 1478 } 1479 // Otherwise get the active source and look for it from the device list 1480 ActiveSource activeSource = mActiveSource; 1481 // If the active source is not set yet, return null 1482 if (!activeSource.isValid()) { 1483 return null; 1484 } 1485 if (audioSystem() != null) { 1486 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem(); 1487 for (HdmiDeviceInfo info : audioSystem.getSafeCecDevicesLocked()) { 1488 if (info.getLogicalAddress() == activeSource.logicalAddress) { 1489 return info; 1490 } 1491 } 1492 } 1493 // If the device info is not in the list yet, return a device info with minimum 1494 // information from mActiveSource. 1495 return new HdmiDeviceInfo(activeSource.logicalAddress, 1496 activeSource.physicalAddress, pathToPortId(activeSource.physicalAddress), 1497 HdmiUtils.getTypeFromAddress(activeSource.logicalAddress), 0, 1498 HdmiUtils.getDefaultDeviceName(activeSource.logicalAddress)); 1499 } 1500 return null; 1501 } 1502 ActiveSource activeSource = tv.getActiveSource(); 1503 if (activeSource.isValid()) { 1504 return new HdmiDeviceInfo(activeSource.logicalAddress, 1505 activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID, 1506 HdmiDeviceInfo.DEVICE_INACTIVE, 0, ""); 1507 } 1508 int activePath = tv.getActivePath(); 1509 if (activePath != HdmiDeviceInfo.PATH_INVALID) { 1510 HdmiDeviceInfo info = tv.getSafeDeviceInfoByPath(activePath); 1511 return (info != null) ? info : new HdmiDeviceInfo(activePath, tv.getActivePortId()); 1512 } 1513 return null; 1514 } 1515 1516 @Override deviceSelect(final int deviceId, final IHdmiControlCallback callback)1517 public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) { 1518 enforceAccessPermission(); 1519 runOnServiceThread(new Runnable() { 1520 @Override 1521 public void run() { 1522 if (callback == null) { 1523 Slog.e(TAG, "Callback cannot be null"); 1524 return; 1525 } 1526 HdmiCecLocalDeviceTv tv = tv(); 1527 if (tv == null) { 1528 if (!mAddressAllocated) { 1529 mSelectRequestBuffer.set(SelectRequestBuffer.newDeviceSelect( 1530 HdmiControlService.this, deviceId, callback)); 1531 return; 1532 } 1533 if (isTvDevice()) { 1534 Slog.e(TAG, "Local tv device not available"); 1535 return; 1536 } 1537 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1538 return; 1539 } 1540 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId); 1541 if (device != null) { 1542 if (device.getPortId() == tv.getActivePortId()) { 1543 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); 1544 return; 1545 } 1546 // Upon selecting MHL device, we send RAP[Content On] to wake up 1547 // the connected mobile device, start routing control to switch ports. 1548 // callback is handled by MHL action. 1549 device.turnOn(callback); 1550 tv.doManualPortSwitching(device.getPortId(), null); 1551 return; 1552 } 1553 tv.deviceSelect(deviceId, callback); 1554 } 1555 }); 1556 } 1557 1558 @Override portSelect(final int portId, final IHdmiControlCallback callback)1559 public void portSelect(final int portId, final IHdmiControlCallback callback) { 1560 enforceAccessPermission(); 1561 runOnServiceThread(new Runnable() { 1562 @Override 1563 public void run() { 1564 if (callback == null) { 1565 Slog.e(TAG, "Callback cannot be null"); 1566 return; 1567 } 1568 HdmiCecLocalDeviceTv tv = tv(); 1569 if (tv != null) { 1570 tv.doManualPortSwitching(portId, callback); 1571 return; 1572 } 1573 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem(); 1574 if (audioSystem != null) { 1575 audioSystem.doManualPortSwitching(portId, callback); 1576 return; 1577 } 1578 1579 if (!mAddressAllocated) { 1580 mSelectRequestBuffer.set(SelectRequestBuffer.newPortSelect( 1581 HdmiControlService.this, portId, callback)); 1582 return; 1583 } 1584 Slog.w(TAG, "Local device not available"); 1585 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1586 return; 1587 } 1588 }); 1589 } 1590 1591 @Override sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed)1592 public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) { 1593 enforceAccessPermission(); 1594 runOnServiceThread(new Runnable() { 1595 @Override 1596 public void run() { 1597 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId); 1598 if (device != null) { 1599 device.sendKeyEvent(keyCode, isPressed); 1600 return; 1601 } 1602 if (mCecController != null) { 1603 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType); 1604 if (localDevice == null) { 1605 Slog.w(TAG, "Local device not available to send key event."); 1606 return; 1607 } 1608 localDevice.sendKeyEvent(keyCode, isPressed); 1609 } 1610 } 1611 }); 1612 } 1613 1614 @Override sendVolumeKeyEvent( final int deviceType, final int keyCode, final boolean isPressed)1615 public void sendVolumeKeyEvent( 1616 final int deviceType, final int keyCode, final boolean isPressed) { 1617 enforceAccessPermission(); 1618 runOnServiceThread(new Runnable() { 1619 @Override 1620 public void run() { 1621 if (mCecController == null) { 1622 Slog.w(TAG, "CEC controller not available to send volume key event."); 1623 return; 1624 } 1625 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType); 1626 if (localDevice == null) { 1627 Slog.w(TAG, "Local device " + deviceType 1628 + " not available to send volume key event."); 1629 return; 1630 } 1631 localDevice.sendVolumeKeyEvent(keyCode, isPressed); 1632 } 1633 }); 1634 } 1635 1636 @Override oneTouchPlay(final IHdmiControlCallback callback)1637 public void oneTouchPlay(final IHdmiControlCallback callback) { 1638 enforceAccessPermission(); 1639 runOnServiceThread(new Runnable() { 1640 @Override 1641 public void run() { 1642 HdmiControlService.this.oneTouchPlay(callback); 1643 } 1644 }); 1645 } 1646 1647 @Override queryDisplayStatus(final IHdmiControlCallback callback)1648 public void queryDisplayStatus(final IHdmiControlCallback callback) { 1649 enforceAccessPermission(); 1650 runOnServiceThread(new Runnable() { 1651 @Override 1652 public void run() { 1653 HdmiControlService.this.queryDisplayStatus(callback); 1654 } 1655 }); 1656 } 1657 1658 @Override addHdmiControlStatusChangeListener( final IHdmiControlStatusChangeListener listener)1659 public void addHdmiControlStatusChangeListener( 1660 final IHdmiControlStatusChangeListener listener) { 1661 enforceAccessPermission(); 1662 HdmiControlService.this.addHdmiControlStatusChangeListener(listener); 1663 } 1664 1665 @Override removeHdmiControlStatusChangeListener( final IHdmiControlStatusChangeListener listener)1666 public void removeHdmiControlStatusChangeListener( 1667 final IHdmiControlStatusChangeListener listener) { 1668 enforceAccessPermission(); 1669 HdmiControlService.this.removeHdmiControlStatusChangeListener(listener); 1670 } 1671 1672 @Override addHotplugEventListener(final IHdmiHotplugEventListener listener)1673 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 1674 enforceAccessPermission(); 1675 HdmiControlService.this.addHotplugEventListener(listener); 1676 } 1677 1678 @Override removeHotplugEventListener(final IHdmiHotplugEventListener listener)1679 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) { 1680 enforceAccessPermission(); 1681 HdmiControlService.this.removeHotplugEventListener(listener); 1682 } 1683 1684 @Override addDeviceEventListener(final IHdmiDeviceEventListener listener)1685 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) { 1686 enforceAccessPermission(); 1687 HdmiControlService.this.addDeviceEventListener(listener); 1688 } 1689 1690 @Override getPortInfo()1691 public List<HdmiPortInfo> getPortInfo() { 1692 enforceAccessPermission(); 1693 return HdmiControlService.this.getPortInfo(); 1694 } 1695 1696 @Override canChangeSystemAudioMode()1697 public boolean canChangeSystemAudioMode() { 1698 enforceAccessPermission(); 1699 HdmiCecLocalDeviceTv tv = tv(); 1700 if (tv == null) { 1701 return false; 1702 } 1703 return tv.hasSystemAudioDevice(); 1704 } 1705 1706 @Override getSystemAudioMode()1707 public boolean getSystemAudioMode() { 1708 // TODO(shubang): handle getSystemAudioMode() for all device types 1709 enforceAccessPermission(); 1710 HdmiCecLocalDeviceTv tv = tv(); 1711 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem(); 1712 return (tv != null && tv.isSystemAudioActivated()) 1713 || (audioSystem != null && audioSystem.isSystemAudioActivated()); 1714 } 1715 1716 @Override getPhysicalAddress()1717 public int getPhysicalAddress() { 1718 enforceAccessPermission(); 1719 synchronized (mLock) { 1720 return mPhysicalAddress; 1721 } 1722 } 1723 1724 @Override setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback)1725 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) { 1726 enforceAccessPermission(); 1727 runOnServiceThread(new Runnable() { 1728 @Override 1729 public void run() { 1730 HdmiCecLocalDeviceTv tv = tv(); 1731 if (tv == null) { 1732 Slog.w(TAG, "Local tv device not available"); 1733 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1734 return; 1735 } 1736 tv.changeSystemAudioMode(enabled, callback); 1737 } 1738 }); 1739 } 1740 1741 @Override addSystemAudioModeChangeListener( final IHdmiSystemAudioModeChangeListener listener)1742 public void addSystemAudioModeChangeListener( 1743 final IHdmiSystemAudioModeChangeListener listener) { 1744 enforceAccessPermission(); 1745 HdmiControlService.this.addSystemAudioModeChangeListner(listener); 1746 } 1747 1748 @Override removeSystemAudioModeChangeListener( final IHdmiSystemAudioModeChangeListener listener)1749 public void removeSystemAudioModeChangeListener( 1750 final IHdmiSystemAudioModeChangeListener listener) { 1751 enforceAccessPermission(); 1752 HdmiControlService.this.removeSystemAudioModeChangeListener(listener); 1753 } 1754 1755 @Override setInputChangeListener(final IHdmiInputChangeListener listener)1756 public void setInputChangeListener(final IHdmiInputChangeListener listener) { 1757 enforceAccessPermission(); 1758 HdmiControlService.this.setInputChangeListener(listener); 1759 } 1760 1761 @Override getInputDevices()1762 public List<HdmiDeviceInfo> getInputDevices() { 1763 enforceAccessPermission(); 1764 // No need to hold the lock for obtaining TV device as the local device instance 1765 // is preserved while the HDMI control is enabled. 1766 HdmiCecLocalDeviceTv tv = tv(); 1767 synchronized (mLock) { 1768 List<HdmiDeviceInfo> cecDevices = (tv == null) 1769 ? Collections.<HdmiDeviceInfo>emptyList() 1770 : tv.getSafeExternalInputsLocked(); 1771 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked()); 1772 } 1773 } 1774 1775 // Returns all the CEC devices on the bus including system audio, switch, 1776 // even those of reserved type. 1777 @Override getDeviceList()1778 public List<HdmiDeviceInfo> getDeviceList() { 1779 enforceAccessPermission(); 1780 HdmiCecLocalDeviceTv tv = tv(); 1781 if (tv != null) { 1782 synchronized (mLock) { 1783 return tv.getSafeCecDevicesLocked(); 1784 } 1785 } else { 1786 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem(); 1787 synchronized (mLock) { 1788 return (audioSystem == null) 1789 ? Collections.<HdmiDeviceInfo>emptyList() 1790 : audioSystem.getSafeCecDevicesLocked(); 1791 } 1792 } 1793 } 1794 1795 @Override powerOffRemoteDevice(int logicalAddress, int powerStatus)1796 public void powerOffRemoteDevice(int logicalAddress, int powerStatus) { 1797 enforceAccessPermission(); 1798 runOnServiceThread(new Runnable() { 1799 @Override 1800 public void run() { 1801 Slog.w(TAG, "Device " 1802 + logicalAddress + " power status is " + powerStatus 1803 + " before standby command sent out"); 1804 sendCecCommand(HdmiCecMessageBuilder.buildStandby( 1805 getRemoteControlSourceAddress(), logicalAddress)); 1806 } 1807 }); 1808 } 1809 1810 @Override powerOnRemoteDevice(int logicalAddress, int powerStatus)1811 public void powerOnRemoteDevice(int logicalAddress, int powerStatus) { 1812 // TODO(amyjojo): implement the method 1813 } 1814 1815 @Override 1816 // TODO(b/128427908): add a result callback askRemoteDeviceToBecomeActiveSource(int physicalAddress)1817 public void askRemoteDeviceToBecomeActiveSource(int physicalAddress) { 1818 enforceAccessPermission(); 1819 runOnServiceThread(new Runnable() { 1820 @Override 1821 public void run() { 1822 HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath( 1823 getRemoteControlSourceAddress(), physicalAddress); 1824 if (pathToPortId(physicalAddress) != Constants.INVALID_PORT_ID) { 1825 if (getSwitchDevice() != null) { 1826 getSwitchDevice().handleSetStreamPath(setStreamPath); 1827 } else { 1828 Slog.e(TAG, "Can't get the correct local device to handle routing."); 1829 } 1830 } 1831 sendCecCommand(setStreamPath); 1832 } 1833 }); 1834 } 1835 1836 @Override setSystemAudioVolume(final int oldIndex, final int newIndex, final int maxIndex)1837 public void setSystemAudioVolume(final int oldIndex, final int newIndex, 1838 final int maxIndex) { 1839 enforceAccessPermission(); 1840 runOnServiceThread(new Runnable() { 1841 @Override 1842 public void run() { 1843 HdmiCecLocalDeviceTv tv = tv(); 1844 if (tv == null) { 1845 Slog.w(TAG, "Local tv device not available"); 1846 return; 1847 } 1848 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex); 1849 } 1850 }); 1851 } 1852 1853 @Override setSystemAudioMute(final boolean mute)1854 public void setSystemAudioMute(final boolean mute) { 1855 enforceAccessPermission(); 1856 runOnServiceThread(new Runnable() { 1857 @Override 1858 public void run() { 1859 HdmiCecLocalDeviceTv tv = tv(); 1860 if (tv == null) { 1861 Slog.w(TAG, "Local tv device not available"); 1862 return; 1863 } 1864 tv.changeMute(mute); 1865 } 1866 }); 1867 } 1868 1869 @Override setArcMode(final boolean enabled)1870 public void setArcMode(final boolean enabled) { 1871 enforceAccessPermission(); 1872 runOnServiceThread(new Runnable() { 1873 @Override 1874 public void run() { 1875 HdmiCecLocalDeviceTv tv = tv(); 1876 if (tv == null) { 1877 Slog.w(TAG, "Local tv device not available to change arc mode."); 1878 return; 1879 } 1880 } 1881 }); 1882 } 1883 1884 @Override setProhibitMode(final boolean enabled)1885 public void setProhibitMode(final boolean enabled) { 1886 enforceAccessPermission(); 1887 if (!isTvDevice()) { 1888 return; 1889 } 1890 HdmiControlService.this.setProhibitMode(enabled); 1891 } 1892 1893 @Override addVendorCommandListener(final IHdmiVendorCommandListener listener, final int deviceType)1894 public void addVendorCommandListener(final IHdmiVendorCommandListener listener, 1895 final int deviceType) { 1896 enforceAccessPermission(); 1897 HdmiControlService.this.addVendorCommandListener(listener, deviceType); 1898 } 1899 1900 @Override sendVendorCommand(final int deviceType, final int targetAddress, final byte[] params, final boolean hasVendorId)1901 public void sendVendorCommand(final int deviceType, final int targetAddress, 1902 final byte[] params, final boolean hasVendorId) { 1903 enforceAccessPermission(); 1904 runOnServiceThread(new Runnable() { 1905 @Override 1906 public void run() { 1907 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType); 1908 if (device == null) { 1909 Slog.w(TAG, "Local device not available"); 1910 return; 1911 } 1912 if (hasVendorId) { 1913 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId( 1914 device.getDeviceInfo().getLogicalAddress(), targetAddress, 1915 getVendorId(), params)); 1916 } else { 1917 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand( 1918 device.getDeviceInfo().getLogicalAddress(), targetAddress, params)); 1919 } 1920 } 1921 }); 1922 } 1923 1924 @Override sendStandby(final int deviceType, final int deviceId)1925 public void sendStandby(final int deviceType, final int deviceId) { 1926 enforceAccessPermission(); 1927 runOnServiceThread(new Runnable() { 1928 @Override 1929 public void run() { 1930 HdmiMhlLocalDeviceStub mhlDevice = mMhlController.getLocalDeviceById(deviceId); 1931 if (mhlDevice != null) { 1932 mhlDevice.sendStandby(); 1933 return; 1934 } 1935 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType); 1936 if (device == null) { 1937 device = audioSystem(); 1938 } 1939 if (device == null) { 1940 Slog.w(TAG, "Local device not available"); 1941 return; 1942 } 1943 device.sendStandby(deviceId); 1944 } 1945 }); 1946 } 1947 1948 @Override setHdmiRecordListener(IHdmiRecordListener listener)1949 public void setHdmiRecordListener(IHdmiRecordListener listener) { 1950 enforceAccessPermission(); 1951 HdmiControlService.this.setHdmiRecordListener(listener); 1952 } 1953 1954 @Override startOneTouchRecord(final int recorderAddress, final byte[] recordSource)1955 public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) { 1956 enforceAccessPermission(); 1957 runOnServiceThread(new Runnable() { 1958 @Override 1959 public void run() { 1960 if (!isTvDeviceEnabled()) { 1961 Slog.w(TAG, "TV device is not enabled."); 1962 return; 1963 } 1964 tv().startOneTouchRecord(recorderAddress, recordSource); 1965 } 1966 }); 1967 } 1968 1969 @Override stopOneTouchRecord(final int recorderAddress)1970 public void stopOneTouchRecord(final int recorderAddress) { 1971 enforceAccessPermission(); 1972 runOnServiceThread(new Runnable() { 1973 @Override 1974 public void run() { 1975 if (!isTvDeviceEnabled()) { 1976 Slog.w(TAG, "TV device is not enabled."); 1977 return; 1978 } 1979 tv().stopOneTouchRecord(recorderAddress); 1980 } 1981 }); 1982 } 1983 1984 @Override startTimerRecording(final int recorderAddress, final int sourceType, final byte[] recordSource)1985 public void startTimerRecording(final int recorderAddress, final int sourceType, 1986 final byte[] recordSource) { 1987 enforceAccessPermission(); 1988 runOnServiceThread(new Runnable() { 1989 @Override 1990 public void run() { 1991 if (!isTvDeviceEnabled()) { 1992 Slog.w(TAG, "TV device is not enabled."); 1993 return; 1994 } 1995 tv().startTimerRecording(recorderAddress, sourceType, recordSource); 1996 } 1997 }); 1998 } 1999 2000 @Override clearTimerRecording(final int recorderAddress, final int sourceType, final byte[] recordSource)2001 public void clearTimerRecording(final int recorderAddress, final int sourceType, 2002 final byte[] recordSource) { 2003 enforceAccessPermission(); 2004 runOnServiceThread(new Runnable() { 2005 @Override 2006 public void run() { 2007 if (!isTvDeviceEnabled()) { 2008 Slog.w(TAG, "TV device is not enabled."); 2009 return; 2010 } 2011 tv().clearTimerRecording(recorderAddress, sourceType, recordSource); 2012 } 2013 }); 2014 } 2015 2016 @Override sendMhlVendorCommand(final int portId, final int offset, final int length, final byte[] data)2017 public void sendMhlVendorCommand(final int portId, final int offset, final int length, 2018 final byte[] data) { 2019 enforceAccessPermission(); 2020 runOnServiceThread(new Runnable() { 2021 @Override 2022 public void run() { 2023 if (!isControlEnabled()) { 2024 Slog.w(TAG, "Hdmi control is disabled."); 2025 return ; 2026 } 2027 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); 2028 if (device == null) { 2029 Slog.w(TAG, "Invalid port id:" + portId); 2030 return; 2031 } 2032 mMhlController.sendVendorCommand(portId, offset, length, data); 2033 } 2034 }); 2035 } 2036 2037 @Override addHdmiMhlVendorCommandListener( IHdmiMhlVendorCommandListener listener)2038 public void addHdmiMhlVendorCommandListener( 2039 IHdmiMhlVendorCommandListener listener) { 2040 enforceAccessPermission(); 2041 HdmiControlService.this.addHdmiMhlVendorCommandListener(listener); 2042 } 2043 2044 @Override setStandbyMode(final boolean isStandbyModeOn)2045 public void setStandbyMode(final boolean isStandbyModeOn) { 2046 enforceAccessPermission(); 2047 runOnServiceThread(new Runnable() { 2048 @Override 2049 public void run() { 2050 HdmiControlService.this.setStandbyMode(isStandbyModeOn); 2051 } 2052 }); 2053 } 2054 2055 @Override reportAudioStatus(final int deviceType, final int volume, final int maxVolume, final boolean isMute)2056 public void reportAudioStatus(final int deviceType, final int volume, final int maxVolume, 2057 final boolean isMute) { 2058 enforceAccessPermission(); 2059 runOnServiceThread(new Runnable() { 2060 @Override 2061 public void run() { 2062 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType); 2063 if (device == null) { 2064 Slog.w(TAG, "Local device not available"); 2065 return; 2066 } 2067 if (audioSystem() == null) { 2068 Slog.w(TAG, "audio system is not available"); 2069 return; 2070 } 2071 if (!audioSystem().isSystemAudioActivated()) { 2072 Slog.w(TAG, "audio system is not in system audio mode"); 2073 return; 2074 } 2075 audioSystem().reportAudioStatus(Constants.ADDR_TV); 2076 } 2077 }); 2078 } 2079 2080 @Override setSystemAudioModeOnForAudioOnlySource()2081 public void setSystemAudioModeOnForAudioOnlySource() { 2082 enforceAccessPermission(); 2083 runOnServiceThread(new Runnable() { 2084 @Override 2085 public void run() { 2086 if (!isAudioSystemDevice()) { 2087 Slog.e(TAG, "Not an audio system device. Won't set system audio mode on"); 2088 return; 2089 } 2090 if (audioSystem() == null) { 2091 Slog.e(TAG, "Audio System local device is not registered"); 2092 return; 2093 } 2094 if (!audioSystem().checkSupportAndSetSystemAudioMode(true)) { 2095 Slog.e(TAG, "System Audio Mode is not supported."); 2096 return; 2097 } 2098 sendCecCommand( 2099 HdmiCecMessageBuilder.buildSetSystemAudioMode( 2100 audioSystem().mAddress, Constants.ADDR_BROADCAST, true)); 2101 } 2102 }); 2103 } 2104 2105 @Override dump(FileDescriptor fd, final PrintWriter writer, String[] args)2106 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) { 2107 if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) return; 2108 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); 2109 2110 pw.println("mProhibitMode: " + mProhibitMode); 2111 pw.println("mPowerStatus: " + mPowerStatus); 2112 2113 // System settings 2114 pw.println("System_settings:"); 2115 pw.increaseIndent(); 2116 pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled); 2117 pw.println("mMhlInputChangeEnabled: " + mMhlInputChangeEnabled); 2118 pw.println("mSystemAudioActivated: " + isSystemAudioActivated()); 2119 pw.decreaseIndent(); 2120 2121 pw.println("mMhlController: "); 2122 pw.increaseIndent(); 2123 mMhlController.dump(pw); 2124 pw.decreaseIndent(); 2125 2126 HdmiUtils.dumpIterable(pw, "mPortInfo:", mPortInfo); 2127 if (mCecController != null) { 2128 pw.println("mCecController: "); 2129 pw.increaseIndent(); 2130 mCecController.dump(pw); 2131 pw.decreaseIndent(); 2132 } 2133 } 2134 } 2135 2136 // Get the source address to send out commands to devices connected to the current device 2137 // when other services interact with HdmiControlService. getRemoteControlSourceAddress()2138 private int getRemoteControlSourceAddress() { 2139 if (isAudioSystemDevice()) { 2140 return audioSystem().getDeviceInfo().getLogicalAddress(); 2141 } else if (isPlaybackDevice()) { 2142 return playback().getDeviceInfo().getLogicalAddress(); 2143 } 2144 return ADDR_UNREGISTERED; 2145 } 2146 2147 // Get the switch device to do CEC routing control 2148 @Nullable getSwitchDevice()2149 private HdmiCecLocalDeviceSource getSwitchDevice() { 2150 if (isAudioSystemDevice()) { 2151 return audioSystem(); 2152 } 2153 if (isPlaybackDevice()) { 2154 return playback(); 2155 } 2156 return null; 2157 } 2158 2159 @ServiceThreadOnly oneTouchPlay(final IHdmiControlCallback callback)2160 private void oneTouchPlay(final IHdmiControlCallback callback) { 2161 assertRunOnServiceThread(); 2162 HdmiCecLocalDeviceSource source = playback(); 2163 if (source == null) { 2164 source = audioSystem(); 2165 } 2166 2167 if (source == null) { 2168 Slog.w(TAG, "Local source device not available"); 2169 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 2170 return; 2171 } 2172 source.oneTouchPlay(callback); 2173 } 2174 2175 @ServiceThreadOnly queryDisplayStatus(final IHdmiControlCallback callback)2176 private void queryDisplayStatus(final IHdmiControlCallback callback) { 2177 assertRunOnServiceThread(); 2178 HdmiCecLocalDevicePlayback source = playback(); 2179 if (source == null) { 2180 Slog.w(TAG, "Local playback device not available"); 2181 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 2182 return; 2183 } 2184 source.queryDisplayStatus(callback); 2185 } 2186 addHdmiControlStatusChangeListener( final IHdmiControlStatusChangeListener listener)2187 private void addHdmiControlStatusChangeListener( 2188 final IHdmiControlStatusChangeListener listener) { 2189 final HdmiControlStatusChangeListenerRecord record = 2190 new HdmiControlStatusChangeListenerRecord(listener); 2191 try { 2192 listener.asBinder().linkToDeath(record, 0); 2193 } catch (RemoteException e) { 2194 Slog.w(TAG, "Listener already died"); 2195 return; 2196 } 2197 synchronized (mLock) { 2198 mHdmiControlStatusChangeListenerRecords.add(record); 2199 } 2200 2201 // Inform the listener of the initial state of each HDMI port by generating 2202 // hotplug events. 2203 runOnServiceThread(new Runnable() { 2204 @Override 2205 public void run() { 2206 synchronized (mLock) { 2207 if (!mHdmiControlStatusChangeListenerRecords.contains(record)) return; 2208 } 2209 2210 // Return the current status of mHdmiControlEnabled; 2211 synchronized (mLock) { 2212 invokeHdmiControlStatusChangeListenerLocked(listener, mHdmiControlEnabled); 2213 } 2214 } 2215 }); 2216 } 2217 removeHdmiControlStatusChangeListener( final IHdmiControlStatusChangeListener listener)2218 private void removeHdmiControlStatusChangeListener( 2219 final IHdmiControlStatusChangeListener listener) { 2220 synchronized (mLock) { 2221 for (HdmiControlStatusChangeListenerRecord record : 2222 mHdmiControlStatusChangeListenerRecords) { 2223 if (record.mListener.asBinder() == listener.asBinder()) { 2224 listener.asBinder().unlinkToDeath(record, 0); 2225 mHdmiControlStatusChangeListenerRecords.remove(record); 2226 break; 2227 } 2228 } 2229 } 2230 } 2231 addHotplugEventListener(final IHdmiHotplugEventListener listener)2232 private void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 2233 final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); 2234 try { 2235 listener.asBinder().linkToDeath(record, 0); 2236 } catch (RemoteException e) { 2237 Slog.w(TAG, "Listener already died"); 2238 return; 2239 } 2240 synchronized (mLock) { 2241 mHotplugEventListenerRecords.add(record); 2242 } 2243 2244 // Inform the listener of the initial state of each HDMI port by generating 2245 // hotplug events. 2246 runOnServiceThread(new Runnable() { 2247 @Override 2248 public void run() { 2249 synchronized (mLock) { 2250 if (!mHotplugEventListenerRecords.contains(record)) return; 2251 } 2252 for (HdmiPortInfo port : mPortInfo) { 2253 HdmiHotplugEvent event = new HdmiHotplugEvent(port.getId(), 2254 mCecController.isConnected(port.getId())); 2255 synchronized (mLock) { 2256 invokeHotplugEventListenerLocked(listener, event); 2257 } 2258 } 2259 } 2260 }); 2261 } 2262 removeHotplugEventListener(IHdmiHotplugEventListener listener)2263 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) { 2264 synchronized (mLock) { 2265 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 2266 if (record.mListener.asBinder() == listener.asBinder()) { 2267 listener.asBinder().unlinkToDeath(record, 0); 2268 mHotplugEventListenerRecords.remove(record); 2269 break; 2270 } 2271 } 2272 } 2273 } 2274 addDeviceEventListener(IHdmiDeviceEventListener listener)2275 private void addDeviceEventListener(IHdmiDeviceEventListener listener) { 2276 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener); 2277 try { 2278 listener.asBinder().linkToDeath(record, 0); 2279 } catch (RemoteException e) { 2280 Slog.w(TAG, "Listener already died"); 2281 return; 2282 } 2283 synchronized (mLock) { 2284 mDeviceEventListenerRecords.add(record); 2285 } 2286 } 2287 invokeDeviceEventListeners(HdmiDeviceInfo device, int status)2288 void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) { 2289 synchronized (mLock) { 2290 for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) { 2291 try { 2292 record.mListener.onStatusChanged(device, status); 2293 } catch (RemoteException e) { 2294 Slog.e(TAG, "Failed to report device event:" + e); 2295 } 2296 } 2297 } 2298 } 2299 addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener)2300 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) { 2301 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord( 2302 listener); 2303 try { 2304 listener.asBinder().linkToDeath(record, 0); 2305 } catch (RemoteException e) { 2306 Slog.w(TAG, "Listener already died"); 2307 return; 2308 } 2309 synchronized (mLock) { 2310 mSystemAudioModeChangeListenerRecords.add(record); 2311 } 2312 } 2313 removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener)2314 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) { 2315 synchronized (mLock) { 2316 for (SystemAudioModeChangeListenerRecord record : 2317 mSystemAudioModeChangeListenerRecords) { 2318 if (record.mListener.asBinder() == listener) { 2319 listener.asBinder().unlinkToDeath(record, 0); 2320 mSystemAudioModeChangeListenerRecords.remove(record); 2321 break; 2322 } 2323 } 2324 } 2325 } 2326 2327 private final class InputChangeListenerRecord implements IBinder.DeathRecipient { 2328 private final IHdmiInputChangeListener mListener; 2329 InputChangeListenerRecord(IHdmiInputChangeListener listener)2330 public InputChangeListenerRecord(IHdmiInputChangeListener listener) { 2331 mListener = listener; 2332 } 2333 2334 @Override binderDied()2335 public void binderDied() { 2336 synchronized (mLock) { 2337 if (mInputChangeListenerRecord == this) { 2338 mInputChangeListenerRecord = null; 2339 } 2340 } 2341 } 2342 } 2343 setInputChangeListener(IHdmiInputChangeListener listener)2344 private void setInputChangeListener(IHdmiInputChangeListener listener) { 2345 synchronized (mLock) { 2346 mInputChangeListenerRecord = new InputChangeListenerRecord(listener); 2347 try { 2348 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0); 2349 } catch (RemoteException e) { 2350 Slog.w(TAG, "Listener already died"); 2351 return; 2352 } 2353 } 2354 } 2355 invokeInputChangeListener(HdmiDeviceInfo info)2356 void invokeInputChangeListener(HdmiDeviceInfo info) { 2357 synchronized (mLock) { 2358 if (mInputChangeListenerRecord != null) { 2359 try { 2360 mInputChangeListenerRecord.mListener.onChanged(info); 2361 } catch (RemoteException e) { 2362 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e); 2363 } 2364 } 2365 } 2366 } 2367 setHdmiRecordListener(IHdmiRecordListener listener)2368 private void setHdmiRecordListener(IHdmiRecordListener listener) { 2369 synchronized (mLock) { 2370 mRecordListenerRecord = new HdmiRecordListenerRecord(listener); 2371 try { 2372 listener.asBinder().linkToDeath(mRecordListenerRecord, 0); 2373 } catch (RemoteException e) { 2374 Slog.w(TAG, "Listener already died.", e); 2375 } 2376 } 2377 } 2378 invokeRecordRequestListener(int recorderAddress)2379 byte[] invokeRecordRequestListener(int recorderAddress) { 2380 synchronized (mLock) { 2381 if (mRecordListenerRecord != null) { 2382 try { 2383 return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress); 2384 } catch (RemoteException e) { 2385 Slog.w(TAG, "Failed to start record.", e); 2386 } 2387 } 2388 return EmptyArray.BYTE; 2389 } 2390 } 2391 invokeOneTouchRecordResult(int recorderAddress, int result)2392 void invokeOneTouchRecordResult(int recorderAddress, int result) { 2393 synchronized (mLock) { 2394 if (mRecordListenerRecord != null) { 2395 try { 2396 mRecordListenerRecord.mListener.onOneTouchRecordResult(recorderAddress, result); 2397 } catch (RemoteException e) { 2398 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e); 2399 } 2400 } 2401 } 2402 } 2403 invokeTimerRecordingResult(int recorderAddress, int result)2404 void invokeTimerRecordingResult(int recorderAddress, int result) { 2405 synchronized (mLock) { 2406 if (mRecordListenerRecord != null) { 2407 try { 2408 mRecordListenerRecord.mListener.onTimerRecordingResult(recorderAddress, result); 2409 } catch (RemoteException e) { 2410 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e); 2411 } 2412 } 2413 } 2414 } 2415 invokeClearTimerRecordingResult(int recorderAddress, int result)2416 void invokeClearTimerRecordingResult(int recorderAddress, int result) { 2417 synchronized (mLock) { 2418 if (mRecordListenerRecord != null) { 2419 try { 2420 mRecordListenerRecord.mListener.onClearTimerRecordingResult(recorderAddress, 2421 result); 2422 } catch (RemoteException e) { 2423 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e); 2424 } 2425 } 2426 } 2427 } 2428 invokeCallback(IHdmiControlCallback callback, int result)2429 private void invokeCallback(IHdmiControlCallback callback, int result) { 2430 try { 2431 callback.onComplete(result); 2432 } catch (RemoteException e) { 2433 Slog.e(TAG, "Invoking callback failed:" + e); 2434 } 2435 } 2436 invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener, boolean enabled)2437 private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener, 2438 boolean enabled) { 2439 try { 2440 listener.onStatusChanged(enabled); 2441 } catch (RemoteException e) { 2442 Slog.e(TAG, "Invoking callback failed:" + e); 2443 } 2444 } 2445 announceHotplugEvent(int portId, boolean connected)2446 private void announceHotplugEvent(int portId, boolean connected) { 2447 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected); 2448 synchronized (mLock) { 2449 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 2450 invokeHotplugEventListenerLocked(record.mListener, event); 2451 } 2452 } 2453 } 2454 invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener, HdmiHotplugEvent event)2455 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener, 2456 HdmiHotplugEvent event) { 2457 try { 2458 listener.onReceived(event); 2459 } catch (RemoteException e) { 2460 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e); 2461 } 2462 } 2463 announceHdmiControlStatusChange(boolean isEnabled)2464 private void announceHdmiControlStatusChange(boolean isEnabled) { 2465 assertRunOnServiceThread(); 2466 synchronized (mLock) { 2467 for (HdmiControlStatusChangeListenerRecord record : 2468 mHdmiControlStatusChangeListenerRecords) { 2469 invokeHdmiControlStatusChangeListenerLocked(record.mListener, isEnabled); 2470 } 2471 } 2472 } 2473 invokeHdmiControlStatusChangeListenerLocked( IHdmiControlStatusChangeListener listener, boolean isEnabled)2474 private void invokeHdmiControlStatusChangeListenerLocked( 2475 IHdmiControlStatusChangeListener listener, boolean isEnabled) { 2476 if (isEnabled) { 2477 queryDisplayStatus(new IHdmiControlCallback.Stub() { 2478 public void onComplete(int status) { 2479 boolean isAvailable = true; 2480 if (status == HdmiControlManager.POWER_STATUS_UNKNOWN 2481 || status == HdmiControlManager.RESULT_EXCEPTION 2482 || status == HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE) { 2483 isAvailable = false; 2484 } 2485 2486 try { 2487 listener.onStatusChange(isEnabled, isAvailable); 2488 } catch (RemoteException e) { 2489 Slog.e(TAG, "Failed to report HdmiControlStatusChange: " + isEnabled 2490 + " isAvailable: " + isAvailable, e); 2491 } 2492 } 2493 }); 2494 return; 2495 } 2496 2497 try { 2498 listener.onStatusChange(isEnabled, false); 2499 } catch (RemoteException e) { 2500 Slog.e(TAG, "Failed to report HdmiControlStatusChange: " + isEnabled 2501 + " isAvailable: " + false, e); 2502 } 2503 } 2504 tv()2505 public HdmiCecLocalDeviceTv tv() { 2506 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV); 2507 } 2508 isTvDevice()2509 boolean isTvDevice() { 2510 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV); 2511 } 2512 isAudioSystemDevice()2513 boolean isAudioSystemDevice() { 2514 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); 2515 } 2516 isPlaybackDevice()2517 boolean isPlaybackDevice() { 2518 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_PLAYBACK); 2519 } 2520 isSwitchDevice()2521 boolean isSwitchDevice() { 2522 return HdmiProperties.is_switch().orElse(false); 2523 } 2524 isTvDeviceEnabled()2525 boolean isTvDeviceEnabled() { 2526 return isTvDevice() && tv() != null; 2527 } 2528 playback()2529 protected HdmiCecLocalDevicePlayback playback() { 2530 return (HdmiCecLocalDevicePlayback) 2531 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK); 2532 } 2533 audioSystem()2534 public HdmiCecLocalDeviceAudioSystem audioSystem() { 2535 return (HdmiCecLocalDeviceAudioSystem) mCecController.getLocalDevice( 2536 HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); 2537 } 2538 getAudioManager()2539 AudioManager getAudioManager() { 2540 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); 2541 } 2542 isControlEnabled()2543 boolean isControlEnabled() { 2544 synchronized (mLock) { 2545 return mHdmiControlEnabled; 2546 } 2547 } 2548 2549 @ServiceThreadOnly getPowerStatus()2550 int getPowerStatus() { 2551 assertRunOnServiceThread(); 2552 return mPowerStatus; 2553 } 2554 2555 @ServiceThreadOnly isPowerOnOrTransient()2556 boolean isPowerOnOrTransient() { 2557 assertRunOnServiceThread(); 2558 return mPowerStatus == HdmiControlManager.POWER_STATUS_ON 2559 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; 2560 } 2561 2562 @ServiceThreadOnly isPowerStandbyOrTransient()2563 boolean isPowerStandbyOrTransient() { 2564 assertRunOnServiceThread(); 2565 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY 2566 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY; 2567 } 2568 2569 @ServiceThreadOnly isPowerStandby()2570 boolean isPowerStandby() { 2571 assertRunOnServiceThread(); 2572 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY; 2573 } 2574 2575 @ServiceThreadOnly wakeUp()2576 void wakeUp() { 2577 assertRunOnServiceThread(); 2578 mWakeUpMessageReceived = true; 2579 mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_HDMI, 2580 "android.server.hdmi:WAKE"); 2581 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets 2582 // the intent, the sequence will continue at onWakeUp(). 2583 } 2584 2585 @ServiceThreadOnly standby()2586 void standby() { 2587 assertRunOnServiceThread(); 2588 if (!canGoToStandby()) { 2589 return; 2590 } 2591 mStandbyMessageReceived = true; 2592 mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0); 2593 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets 2594 // the intent, the sequence will continue at onStandby(). 2595 } 2596 isWakeUpMessageReceived()2597 boolean isWakeUpMessageReceived() { 2598 return mWakeUpMessageReceived; 2599 } 2600 2601 @VisibleForTesting isStandbyMessageReceived()2602 boolean isStandbyMessageReceived() { 2603 return mStandbyMessageReceived; 2604 } 2605 2606 @ServiceThreadOnly onWakeUp()2607 private void onWakeUp() { 2608 assertRunOnServiceThread(); 2609 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; 2610 if (mCecController != null) { 2611 if (mHdmiControlEnabled) { 2612 int startReason = INITIATED_BY_SCREEN_ON; 2613 if (mWakeUpMessageReceived) { 2614 startReason = INITIATED_BY_WAKE_UP_MESSAGE; 2615 } 2616 initializeCec(startReason); 2617 } 2618 } else { 2619 Slog.i(TAG, "Device does not support HDMI-CEC."); 2620 } 2621 // TODO: Initialize MHL local devices. 2622 } 2623 2624 @ServiceThreadOnly 2625 @VisibleForTesting onStandby(final int standbyAction)2626 protected void onStandby(final int standbyAction) { 2627 assertRunOnServiceThread(); 2628 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY; 2629 invokeVendorCommandListenersOnControlStateChanged(false, 2630 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY); 2631 2632 final List<HdmiCecLocalDevice> devices = getAllLocalDevices(); 2633 2634 if (!isStandbyMessageReceived() && !canGoToStandby()) { 2635 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; 2636 for (HdmiCecLocalDevice device : devices) { 2637 device.onStandby(mStandbyMessageReceived, standbyAction); 2638 } 2639 return; 2640 } 2641 2642 disableDevices(new PendingActionClearedCallback() { 2643 @Override 2644 public void onCleared(HdmiCecLocalDevice device) { 2645 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType); 2646 devices.remove(device); 2647 if (devices.isEmpty()) { 2648 onStandbyCompleted(standbyAction); 2649 // We will not clear local devices here, since some OEM/SOC will keep passing 2650 // the received packets until the application processor enters to the sleep 2651 // actually. 2652 } 2653 } 2654 }); 2655 } 2656 canGoToStandby()2657 private boolean canGoToStandby() { 2658 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 2659 if (!device.canGoToStandby()) return false; 2660 } 2661 return true; 2662 } 2663 2664 @ServiceThreadOnly onLanguageChanged(String language)2665 private void onLanguageChanged(String language) { 2666 assertRunOnServiceThread(); 2667 mLanguage = language; 2668 2669 if (isTvDeviceEnabled()) { 2670 tv().broadcastMenuLanguage(language); 2671 mCecController.setLanguage(language); 2672 } 2673 } 2674 2675 @ServiceThreadOnly getLanguage()2676 String getLanguage() { 2677 assertRunOnServiceThread(); 2678 return mLanguage; 2679 } 2680 disableDevices(PendingActionClearedCallback callback)2681 private void disableDevices(PendingActionClearedCallback callback) { 2682 if (mCecController != null) { 2683 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 2684 device.disableDevice(mStandbyMessageReceived, callback); 2685 } 2686 } 2687 2688 mMhlController.clearAllLocalDevices(); 2689 } 2690 2691 @ServiceThreadOnly clearLocalDevices()2692 private void clearLocalDevices() { 2693 assertRunOnServiceThread(); 2694 if (mCecController == null) { 2695 return; 2696 } 2697 mCecController.clearLogicalAddress(); 2698 mCecController.clearLocalDevices(); 2699 } 2700 2701 @ServiceThreadOnly onStandbyCompleted(int standbyAction)2702 private void onStandbyCompleted(int standbyAction) { 2703 assertRunOnServiceThread(); 2704 Slog.v(TAG, "onStandbyCompleted"); 2705 2706 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) { 2707 return; 2708 } 2709 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; 2710 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 2711 device.onStandby(mStandbyMessageReceived, standbyAction); 2712 } 2713 mStandbyMessageReceived = false; 2714 if (!isAudioSystemDevice()) { 2715 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false); 2716 mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED); 2717 } 2718 } 2719 addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType)2720 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) { 2721 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType); 2722 try { 2723 listener.asBinder().linkToDeath(record, 0); 2724 } catch (RemoteException e) { 2725 Slog.w(TAG, "Listener already died"); 2726 return; 2727 } 2728 synchronized (mLock) { 2729 mVendorCommandListenerRecords.add(record); 2730 } 2731 } 2732 invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress, byte[] params, boolean hasVendorId)2733 boolean invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress, 2734 byte[] params, boolean hasVendorId) { 2735 synchronized (mLock) { 2736 if (mVendorCommandListenerRecords.isEmpty()) { 2737 return false; 2738 } 2739 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) { 2740 if (record.mDeviceType != deviceType) { 2741 continue; 2742 } 2743 try { 2744 record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId); 2745 } catch (RemoteException e) { 2746 Slog.e(TAG, "Failed to notify vendor command reception", e); 2747 } 2748 } 2749 return true; 2750 } 2751 } 2752 invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason)2753 boolean invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason) { 2754 synchronized (mLock) { 2755 if (mVendorCommandListenerRecords.isEmpty()) { 2756 return false; 2757 } 2758 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) { 2759 try { 2760 record.mListener.onControlStateChanged(enabled, reason); 2761 } catch (RemoteException e) { 2762 Slog.e(TAG, "Failed to notify control-state-changed to vendor handler", e); 2763 } 2764 } 2765 return true; 2766 } 2767 } 2768 addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener)2769 private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) { 2770 HdmiMhlVendorCommandListenerRecord record = 2771 new HdmiMhlVendorCommandListenerRecord(listener); 2772 try { 2773 listener.asBinder().linkToDeath(record, 0); 2774 } catch (RemoteException e) { 2775 Slog.w(TAG, "Listener already died."); 2776 return; 2777 } 2778 2779 synchronized (mLock) { 2780 mMhlVendorCommandListenerRecords.add(record); 2781 } 2782 } 2783 invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data)2784 void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) { 2785 synchronized (mLock) { 2786 for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) { 2787 try { 2788 record.mListener.onReceived(portId, offest, length, data); 2789 } catch (RemoteException e) { 2790 Slog.e(TAG, "Failed to notify MHL vendor command", e); 2791 } 2792 } 2793 } 2794 } 2795 setStandbyMode(boolean isStandbyModeOn)2796 void setStandbyMode(boolean isStandbyModeOn) { 2797 assertRunOnServiceThread(); 2798 if (isPowerOnOrTransient() && isStandbyModeOn) { 2799 mPowerManager.goToSleep(SystemClock.uptimeMillis(), 2800 PowerManager.GO_TO_SLEEP_REASON_HDMI, 0); 2801 if (playback() != null) { 2802 playback().sendStandby(0 /* unused */); 2803 } 2804 } else if (isPowerStandbyOrTransient() && !isStandbyModeOn) { 2805 mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_HDMI, 2806 "android.server.hdmi:WAKE"); 2807 if (playback() != null) { 2808 oneTouchPlay(new IHdmiControlCallback.Stub() { 2809 @Override 2810 public void onComplete(int result) { 2811 if (result != HdmiControlManager.RESULT_SUCCESS) { 2812 Slog.w(TAG, "Failed to complete 'one touch play'. result=" + result); 2813 } 2814 } 2815 }); 2816 } 2817 } 2818 } 2819 isProhibitMode()2820 boolean isProhibitMode() { 2821 synchronized (mLock) { 2822 return mProhibitMode; 2823 } 2824 } 2825 setProhibitMode(boolean enabled)2826 void setProhibitMode(boolean enabled) { 2827 synchronized (mLock) { 2828 mProhibitMode = enabled; 2829 } 2830 } 2831 isSystemAudioActivated()2832 boolean isSystemAudioActivated() { 2833 synchronized (mLock) { 2834 return mSystemAudioActivated; 2835 } 2836 } 2837 setSystemAudioActivated(boolean on)2838 void setSystemAudioActivated(boolean on) { 2839 synchronized (mLock) { 2840 mSystemAudioActivated = on; 2841 } 2842 } 2843 2844 @ServiceThreadOnly setCecOption(int key, boolean value)2845 void setCecOption(int key, boolean value) { 2846 assertRunOnServiceThread(); 2847 mCecController.setOption(key, value); 2848 } 2849 2850 @ServiceThreadOnly setControlEnabled(boolean enabled)2851 void setControlEnabled(boolean enabled) { 2852 assertRunOnServiceThread(); 2853 2854 synchronized (mLock) { 2855 mHdmiControlEnabled = enabled; 2856 } 2857 2858 if (enabled) { 2859 enableHdmiControlService(); 2860 return; 2861 } 2862 // Call the vendor handler before the service is disabled. 2863 invokeVendorCommandListenersOnControlStateChanged(false, 2864 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING); 2865 // Post the remained tasks in the service thread again to give the vendor-issued-tasks 2866 // a chance to run. 2867 runOnServiceThread(new Runnable() { 2868 @Override 2869 public void run() { 2870 disableHdmiControlService(); 2871 } 2872 }); 2873 announceHdmiControlStatusChange(enabled); 2874 2875 return; 2876 } 2877 2878 @ServiceThreadOnly enableHdmiControlService()2879 private void enableHdmiControlService() { 2880 mCecController.setOption(OptionKey.ENABLE_CEC, true); 2881 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true); 2882 mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED); 2883 2884 initializeCec(INITIATED_BY_ENABLE_CEC); 2885 } 2886 2887 @ServiceThreadOnly disableHdmiControlService()2888 private void disableHdmiControlService() { 2889 disableDevices(new PendingActionClearedCallback() { 2890 @Override 2891 public void onCleared(HdmiCecLocalDevice device) { 2892 assertRunOnServiceThread(); 2893 mCecController.flush(new Runnable() { 2894 @Override 2895 public void run() { 2896 mCecController.setOption(OptionKey.ENABLE_CEC, false); 2897 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false); 2898 mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED); 2899 clearLocalDevices(); 2900 } 2901 }); 2902 } 2903 }); 2904 } 2905 2906 @ServiceThreadOnly setActivePortId(int portId)2907 void setActivePortId(int portId) { 2908 assertRunOnServiceThread(); 2909 mActivePortId = portId; 2910 2911 // Resets last input for MHL, which stays valid only after the MHL device was selected, 2912 // and no further switching is done. 2913 setLastInputForMhl(Constants.INVALID_PORT_ID); 2914 } 2915 getActiveSource()2916 ActiveSource getActiveSource() { 2917 synchronized (mLock) { 2918 return mActiveSource; 2919 } 2920 } 2921 setActiveSource(int logicalAddress, int physicalAddress)2922 void setActiveSource(int logicalAddress, int physicalAddress) { 2923 synchronized (mLock) { 2924 mActiveSource.logicalAddress = logicalAddress; 2925 mActiveSource.physicalAddress = physicalAddress; 2926 } 2927 } 2928 2929 // This method should only be called when the device can be the active source 2930 // and all the device types call into this method. 2931 // For example, when receiving broadcast messages, all the device types will call this 2932 // method but only one of them will be the Active Source. setAndBroadcastActiveSource( int physicalAddress, int deviceType, int source)2933 protected void setAndBroadcastActiveSource( 2934 int physicalAddress, int deviceType, int source) { 2935 // If the device has both playback and audio system logical addresses, 2936 // playback will claim active source. Otherwise audio system will. 2937 if (deviceType == HdmiDeviceInfo.DEVICE_PLAYBACK) { 2938 HdmiCecLocalDevicePlayback playback = playback(); 2939 playback.setIsActiveSource(true); 2940 playback.wakeUpIfActiveSource(); 2941 playback.maySendActiveSource(source); 2942 setActiveSource(playback.mAddress, physicalAddress); 2943 } 2944 2945 if (deviceType == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) { 2946 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem(); 2947 if (playback() != null) { 2948 audioSystem.setIsActiveSource(false); 2949 } else { 2950 audioSystem.setIsActiveSource(true); 2951 audioSystem.wakeUpIfActiveSource(); 2952 audioSystem.maySendActiveSource(source); 2953 setActiveSource(audioSystem.mAddress, physicalAddress); 2954 } 2955 } 2956 } 2957 2958 // This method should only be called when the device can be the active source 2959 // and only one of the device types calls into this method. 2960 // For example, when receiving One Touch Play, only playback device handles it 2961 // and this method updates Active Source in all the device types sharing the same 2962 // Physical Address. setAndBroadcastActiveSourceFromOneDeviceType( int sourceAddress, int physicalAddress)2963 protected void setAndBroadcastActiveSourceFromOneDeviceType( 2964 int sourceAddress, int physicalAddress) { 2965 // If the device has both playback and audio system logical addresses, 2966 // playback will claim active source. Otherwise audio system will. 2967 HdmiCecLocalDevicePlayback playback = playback(); 2968 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem(); 2969 if (playback != null) { 2970 playback.setIsActiveSource(true); 2971 playback.wakeUpIfActiveSource(); 2972 playback.maySendActiveSource(sourceAddress); 2973 if (audioSystem != null) { 2974 audioSystem.setIsActiveSource(false); 2975 } 2976 setActiveSource(playback.mAddress, physicalAddress); 2977 } else { 2978 if (audioSystem != null) { 2979 audioSystem.setIsActiveSource(true); 2980 audioSystem.wakeUpIfActiveSource(); 2981 audioSystem.maySendActiveSource(sourceAddress); 2982 setActiveSource(audioSystem.mAddress, physicalAddress); 2983 } 2984 } 2985 } 2986 2987 @ServiceThreadOnly setLastInputForMhl(int portId)2988 void setLastInputForMhl(int portId) { 2989 assertRunOnServiceThread(); 2990 mLastInputMhl = portId; 2991 } 2992 2993 @ServiceThreadOnly getLastInputForMhl()2994 int getLastInputForMhl() { 2995 assertRunOnServiceThread(); 2996 return mLastInputMhl; 2997 } 2998 2999 /** 3000 * Performs input change, routing control for MHL device. 3001 * 3002 * @param portId MHL port, or the last port to go back to if {@code contentOn} is false 3003 * @param contentOn {@code true} if RAP data is content on; otherwise false 3004 */ 3005 @ServiceThreadOnly changeInputForMhl(int portId, boolean contentOn)3006 void changeInputForMhl(int portId, boolean contentOn) { 3007 assertRunOnServiceThread(); 3008 if (tv() == null) return; 3009 final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID; 3010 if (portId != Constants.INVALID_PORT_ID) { 3011 tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() { 3012 @Override 3013 public void onComplete(int result) throws RemoteException { 3014 // Keep the last input to switch back later when RAP[ContentOff] is received. 3015 // This effectively sets the port to invalid one if the switching is for 3016 // RAP[ContentOff]. 3017 setLastInputForMhl(lastInput); 3018 } 3019 }); 3020 } 3021 // MHL device is always directly connected to the port. Update the active port ID to avoid 3022 // unnecessary post-routing control task. 3023 tv().setActivePortId(portId); 3024 3025 // The port is either the MHL-enabled port where the mobile device is connected, or 3026 // the last port to go back to when turnoff command is received. Note that the last port 3027 // may not be the MHL-enabled one. In this case the device info to be passed to 3028 // input change listener should be the one describing the corresponding HDMI port. 3029 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); 3030 HdmiDeviceInfo info = (device != null) ? device.getInfo() 3031 : mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE); 3032 invokeInputChangeListener(info); 3033 } 3034 setMhlInputChangeEnabled(boolean enabled)3035 void setMhlInputChangeEnabled(boolean enabled) { 3036 mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled)); 3037 3038 synchronized (mLock) { 3039 mMhlInputChangeEnabled = enabled; 3040 } 3041 } 3042 isMhlInputChangeEnabled()3043 boolean isMhlInputChangeEnabled() { 3044 synchronized (mLock) { 3045 return mMhlInputChangeEnabled; 3046 } 3047 } 3048 3049 @ServiceThreadOnly displayOsd(int messageId)3050 void displayOsd(int messageId) { 3051 assertRunOnServiceThread(); 3052 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE); 3053 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId); 3054 getContext().sendBroadcastAsUser(intent, UserHandle.ALL, 3055 HdmiControlService.PERMISSION); 3056 } 3057 3058 @ServiceThreadOnly displayOsd(int messageId, int extra)3059 void displayOsd(int messageId, int extra) { 3060 assertRunOnServiceThread(); 3061 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE); 3062 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId); 3063 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra); 3064 getContext().sendBroadcastAsUser(intent, UserHandle.ALL, 3065 HdmiControlService.PERMISSION); 3066 } 3067 } 3068