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