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