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