HdmiControlService.java revision e9f6ed3b11fb8ebae5e73db1e4736b86cae272d9
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_MHL_ENABLE; 27import static com.android.server.hdmi.Constants.OPTION_MHL_INPUT_SWITCHING; 28import static com.android.server.hdmi.Constants.OPTION_MHL_POWER_CHARGE; 29 30import android.annotation.Nullable; 31import android.content.BroadcastReceiver; 32import android.content.ContentResolver; 33import android.content.Context; 34import android.content.Intent; 35import android.content.IntentFilter; 36import android.database.ContentObserver; 37import android.hardware.hdmi.HdmiControlManager; 38import android.hardware.hdmi.HdmiDeviceInfo; 39import android.hardware.hdmi.HdmiHotplugEvent; 40import android.hardware.hdmi.HdmiPortInfo; 41import android.hardware.hdmi.IHdmiControlCallback; 42import android.hardware.hdmi.IHdmiControlService; 43import android.hardware.hdmi.IHdmiDeviceEventListener; 44import android.hardware.hdmi.IHdmiHotplugEventListener; 45import android.hardware.hdmi.IHdmiInputChangeListener; 46import android.hardware.hdmi.IHdmiRecordListener; 47import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener; 48import android.hardware.hdmi.IHdmiVendorCommandListener; 49import android.media.AudioManager; 50import android.net.Uri; 51import android.os.Build; 52import android.os.Handler; 53import android.os.HandlerThread; 54import android.os.IBinder; 55import android.os.Looper; 56import android.os.PowerManager; 57import android.os.RemoteException; 58import android.os.SystemClock; 59import android.os.SystemProperties; 60import android.os.UserHandle; 61import android.provider.Settings.Global; 62import android.text.TextUtils; 63import android.util.ArraySet; 64import android.util.Slog; 65import android.util.SparseArray; 66import android.util.SparseIntArray; 67 68import com.android.internal.annotations.GuardedBy; 69import com.android.server.SystemService; 70import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; 71import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; 72import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource; 73import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback; 74 75import libcore.util.EmptyArray; 76 77import java.util.ArrayList; 78import java.util.Arrays; 79import java.util.Collections; 80import java.util.List; 81import java.util.Locale; 82 83/** 84 * Provides a service for sending and processing HDMI control messages, 85 * HDMI-CEC and MHL control command, and providing the information on both standard. 86 */ 87public final class HdmiControlService extends SystemService { 88 private static final String TAG = "HdmiControlService"; 89 90 static final String PERMISSION = "android.permission.HDMI_CEC"; 91 92 // The reason code to initiate intializeCec(). 93 static final int INITIATED_BY_ENABLE_CEC = 0; 94 static final int INITIATED_BY_BOOT_UP = 1; 95 static final int INITIATED_BY_SCREEN_ON = 2; 96 static final int INITIATED_BY_WAKE_UP_MESSAGE = 3; 97 98 /** 99 * Interface to report send result. 100 */ 101 interface SendMessageCallback { 102 /** 103 * Called when {@link HdmiControlService#sendCecCommand} is completed. 104 * 105 * @param error result of send request. 106 * <ul> 107 * <li>{@link Constants#SEND_RESULT_SUCCESS} 108 * <li>{@link Constants#SEND_RESULT_NAK} 109 * <li>{@link Constants#SEND_RESULT_FAILURE} 110 * </ul> 111 */ 112 void onSendCompleted(int error); 113 } 114 115 /** 116 * Interface to get a list of available logical devices. 117 */ 118 interface DevicePollingCallback { 119 /** 120 * Called when device polling is finished. 121 * 122 * @param ackedAddress a list of logical addresses of available devices 123 */ 124 void onPollingFinished(List<Integer> ackedAddress); 125 } 126 127 private class HdmiControlBroadcastReceiver extends BroadcastReceiver { 128 @Override 129 public void onReceive(Context context, Intent intent) { 130 switch (intent.getAction()) { 131 case Intent.ACTION_SCREEN_OFF: 132 if (isPowerOnOrTransient()) { 133 onStandby(); 134 } 135 break; 136 case Intent.ACTION_SCREEN_ON: 137 if (isPowerStandbyOrTransient()) { 138 onWakeUp(); 139 } 140 break; 141 case Intent.ACTION_CONFIGURATION_CHANGED: 142 String language = Locale.getDefault().getISO3Language(); 143 if (!mLanguage.equals(language)) { 144 onLanguageChanged(language); 145 } 146 break; 147 } 148 } 149 } 150 151 // A thread to handle synchronous IO of CEC and MHL control service. 152 // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms) 153 // and sparse call it shares a thread to handle IO operations. 154 private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread"); 155 156 // Used to synchronize the access to the service. 157 private final Object mLock = new Object(); 158 159 // Type of logical devices hosted in the system. Stored in the unmodifiable list. 160 private final List<Integer> mLocalDevices; 161 162 // List of listeners registered by callers that want to get notified of 163 // hotplug events. 164 @GuardedBy("mLock") 165 private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>(); 166 167 // List of records for hotplug event listener to handle the the caller killed in action. 168 @GuardedBy("mLock") 169 private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords = 170 new ArrayList<>(); 171 172 // List of listeners registered by callers that want to get notified of 173 // device status events. 174 @GuardedBy("mLock") 175 private final ArrayList<IHdmiDeviceEventListener> mDeviceEventListeners = new ArrayList<>(); 176 177 // List of records for device event listener to handle the the caller killed in action. 178 @GuardedBy("mLock") 179 private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords = 180 new ArrayList<>(); 181 182 // List of records for vendor command listener to handle the the caller killed in action. 183 @GuardedBy("mLock") 184 private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords = 185 new ArrayList<>(); 186 187 @GuardedBy("mLock") 188 private IHdmiInputChangeListener mInputChangeListener; 189 190 @GuardedBy("mLock") 191 private InputChangeListenerRecord mInputChangeListenerRecord; 192 193 @GuardedBy("mLock") 194 private IHdmiRecordListener mRecordListener; 195 196 @GuardedBy("mLock") 197 private HdmiRecordListenerRecord mRecordListenerRecord; 198 199 // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol 200 // handling will be disabled and no request will be handled. 201 @GuardedBy("mLock") 202 private boolean mHdmiControlEnabled; 203 204 // Set to true while the service is in normal mode. While set to false, no input change is 205 // allowed. Used for situations where input change can confuse users such as channel auto-scan, 206 // system upgrade, etc., a.k.a. "prohibit mode". 207 @GuardedBy("mLock") 208 private boolean mProhibitMode; 209 210 // Set to true while the input change by MHL is allowed. 211 @GuardedBy("mLock") 212 private boolean mMhlInputChangeEnabled; 213 214 @GuardedBy("mLock") 215 private List<HdmiDeviceInfo> mMhlDevices; 216 217 // List of listeners registered by callers that want to get notified of 218 // system audio mode changes. 219 private final ArrayList<IHdmiSystemAudioModeChangeListener> 220 mSystemAudioModeChangeListeners = new ArrayList<>(); 221 // List of records for system audio mode change to handle the the caller killed in action. 222 private final ArrayList<SystemAudioModeChangeListenerRecord> 223 mSystemAudioModeChangeListenerRecords = new ArrayList<>(); 224 225 // Handler used to run a task in service thread. 226 private final Handler mHandler = new Handler(); 227 228 private final SettingsObserver mSettingsObserver; 229 230 @Nullable 231 private HdmiCecController mCecController; 232 233 @Nullable 234 private HdmiMhlController mMhlController; 235 236 // HDMI port information. Stored in the unmodifiable list to keep the static information 237 // from being modified. 238 private List<HdmiPortInfo> mPortInfo; 239 240 // Map from path(physical address) to port ID. 241 private UnmodifiableSparseIntArray mPortIdMap; 242 243 // Map from port ID to HdmiPortInfo. 244 private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap; 245 246 // Map from port ID to HdmiDeviceInfo. 247 private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap; 248 249 private HdmiCecMessageValidator mMessageValidator; 250 251 private final HdmiControlBroadcastReceiver 252 mHdmiControlBroadcastReceiver = new HdmiControlBroadcastReceiver(); 253 254 @ServiceThreadOnly 255 private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; 256 257 @ServiceThreadOnly 258 private String mLanguage = Locale.getDefault().getISO3Language(); 259 260 @ServiceThreadOnly 261 private boolean mStandbyMessageReceived = false; 262 263 @ServiceThreadOnly 264 private boolean mWakeUpMessageReceived = false; 265 266 @ServiceThreadOnly 267 private int mActivePortId = Constants.INVALID_PORT_ID; 268 269 // Last input port before switching to the MHL port by way of incoming request RAP[ContentOn]. 270 // Should switch back to this port when the device sends RAP[ContentOff]. 271 // Gets invalidated if we go to other port/input. 272 @ServiceThreadOnly 273 private int mLastInputMhl = Constants.INVALID_PORT_ID; 274 275 public HdmiControlService(Context context) { 276 super(context); 277 mLocalDevices = getIntList(SystemProperties.get(Constants.PROPERTY_DEVICE_TYPE)); 278 mSettingsObserver = new SettingsObserver(mHandler); 279 } 280 281 private static List<Integer> getIntList(String string) { 282 ArrayList<Integer> list = new ArrayList<>(); 283 TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(','); 284 splitter.setString(string); 285 for (String item : splitter) { 286 try { 287 list.add(Integer.parseInt(item)); 288 } catch (NumberFormatException e) { 289 Slog.w(TAG, "Can't parseInt: " + item); 290 } 291 } 292 return Collections.unmodifiableList(list); 293 } 294 295 @Override 296 public void onStart() { 297 mIoThread.start(); 298 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; 299 mProhibitMode = false; 300 mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true); 301 mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true); 302 303 mCecController = HdmiCecController.create(this); 304 if (mCecController != null) { 305 // TODO: Remove this as soon as OEM's HAL implementation is corrected. 306 mCecController.setOption(OPTION_CEC_ENABLE, ENABLED); 307 308 // TODO: load value for mHdmiControlEnabled from preference. 309 if (mHdmiControlEnabled) { 310 initializeCec(INITIATED_BY_BOOT_UP); 311 } 312 } else { 313 Slog.i(TAG, "Device does not support HDMI-CEC."); 314 } 315 316 mMhlController = HdmiMhlController.create(this); 317 if (mMhlController == null) { 318 Slog.i(TAG, "Device does not support MHL-control."); 319 } 320 initPortInfo(); 321 mMhlDevices = Collections.emptyList(); 322 mMessageValidator = new HdmiCecMessageValidator(this); 323 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); 324 325 // Register broadcast receiver for power state change. 326 if (mCecController != null || mMhlController != null) { 327 IntentFilter filter = new IntentFilter(); 328 filter.addAction(Intent.ACTION_SCREEN_OFF); 329 filter.addAction(Intent.ACTION_SCREEN_ON); 330 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); 331 getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter); 332 } 333 } 334 335 /** 336 * Called when the initialization of local devices is complete. 337 */ 338 private void onInitializeCecComplete() { 339 if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) { 340 mPowerStatus = HdmiControlManager.POWER_STATUS_ON; 341 } 342 mWakeUpMessageReceived = false; 343 344 if (isTvDevice()) { 345 mCecController.setOption(OPTION_CEC_AUTO_WAKEUP, toInt(tv().getAutoWakeup())); 346 registerContentObserver(); 347 } 348 } 349 350 private void registerContentObserver() { 351 ContentResolver resolver = getContext().getContentResolver(); 352 String[] settings = new String[] { 353 Global.HDMI_CONTROL_ENABLED, 354 Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, 355 Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, 356 Global.MHL_INPUT_SWITCHING_ENABLED, 357 Global.MHL_POWER_CHARGE_ENABLED 358 }; 359 for (String s : settings) { 360 resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver, 361 UserHandle.USER_ALL); 362 } 363 } 364 365 private class SettingsObserver extends ContentObserver { 366 public SettingsObserver(Handler handler) { 367 super(handler); 368 } 369 370 @Override 371 public void onChange(boolean selfChange, Uri uri) { 372 String option = uri.getLastPathSegment(); 373 boolean enabled = readBooleanSetting(option, true); 374 switch (option) { 375 case Global.HDMI_CONTROL_ENABLED: 376 setControlEnabled(enabled); 377 break; 378 case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED: 379 tv().setAutoWakeup(enabled); 380 setCecOption(OPTION_CEC_AUTO_WAKEUP, toInt(enabled)); 381 break; 382 case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED: 383 tv().setAutoDeviceOff(enabled); 384 // No need to propagate to HAL. 385 break; 386 case Global.MHL_INPUT_SWITCHING_ENABLED: 387 setMhlInputChangeEnabled(enabled); 388 break; 389 case Global.MHL_POWER_CHARGE_ENABLED: 390 if (mMhlController != null) { 391 mMhlController.setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled)); 392 } 393 break; 394 } 395 } 396 } 397 398 private static int toInt(boolean enabled) { 399 return enabled ? ENABLED : DISABLED; 400 } 401 402 boolean readBooleanSetting(String key, boolean defVal) { 403 ContentResolver cr = getContext().getContentResolver(); 404 return Global.getInt(cr, key, toInt(defVal)) == ENABLED; 405 } 406 407 void writeBooleanSetting(String key, boolean value) { 408 ContentResolver cr = getContext().getContentResolver(); 409 Global.putInt(cr, key, toInt(value)); 410 } 411 412 private void unregisterSettingsObserver() { 413 getContext().getContentResolver().unregisterContentObserver(mSettingsObserver); 414 } 415 416 private void initializeCec(int initiatedBy) { 417 mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, ENABLED); 418 initializeLocalDevices(mLocalDevices, initiatedBy); 419 } 420 421 @ServiceThreadOnly 422 private void initializeLocalDevices(final List<Integer> deviceTypes, final int initiatedBy) { 423 assertRunOnServiceThread(); 424 // A container for [Logical Address, Local device info]. 425 final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>(); 426 final int[] finished = new int[1]; 427 clearLocalDevices(); 428 for (int type : deviceTypes) { 429 final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type); 430 localDevice.init(); 431 mCecController.allocateLogicalAddress(type, 432 localDevice.getPreferredAddress(), new AllocateAddressCallback() { 433 @Override 434 public void onAllocated(int deviceType, int logicalAddress) { 435 if (logicalAddress == Constants.ADDR_UNREGISTERED) { 436 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]"); 437 } else { 438 // Set POWER_STATUS_ON to all local devices because they share lifetime 439 // with system. 440 HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType, 441 HdmiControlManager.POWER_STATUS_ON); 442 localDevice.setDeviceInfo(deviceInfo); 443 mCecController.addLocalDevice(deviceType, localDevice); 444 mCecController.addLogicalAddress(logicalAddress); 445 devices.append(logicalAddress, localDevice); 446 } 447 448 // Address allocation completed for all devices. Notify each device. 449 if (deviceTypes.size() == ++finished[0]) { 450 onInitializeCecComplete(); 451 notifyAddressAllocated(devices, initiatedBy); 452 } 453 } 454 }); 455 } 456 } 457 458 @ServiceThreadOnly 459 private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices, int initiatedBy) { 460 assertRunOnServiceThread(); 461 for (int i = 0; i < devices.size(); ++i) { 462 int address = devices.keyAt(i); 463 HdmiCecLocalDevice device = devices.valueAt(i); 464 device.handleAddressAllocated(address, initiatedBy); 465 } 466 } 467 468 // Initialize HDMI port information. Combine the information from CEC and MHL HAL and 469 // keep them in one place. 470 @ServiceThreadOnly 471 private void initPortInfo() { 472 assertRunOnServiceThread(); 473 HdmiPortInfo[] cecPortInfo = null; 474 475 // CEC HAL provides majority of the info while MHL does only MHL support flag for 476 // each port. Return empty array if CEC HAL didn't provide the info. 477 if (mCecController != null) { 478 cecPortInfo = mCecController.getPortInfos(); 479 } 480 if (cecPortInfo == null) { 481 return; 482 } 483 484 SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>(); 485 SparseIntArray portIdMap = new SparseIntArray(); 486 SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>(); 487 for (HdmiPortInfo info : cecPortInfo) { 488 portIdMap.put(info.getAddress(), info.getId()); 489 portInfoMap.put(info.getId(), info); 490 portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId())); 491 } 492 mPortIdMap = new UnmodifiableSparseIntArray(portIdMap); 493 mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap); 494 mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap); 495 496 if (mMhlController == null) { 497 mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo)); 498 return; 499 } else { 500 HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos(); 501 ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length); 502 for (HdmiPortInfo info : mhlPortInfo) { 503 if (info.isMhlSupported()) { 504 mhlSupportedPorts.add(info.getId()); 505 } 506 } 507 508 // Build HDMI port info list with CEC port info plus MHL supported flag. 509 ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length); 510 for (HdmiPortInfo info : cecPortInfo) { 511 if (mhlSupportedPorts.contains(info.getId())) { 512 result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(), 513 info.isCecSupported(), true, info.isArcSupported())); 514 } else { 515 result.add(info); 516 } 517 } 518 mPortInfo = Collections.unmodifiableList(result); 519 } 520 } 521 522 List<HdmiPortInfo> getPortInfo() { 523 return mPortInfo; 524 } 525 526 /** 527 * Returns HDMI port information for the given port id. 528 * 529 * @param portId HDMI port id 530 * @return {@link HdmiPortInfo} for the given port 531 */ 532 HdmiPortInfo getPortInfo(int portId) { 533 return mPortInfoMap.get(portId, null); 534 } 535 536 /** 537 * Returns the routing path (physical address) of the HDMI port for the given 538 * port id. 539 */ 540 int portIdToPath(int portId) { 541 HdmiPortInfo portInfo = getPortInfo(portId); 542 if (portInfo == null) { 543 Slog.e(TAG, "Cannot find the port info: " + portId); 544 return Constants.INVALID_PHYSICAL_ADDRESS; 545 } 546 return portInfo.getAddress(); 547 } 548 549 /** 550 * Returns the id of HDMI port located at the top of the hierarchy of 551 * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance, 552 * the port id to be returned is the ID associated with the port address 553 * 0x1000 (1.0.0.0) which is the topmost path of the given routing path. 554 */ 555 int pathToPortId(int path) { 556 int portAddress = path & Constants.ROUTING_PATH_TOP_MASK; 557 return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID); 558 } 559 560 boolean isValidPortId(int portId) { 561 return getPortInfo(portId) != null; 562 } 563 564 /** 565 * Returns {@link Looper} for IO operation. 566 * 567 * <p>Declared as package-private. 568 */ 569 Looper getIoLooper() { 570 return mIoThread.getLooper(); 571 } 572 573 /** 574 * Returns {@link Looper} of main thread. Use this {@link Looper} instance 575 * for tasks that are running on main service thread. 576 * 577 * <p>Declared as package-private. 578 */ 579 Looper getServiceLooper() { 580 return mHandler.getLooper(); 581 } 582 583 /** 584 * Returns physical address of the device. 585 */ 586 int getPhysicalAddress() { 587 return mCecController.getPhysicalAddress(); 588 } 589 590 /** 591 * Returns vendor id of CEC service. 592 */ 593 int getVendorId() { 594 return mCecController.getVendorId(); 595 } 596 597 @ServiceThreadOnly 598 HdmiDeviceInfo getDeviceInfo(int logicalAddress) { 599 assertRunOnServiceThread(); 600 HdmiCecLocalDeviceTv tv = tv(); 601 if (tv == null) { 602 return null; 603 } 604 return tv.getCecDeviceInfo(logicalAddress); 605 } 606 607 /** 608 * Returns version of CEC. 609 */ 610 int getCecVersion() { 611 return mCecController.getVersion(); 612 } 613 614 /** 615 * Whether a device of the specified physical address is connected to ARC enabled port. 616 */ 617 boolean isConnectedToArcPort(int physicalAddress) { 618 int portId = mPortIdMap.get(physicalAddress); 619 if (portId != Constants.INVALID_PORT_ID) { 620 return mPortInfoMap.get(portId).isArcSupported(); 621 } 622 return false; 623 } 624 625 void runOnServiceThread(Runnable runnable) { 626 mHandler.post(runnable); 627 } 628 629 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) { 630 mHandler.postAtFrontOfQueue(runnable); 631 } 632 633 private void assertRunOnServiceThread() { 634 if (Looper.myLooper() != mHandler.getLooper()) { 635 throw new IllegalStateException("Should run on service thread."); 636 } 637 } 638 639 /** 640 * Transmit a CEC command to CEC bus. 641 * 642 * @param command CEC command to send out 643 * @param callback interface used to the result of send command 644 */ 645 @ServiceThreadOnly 646 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) { 647 assertRunOnServiceThread(); 648 if (mMessageValidator.isValid(command)) { 649 mCecController.sendCommand(command, callback); 650 } else { 651 Slog.e(TAG, "Invalid message type:" + command); 652 if (callback != null) { 653 callback.onSendCompleted(Constants.SEND_RESULT_FAILURE); 654 } 655 } 656 } 657 658 @ServiceThreadOnly 659 void sendCecCommand(HdmiCecMessage command) { 660 assertRunOnServiceThread(); 661 sendCecCommand(command, null); 662 } 663 664 @ServiceThreadOnly 665 void sendMhlSubcommand(int portId, HdmiMhlSubcommand command) { 666 assertRunOnServiceThread(); 667 sendMhlSubcommand(portId, command, null); 668 } 669 670 @ServiceThreadOnly 671 void sendMhlSubcommand(int portId, HdmiMhlSubcommand command, SendMessageCallback callback) { 672 assertRunOnServiceThread(); 673 mMhlController.sendSubcommand(portId, command, callback); 674 } 675 676 /** 677 * Send <Feature Abort> command on the given CEC message if possible. 678 * If the aborted message is invalid, then it wont send the message. 679 * @param command original command to be aborted 680 * @param reason reason of feature abort 681 */ 682 @ServiceThreadOnly 683 void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) { 684 assertRunOnServiceThread(); 685 mCecController.maySendFeatureAbortCommand(command, reason); 686 } 687 688 @ServiceThreadOnly 689 boolean handleCecCommand(HdmiCecMessage message) { 690 assertRunOnServiceThread(); 691 if (!mMessageValidator.isValid(message)) { 692 return false; 693 } 694 return dispatchMessageToLocalDevice(message); 695 } 696 697 void setAudioReturnChannel(boolean enabled) { 698 mCecController.setAudioReturnChannel(enabled); 699 } 700 701 @ServiceThreadOnly 702 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) { 703 assertRunOnServiceThread(); 704 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 705 if (device.dispatchMessage(message) 706 && message.getDestination() != Constants.ADDR_BROADCAST) { 707 return true; 708 } 709 } 710 711 if (message.getDestination() != Constants.ADDR_BROADCAST) { 712 Slog.w(TAG, "Unhandled cec command:" + message); 713 } 714 return false; 715 } 716 717 /** 718 * Called when a new hotplug event is issued. 719 * 720 * @param portId hdmi port number where hot plug event issued. 721 * @param connected whether to be plugged in or not 722 */ 723 @ServiceThreadOnly 724 void onHotplug(int portId, boolean connected) { 725 assertRunOnServiceThread(); 726 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 727 device.onHotplug(portId, connected); 728 } 729 announceHotplugEvent(portId, connected); 730 } 731 732 /** 733 * Poll all remote devices. It sends <Polling Message> to all remote 734 * devices. 735 * 736 * @param callback an interface used to get a list of all remote devices' address 737 * @param sourceAddress a logical address of source device where sends polling message 738 * @param pickStrategy strategy how to pick polling candidates 739 * @param retryCount the number of retry used to send polling message to remote devices 740 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value 741 */ 742 @ServiceThreadOnly 743 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, 744 int retryCount) { 745 assertRunOnServiceThread(); 746 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy), 747 retryCount); 748 } 749 750 private int checkPollStrategy(int pickStrategy) { 751 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK; 752 if (strategy == 0) { 753 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy); 754 } 755 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK; 756 if (iterationStrategy == 0) { 757 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy); 758 } 759 return strategy | iterationStrategy; 760 } 761 762 List<HdmiCecLocalDevice> getAllLocalDevices() { 763 assertRunOnServiceThread(); 764 return mCecController.getLocalDeviceList(); 765 } 766 767 Object getServiceLock() { 768 return mLock; 769 } 770 771 void setAudioStatus(boolean mute, int volume) { 772 AudioManager audioManager = getAudioManager(); 773 boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC); 774 if (mute) { 775 if (!muted) { 776 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true); 777 } 778 } else { 779 if (muted) { 780 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false); 781 } 782 // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing 783 // volume change notification back to hdmi control service. 784 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 785 AudioManager.FLAG_SHOW_UI | 786 AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME); 787 } 788 } 789 790 void announceSystemAudioModeChange(boolean enabled) { 791 for (IHdmiSystemAudioModeChangeListener listener : mSystemAudioModeChangeListeners) { 792 invokeSystemAudioModeChange(listener, enabled); 793 } 794 } 795 796 private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) { 797 // TODO: find better name instead of model name. 798 String displayName = Build.MODEL; 799 return new HdmiDeviceInfo(logicalAddress, 800 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType, 801 getVendorId(), displayName); 802 } 803 804 @ServiceThreadOnly 805 boolean handleMhlSubcommand(int portId, HdmiMhlSubcommand message) { 806 assertRunOnServiceThread(); 807 808 HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId); 809 if (device != null) { 810 return device.handleSubcommand(message); 811 } 812 Slog.w(TAG, "No mhl device exists[portId:" + portId + ", message:" + message); 813 return false; 814 } 815 816 @ServiceThreadOnly 817 void handleMhlHotplugEvent(int portId, boolean connected) { 818 assertRunOnServiceThread(); 819 if (connected) { 820 HdmiMhlLocalDevice newDevice = new HdmiMhlLocalDevice(this, portId); 821 HdmiMhlLocalDevice oldDevice = mMhlController.addLocalDevice(newDevice); 822 if (oldDevice != null) { 823 oldDevice.onDeviceRemoved(); 824 Slog.i(TAG, "Old device of port " + portId + " is removed"); 825 } 826 } else { 827 HdmiMhlLocalDevice device = mMhlController.removeLocalDevice(portId); 828 if (device != null) { 829 device.onDeviceRemoved(); 830 // There is no explicit event for device removal unlike capability register event 831 // used for device addition . Hence we remove the device on hotplug event. 832 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE); 833 updateSafeMhlInput(); 834 } else { 835 Slog.w(TAG, "No device to remove:[portId=" + portId); 836 } 837 } 838 announceHotplugEvent(portId, connected); 839 } 840 841 @ServiceThreadOnly 842 void handleMhlCbusModeChanged(int portId, int cbusmode) { 843 assertRunOnServiceThread(); 844 HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId); 845 if (device != null) { 846 device.setCbusMode(cbusmode); 847 } else { 848 Slog.w(TAG, "No mhl device exists for cbus mode change[portId:" + portId + 849 ", cbusmode:" + cbusmode + "]"); 850 } 851 } 852 853 @ServiceThreadOnly 854 void handleMhlVbusOvercurrent(int portId, boolean on) { 855 assertRunOnServiceThread(); 856 HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId); 857 if (device != null) { 858 device.onVbusOvercurrentDetected(on); 859 } else { 860 Slog.w(TAG, "No mhl device exists for vbus overcurrent event[portId:" + portId + "]"); 861 } 862 } 863 864 @ServiceThreadOnly 865 void handleMhlCapabilityRegisterChanged(int portId, int adopterId, int deviceId) { 866 assertRunOnServiceThread(); 867 HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId); 868 869 // Hotplug event should already have been called before capability register change event. 870 if (device != null) { 871 device.setCapabilityRegister(adopterId, deviceId); 872 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_ADD_DEVICE); 873 updateSafeMhlInput(); 874 } else { 875 Slog.w(TAG, "No mhl device exists for capability register change event[portId:" 876 + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]"); 877 } 878 } 879 880 @ServiceThreadOnly 881 private void updateSafeMhlInput() { 882 assertRunOnServiceThread(); 883 List<HdmiDeviceInfo> inputs = Collections.emptyList(); 884 SparseArray<HdmiMhlLocalDevice> devices = mMhlController.getAllLocalDevices(); 885 for (int i = 0; i < devices.size(); ++i) { 886 HdmiMhlLocalDevice device = devices.valueAt(i); 887 HdmiDeviceInfo info = device.getInfo(); 888 if (info != null) { 889 if (inputs.isEmpty()) { 890 inputs = new ArrayList<>(); 891 } 892 inputs.add(device.getInfo()); 893 } 894 } 895 synchronized (mLock) { 896 mMhlDevices = inputs; 897 } 898 } 899 900 private List<HdmiDeviceInfo> getMhlDevicesLocked() { 901 return mMhlDevices; 902 } 903 904 // Record class that monitors the event of the caller of being killed. Used to clean up 905 // the listener list and record list accordingly. 906 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { 907 private final IHdmiHotplugEventListener mListener; 908 909 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) { 910 mListener = listener; 911 } 912 913 @Override 914 public void binderDied() { 915 synchronized (mLock) { 916 mHotplugEventListenerRecords.remove(this); 917 mHotplugEventListeners.remove(mListener); 918 } 919 } 920 } 921 922 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient { 923 private final IHdmiDeviceEventListener mListener; 924 925 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) { 926 mListener = listener; 927 } 928 929 @Override 930 public void binderDied() { 931 synchronized (mLock) { 932 mDeviceEventListenerRecords.remove(this); 933 mDeviceEventListeners.remove(mListener); 934 } 935 } 936 } 937 938 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient { 939 private final IHdmiSystemAudioModeChangeListener mListener; 940 941 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) { 942 mListener = listener; 943 } 944 945 @Override 946 public void binderDied() { 947 synchronized (mLock) { 948 mSystemAudioModeChangeListenerRecords.remove(this); 949 mSystemAudioModeChangeListeners.remove(mListener); 950 } 951 } 952 } 953 954 class VendorCommandListenerRecord implements IBinder.DeathRecipient { 955 private final IHdmiVendorCommandListener mListener; 956 private final int mDeviceType; 957 958 public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) { 959 mListener = listener; 960 mDeviceType = deviceType; 961 } 962 963 @Override 964 public void binderDied() { 965 synchronized (mLock) { 966 mVendorCommandListenerRecords.remove(this); 967 } 968 } 969 } 970 971 private class HdmiRecordListenerRecord implements IBinder.DeathRecipient { 972 @Override 973 public void binderDied() { 974 synchronized (mLock) { 975 mRecordListener = null; 976 } 977 } 978 } 979 980 private void enforceAccessPermission() { 981 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); 982 } 983 984 private final class BinderService extends IHdmiControlService.Stub { 985 @Override 986 public int[] getSupportedTypes() { 987 enforceAccessPermission(); 988 // mLocalDevices is an unmodifiable list - no lock necesary. 989 int[] localDevices = new int[mLocalDevices.size()]; 990 for (int i = 0; i < localDevices.length; ++i) { 991 localDevices[i] = mLocalDevices.get(i); 992 } 993 return localDevices; 994 } 995 996 @Override 997 public HdmiDeviceInfo getActiveSource() { 998 HdmiCecLocalDeviceTv tv = tv(); 999 if (tv == null) { 1000 Slog.w(TAG, "Local tv device not available"); 1001 return null; 1002 } 1003 ActiveSource activeSource = tv.getActiveSource(); 1004 if (activeSource.isValid()) { 1005 return new HdmiDeviceInfo(activeSource.logicalAddress, 1006 activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID, 1007 HdmiDeviceInfo.DEVICE_INACTIVE, 0, ""); 1008 } 1009 int activePath = tv.getActivePath(); 1010 if (activePath != HdmiDeviceInfo.PATH_INVALID) { 1011 return new HdmiDeviceInfo(activePath, tv.getActivePortId()); 1012 } 1013 return null; 1014 } 1015 1016 @Override 1017 public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) { 1018 enforceAccessPermission(); 1019 runOnServiceThread(new Runnable() { 1020 @Override 1021 public void run() { 1022 if (callback == null) { 1023 Slog.e(TAG, "Callback cannot be null"); 1024 return; 1025 } 1026 HdmiCecLocalDeviceTv tv = tv(); 1027 if (tv == null) { 1028 Slog.w(TAG, "Local tv device not available"); 1029 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1030 return; 1031 } 1032 if (mMhlController != null) { 1033 HdmiMhlLocalDevice device = mMhlController.getLocalDeviceById(deviceId); 1034 if (device != null) { 1035 if (device.getPortId() == tv.getActivePortId()) { 1036 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); 1037 return; 1038 } 1039 // Upon selecting MHL device, we send RAP[Content On] to wake up 1040 // the connected mobile device, start routing control to switch ports. 1041 // callback is handled by MHL action. 1042 device.turnOn(callback); 1043 tv.doManualPortSwitching(device.getInfo().getPortId(), null); 1044 return; 1045 } 1046 } 1047 tv.deviceSelect(deviceId, callback); 1048 } 1049 }); 1050 } 1051 1052 @Override 1053 public void portSelect(final int portId, final IHdmiControlCallback callback) { 1054 enforceAccessPermission(); 1055 runOnServiceThread(new Runnable() { 1056 @Override 1057 public void run() { 1058 if (callback == null) { 1059 Slog.e(TAG, "Callback cannot be null"); 1060 return; 1061 } 1062 HdmiCecLocalDeviceTv tv = tv(); 1063 if (tv == null) { 1064 Slog.w(TAG, "Local tv device not available"); 1065 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1066 return; 1067 } 1068 tv.doManualPortSwitching(portId, callback); 1069 } 1070 }); 1071 } 1072 1073 @Override 1074 public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) { 1075 enforceAccessPermission(); 1076 runOnServiceThread(new Runnable() { 1077 @Override 1078 public void run() { 1079 if (mMhlController != null) { 1080 HdmiMhlLocalDevice device = mMhlController.getLocalDevice(mActivePortId); 1081 if (device != null) { 1082 device.sendKeyEvent(keyCode, isPressed); 1083 return; 1084 } 1085 } 1086 if (mCecController != null) { 1087 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType); 1088 if (localDevice == null) { 1089 Slog.w(TAG, "Local device not available"); 1090 return; 1091 } 1092 localDevice.sendKeyEvent(keyCode, isPressed); 1093 } 1094 } 1095 }); 1096 } 1097 1098 @Override 1099 public void oneTouchPlay(final IHdmiControlCallback callback) { 1100 enforceAccessPermission(); 1101 runOnServiceThread(new Runnable() { 1102 @Override 1103 public void run() { 1104 HdmiControlService.this.oneTouchPlay(callback); 1105 } 1106 }); 1107 } 1108 1109 @Override 1110 public void queryDisplayStatus(final IHdmiControlCallback callback) { 1111 enforceAccessPermission(); 1112 runOnServiceThread(new Runnable() { 1113 @Override 1114 public void run() { 1115 HdmiControlService.this.queryDisplayStatus(callback); 1116 } 1117 }); 1118 } 1119 1120 @Override 1121 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 1122 enforceAccessPermission(); 1123 runOnServiceThread(new Runnable() { 1124 @Override 1125 public void run() { 1126 HdmiControlService.this.addHotplugEventListener(listener); 1127 } 1128 }); 1129 } 1130 1131 @Override 1132 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) { 1133 enforceAccessPermission(); 1134 runOnServiceThread(new Runnable() { 1135 @Override 1136 public void run() { 1137 HdmiControlService.this.removeHotplugEventListener(listener); 1138 } 1139 }); 1140 } 1141 1142 @Override 1143 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) { 1144 enforceAccessPermission(); 1145 runOnServiceThread(new Runnable() { 1146 @Override 1147 public void run() { 1148 HdmiControlService.this.addDeviceEventListener(listener); 1149 } 1150 }); 1151 } 1152 1153 @Override 1154 public List<HdmiPortInfo> getPortInfo() { 1155 enforceAccessPermission(); 1156 return HdmiControlService.this.getPortInfo(); 1157 } 1158 1159 @Override 1160 public boolean canChangeSystemAudioMode() { 1161 enforceAccessPermission(); 1162 HdmiCecLocalDeviceTv tv = tv(); 1163 if (tv == null) { 1164 return false; 1165 } 1166 return tv.hasSystemAudioDevice(); 1167 } 1168 1169 @Override 1170 public boolean getSystemAudioMode() { 1171 enforceAccessPermission(); 1172 HdmiCecLocalDeviceTv tv = tv(); 1173 if (tv == null) { 1174 return false; 1175 } 1176 return tv.isSystemAudioActivated(); 1177 } 1178 1179 @Override 1180 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) { 1181 enforceAccessPermission(); 1182 runOnServiceThread(new Runnable() { 1183 @Override 1184 public void run() { 1185 HdmiCecLocalDeviceTv tv = tv(); 1186 if (tv == null) { 1187 Slog.w(TAG, "Local tv device not available"); 1188 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1189 return; 1190 } 1191 tv.changeSystemAudioMode(enabled, callback); 1192 } 1193 }); 1194 } 1195 1196 @Override 1197 public void addSystemAudioModeChangeListener( 1198 final IHdmiSystemAudioModeChangeListener listener) { 1199 enforceAccessPermission(); 1200 HdmiControlService.this.addSystemAudioModeChangeListner(listener); 1201 } 1202 1203 @Override 1204 public void removeSystemAudioModeChangeListener( 1205 final IHdmiSystemAudioModeChangeListener listener) { 1206 enforceAccessPermission(); 1207 HdmiControlService.this.removeSystemAudioModeChangeListener(listener); 1208 } 1209 1210 @Override 1211 public void setInputChangeListener(final IHdmiInputChangeListener listener) { 1212 enforceAccessPermission(); 1213 HdmiControlService.this.setInputChangeListener(listener); 1214 } 1215 1216 @Override 1217 public List<HdmiDeviceInfo> getInputDevices() { 1218 enforceAccessPermission(); 1219 // No need to hold the lock for obtaining TV device as the local device instance 1220 // is preserved while the HDMI control is enabled. 1221 HdmiCecLocalDeviceTv tv = tv(); 1222 synchronized (mLock) { 1223 List<HdmiDeviceInfo> cecDevices = (tv == null) 1224 ? Collections.<HdmiDeviceInfo>emptyList() 1225 : tv.getSafeExternalInputsLocked(); 1226 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked()); 1227 } 1228 } 1229 1230 @Override 1231 public void setSystemAudioVolume(final int oldIndex, final int newIndex, 1232 final int maxIndex) { 1233 enforceAccessPermission(); 1234 runOnServiceThread(new Runnable() { 1235 @Override 1236 public void run() { 1237 HdmiCecLocalDeviceTv tv = tv(); 1238 if (tv == null) { 1239 Slog.w(TAG, "Local tv device not available"); 1240 return; 1241 } 1242 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex); 1243 } 1244 }); 1245 } 1246 1247 @Override 1248 public void setSystemAudioMute(final boolean mute) { 1249 enforceAccessPermission(); 1250 runOnServiceThread(new Runnable() { 1251 @Override 1252 public void run() { 1253 HdmiCecLocalDeviceTv tv = tv(); 1254 if (tv == null) { 1255 Slog.w(TAG, "Local tv device not available"); 1256 return; 1257 } 1258 tv.changeMute(mute); 1259 } 1260 }); 1261 } 1262 1263 @Override 1264 public void setArcMode(final boolean enabled) { 1265 enforceAccessPermission(); 1266 runOnServiceThread(new Runnable() { 1267 @Override 1268 public void run() { 1269 HdmiCecLocalDeviceTv tv = tv(); 1270 if (tv == null) { 1271 Slog.w(TAG, "Local tv device not available to change arc mode."); 1272 return; 1273 } 1274 } 1275 }); 1276 } 1277 1278 @Override 1279 public void setProhibitMode(final boolean enabled) { 1280 enforceAccessPermission(); 1281 if (!isTvDevice()) { 1282 return; 1283 } 1284 HdmiControlService.this.setProhibitMode(enabled); 1285 } 1286 1287 @Override 1288 public void addVendorCommandListener(final IHdmiVendorCommandListener listener, 1289 final int deviceType) { 1290 enforceAccessPermission(); 1291 runOnServiceThread(new Runnable() { 1292 @Override 1293 public void run() { 1294 HdmiControlService.this.addVendorCommandListener(listener, deviceType); 1295 } 1296 }); 1297 } 1298 1299 @Override 1300 public void sendVendorCommand(final int deviceType, final int targetAddress, 1301 final byte[] params, final boolean hasVendorId) { 1302 enforceAccessPermission(); 1303 runOnServiceThread(new Runnable() { 1304 @Override 1305 public void run() { 1306 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType); 1307 if (device == null) { 1308 Slog.w(TAG, "Local device not available"); 1309 return; 1310 } 1311 if (hasVendorId) { 1312 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId( 1313 device.getDeviceInfo().getLogicalAddress(), targetAddress, 1314 getVendorId(), params)); 1315 } else { 1316 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand( 1317 device.getDeviceInfo().getLogicalAddress(), targetAddress, params)); 1318 } 1319 } 1320 }); 1321 } 1322 1323 @Override 1324 public void setHdmiRecordListener(IHdmiRecordListener listener) { 1325 HdmiControlService.this.setHdmiRecordListener(listener); 1326 } 1327 1328 @Override 1329 public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) { 1330 runOnServiceThread(new Runnable() { 1331 @Override 1332 public void run() { 1333 if (!isTvDevice()) { 1334 Slog.w(TAG, "No TV is available."); 1335 return; 1336 } 1337 tv().startOneTouchRecord(recorderAddress, recordSource); 1338 } 1339 }); 1340 } 1341 1342 @Override 1343 public void stopOneTouchRecord(final int recorderAddress) { 1344 runOnServiceThread(new Runnable() { 1345 @Override 1346 public void run() { 1347 if (!isTvDevice()) { 1348 Slog.w(TAG, "No TV is available."); 1349 return; 1350 } 1351 tv().stopOneTouchRecord(recorderAddress); 1352 } 1353 }); 1354 } 1355 1356 @Override 1357 public void startTimerRecording(final int recorderAddress, final int sourceType, 1358 final byte[] recordSource) { 1359 runOnServiceThread(new Runnable() { 1360 @Override 1361 public void run() { 1362 if (!isTvDevice()) { 1363 Slog.w(TAG, "No TV is available."); 1364 return; 1365 } 1366 tv().startTimerRecording(recorderAddress, sourceType, recordSource); 1367 } 1368 }); 1369 } 1370 1371 @Override 1372 public void clearTimerRecording(final int recorderAddress, final int sourceType, 1373 final byte[] recordSource) { 1374 runOnServiceThread(new Runnable() { 1375 @Override 1376 public void run() { 1377 if (!isTvDevice()) { 1378 Slog.w(TAG, "No TV is available."); 1379 return; 1380 } 1381 tv().clearTimerRecording(recorderAddress, sourceType, recordSource); 1382 } 1383 }); 1384 } 1385 } 1386 1387 @ServiceThreadOnly 1388 private void oneTouchPlay(final IHdmiControlCallback callback) { 1389 assertRunOnServiceThread(); 1390 HdmiCecLocalDevicePlayback source = playback(); 1391 if (source == null) { 1392 Slog.w(TAG, "Local playback device not available"); 1393 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1394 return; 1395 } 1396 source.oneTouchPlay(callback); 1397 } 1398 1399 @ServiceThreadOnly 1400 private void queryDisplayStatus(final IHdmiControlCallback callback) { 1401 assertRunOnServiceThread(); 1402 HdmiCecLocalDevicePlayback source = playback(); 1403 if (source == null) { 1404 Slog.w(TAG, "Local playback device not available"); 1405 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1406 return; 1407 } 1408 source.queryDisplayStatus(callback); 1409 } 1410 1411 private void addHotplugEventListener(IHdmiHotplugEventListener listener) { 1412 HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); 1413 try { 1414 listener.asBinder().linkToDeath(record, 0); 1415 } catch (RemoteException e) { 1416 Slog.w(TAG, "Listener already died"); 1417 return; 1418 } 1419 synchronized (mLock) { 1420 mHotplugEventListenerRecords.add(record); 1421 mHotplugEventListeners.add(listener); 1422 } 1423 } 1424 1425 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) { 1426 synchronized (mLock) { 1427 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 1428 if (record.mListener.asBinder() == listener.asBinder()) { 1429 listener.asBinder().unlinkToDeath(record, 0); 1430 mHotplugEventListenerRecords.remove(record); 1431 break; 1432 } 1433 } 1434 mHotplugEventListeners.remove(listener); 1435 } 1436 } 1437 1438 private void addDeviceEventListener(IHdmiDeviceEventListener listener) { 1439 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener); 1440 try { 1441 listener.asBinder().linkToDeath(record, 0); 1442 } catch (RemoteException e) { 1443 Slog.w(TAG, "Listener already died"); 1444 return; 1445 } 1446 synchronized (mLock) { 1447 mDeviceEventListeners.add(listener); 1448 mDeviceEventListenerRecords.add(record); 1449 } 1450 } 1451 1452 void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) { 1453 synchronized (mLock) { 1454 for (IHdmiDeviceEventListener listener : mDeviceEventListeners) { 1455 try { 1456 listener.onStatusChanged(device, status); 1457 } catch (RemoteException e) { 1458 Slog.e(TAG, "Failed to report device event:" + e); 1459 } 1460 } 1461 } 1462 } 1463 1464 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) { 1465 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord( 1466 listener); 1467 try { 1468 listener.asBinder().linkToDeath(record, 0); 1469 } catch (RemoteException e) { 1470 Slog.w(TAG, "Listener already died"); 1471 return; 1472 } 1473 synchronized (mLock) { 1474 mSystemAudioModeChangeListeners.add(listener); 1475 mSystemAudioModeChangeListenerRecords.add(record); 1476 } 1477 } 1478 1479 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) { 1480 synchronized (mLock) { 1481 for (SystemAudioModeChangeListenerRecord record : 1482 mSystemAudioModeChangeListenerRecords) { 1483 if (record.mListener.asBinder() == listener) { 1484 listener.asBinder().unlinkToDeath(record, 0); 1485 mSystemAudioModeChangeListenerRecords.remove(record); 1486 break; 1487 } 1488 } 1489 mSystemAudioModeChangeListeners.remove(listener); 1490 } 1491 } 1492 1493 private final class InputChangeListenerRecord implements IBinder.DeathRecipient { 1494 @Override 1495 public void binderDied() { 1496 synchronized (mLock) { 1497 mInputChangeListener = null; 1498 } 1499 } 1500 } 1501 1502 private void setInputChangeListener(IHdmiInputChangeListener listener) { 1503 synchronized (mLock) { 1504 mInputChangeListenerRecord = new InputChangeListenerRecord(); 1505 try { 1506 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0); 1507 } catch (RemoteException e) { 1508 Slog.w(TAG, "Listener already died"); 1509 return; 1510 } 1511 mInputChangeListener = listener; 1512 } 1513 } 1514 1515 void invokeInputChangeListener(HdmiDeviceInfo info) { 1516 synchronized (mLock) { 1517 if (mInputChangeListener != null) { 1518 try { 1519 mInputChangeListener.onChanged(info); 1520 } catch (RemoteException e) { 1521 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e); 1522 } 1523 } 1524 } 1525 } 1526 1527 private void setHdmiRecordListener(IHdmiRecordListener listener) { 1528 synchronized (mLock) { 1529 mRecordListenerRecord = new HdmiRecordListenerRecord(); 1530 try { 1531 listener.asBinder().linkToDeath(mRecordListenerRecord, 0); 1532 } catch (RemoteException e) { 1533 Slog.w(TAG, "Listener already died.", e); 1534 } 1535 mRecordListener = listener; 1536 } 1537 } 1538 1539 byte[] invokeRecordRequestListener(int recorderAddress) { 1540 synchronized (mLock) { 1541 if (mRecordListener != null) { 1542 try { 1543 return mRecordListener.getOneTouchRecordSource(recorderAddress); 1544 } catch (RemoteException e) { 1545 Slog.w(TAG, "Failed to start record.", e); 1546 } 1547 } 1548 return EmptyArray.BYTE; 1549 } 1550 } 1551 1552 void invokeOneTouchRecordResult(int result) { 1553 synchronized (mLock) { 1554 if (mRecordListener != null) { 1555 try { 1556 mRecordListener.onOneTouchRecordResult(result); 1557 } catch (RemoteException e) { 1558 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e); 1559 } 1560 } 1561 } 1562 } 1563 1564 void invokeTimerRecordingResult(int result) { 1565 synchronized (mLock) { 1566 if (mRecordListener != null) { 1567 try { 1568 mRecordListener.onTimerRecordingResult(result); 1569 } catch (RemoteException e) { 1570 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e); 1571 } 1572 } 1573 } 1574 } 1575 1576 void invokeClearTimerRecordingResult(int result) { 1577 synchronized (mLock) { 1578 if (mRecordListener != null) { 1579 try { 1580 mRecordListener.onClearTimerRecordingResult(result); 1581 } catch (RemoteException e) { 1582 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e); 1583 } 1584 } 1585 } 1586 } 1587 1588 private void invokeCallback(IHdmiControlCallback callback, int result) { 1589 try { 1590 callback.onComplete(result); 1591 } catch (RemoteException e) { 1592 Slog.e(TAG, "Invoking callback failed:" + e); 1593 } 1594 } 1595 1596 private void invokeSystemAudioModeChange(IHdmiSystemAudioModeChangeListener listener, 1597 boolean enabled) { 1598 try { 1599 listener.onStatusChanged(enabled); 1600 } catch (RemoteException e) { 1601 Slog.e(TAG, "Invoking callback failed:" + e); 1602 } 1603 } 1604 1605 private void announceHotplugEvent(int portId, boolean connected) { 1606 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected); 1607 synchronized (mLock) { 1608 for (IHdmiHotplugEventListener listener : mHotplugEventListeners) { 1609 invokeHotplugEventListenerLocked(listener, event); 1610 } 1611 } 1612 } 1613 1614 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener, 1615 HdmiHotplugEvent event) { 1616 try { 1617 listener.onReceived(event); 1618 } catch (RemoteException e) { 1619 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e); 1620 } 1621 } 1622 1623 private HdmiCecLocalDeviceTv tv() { 1624 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV); 1625 } 1626 1627 boolean isTvDevice() { 1628 return tv() != null; 1629 } 1630 1631 private HdmiCecLocalDevicePlayback playback() { 1632 return (HdmiCecLocalDevicePlayback) 1633 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK); 1634 } 1635 1636 AudioManager getAudioManager() { 1637 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); 1638 } 1639 1640 boolean isControlEnabled() { 1641 synchronized (mLock) { 1642 return mHdmiControlEnabled; 1643 } 1644 } 1645 1646 int getPowerStatus() { 1647 return mPowerStatus; 1648 } 1649 1650 boolean isPowerOnOrTransient() { 1651 return mPowerStatus == HdmiControlManager.POWER_STATUS_ON 1652 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; 1653 } 1654 1655 boolean isPowerStandbyOrTransient() { 1656 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY 1657 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY; 1658 } 1659 1660 boolean isPowerStandby() { 1661 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY; 1662 } 1663 1664 @ServiceThreadOnly 1665 void wakeUp() { 1666 assertRunOnServiceThread(); 1667 mWakeUpMessageReceived = true; 1668 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); 1669 pm.wakeUp(SystemClock.uptimeMillis()); 1670 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets 1671 // the intent, the sequence will continue at onWakeUp(). 1672 } 1673 1674 @ServiceThreadOnly 1675 void standby() { 1676 assertRunOnServiceThread(); 1677 mStandbyMessageReceived = true; 1678 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); 1679 pm.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0); 1680 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets 1681 // the intent, the sequence will continue at onStandby(). 1682 } 1683 1684 void nap() { 1685 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); 1686 pm.nap(SystemClock.uptimeMillis()); 1687 } 1688 1689 @ServiceThreadOnly 1690 private void onWakeUp() { 1691 assertRunOnServiceThread(); 1692 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; 1693 if (mCecController != null) { 1694 if (mHdmiControlEnabled) { 1695 int startReason = INITIATED_BY_SCREEN_ON; 1696 if (mWakeUpMessageReceived) { 1697 startReason = INITIATED_BY_WAKE_UP_MESSAGE; 1698 } 1699 initializeCec(startReason); 1700 } 1701 } else { 1702 Slog.i(TAG, "Device does not support HDMI-CEC."); 1703 } 1704 // TODO: Initialize MHL local devices. 1705 } 1706 1707 @ServiceThreadOnly 1708 private void onStandby() { 1709 assertRunOnServiceThread(); 1710 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY; 1711 1712 final List<HdmiCecLocalDevice> devices = getAllLocalDevices(); 1713 disableDevices(new PendingActionClearedCallback() { 1714 @Override 1715 public void onCleared(HdmiCecLocalDevice device) { 1716 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType); 1717 devices.remove(device); 1718 if (devices.isEmpty()) { 1719 onStandbyCompleted(); 1720 // We will not clear local devices here, since some OEM/SOC will keep passing 1721 // the received packets until the application processor enters to the sleep 1722 // actually. 1723 } 1724 } 1725 }); 1726 } 1727 1728 @ServiceThreadOnly 1729 private void onLanguageChanged(String language) { 1730 assertRunOnServiceThread(); 1731 mLanguage = language; 1732 1733 if (isTvDevice()) { 1734 tv().broadcastMenuLanguage(language); 1735 } 1736 } 1737 1738 private void disableDevices(PendingActionClearedCallback callback) { 1739 if (mCecController != null) { 1740 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 1741 device.disableDevice(mStandbyMessageReceived, callback); 1742 } 1743 if (isTvDevice()) { 1744 unregisterSettingsObserver(); 1745 } 1746 } 1747 1748 if (mMhlController != null) { 1749 mMhlController.clearAllLocalDevices(); 1750 } 1751 } 1752 1753 @ServiceThreadOnly 1754 private void clearLocalDevices() { 1755 assertRunOnServiceThread(); 1756 if (mCecController == null) { 1757 return; 1758 } 1759 mCecController.clearLogicalAddress(); 1760 mCecController.clearLocalDevices(); 1761 } 1762 1763 @ServiceThreadOnly 1764 private void onStandbyCompleted() { 1765 assertRunOnServiceThread(); 1766 Slog.v(TAG, "onStandbyCompleted"); 1767 1768 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) { 1769 return; 1770 } 1771 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; 1772 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 1773 device.onStandby(mStandbyMessageReceived); 1774 } 1775 mStandbyMessageReceived = false; 1776 mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, DISABLED); 1777 } 1778 1779 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) { 1780 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType); 1781 try { 1782 listener.asBinder().linkToDeath(record, 0); 1783 } catch (RemoteException e) { 1784 Slog.w(TAG, "Listener already died"); 1785 return; 1786 } 1787 synchronized (mLock) { 1788 mVendorCommandListenerRecords.add(record); 1789 } 1790 } 1791 1792 void invokeVendorCommandListeners(int deviceType, int srcAddress, byte[] params, 1793 boolean hasVendorId) { 1794 synchronized (mLock) { 1795 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) { 1796 if (record.mDeviceType != deviceType) { 1797 continue; 1798 } 1799 try { 1800 record.mListener.onReceived(srcAddress, params, hasVendorId); 1801 } catch (RemoteException e) { 1802 Slog.e(TAG, "Failed to notify vendor command reception", e); 1803 } 1804 } 1805 } 1806 } 1807 1808 boolean isProhibitMode() { 1809 synchronized (mLock) { 1810 return mProhibitMode; 1811 } 1812 } 1813 1814 void setProhibitMode(boolean enabled) { 1815 synchronized (mLock) { 1816 mProhibitMode = enabled; 1817 } 1818 } 1819 1820 @ServiceThreadOnly 1821 void setCecOption(int key, int value) { 1822 assertRunOnServiceThread(); 1823 mCecController.setOption(key, value); 1824 } 1825 1826 @ServiceThreadOnly 1827 void setControlEnabled(boolean enabled) { 1828 assertRunOnServiceThread(); 1829 1830 int value = toInt(enabled); 1831 mCecController.setOption(OPTION_CEC_ENABLE, value); 1832 if (mMhlController != null) { 1833 mMhlController.setOption(OPTION_MHL_ENABLE, value); 1834 } 1835 1836 synchronized (mLock) { 1837 mHdmiControlEnabled = enabled; 1838 } 1839 1840 if (enabled) { 1841 initializeCec(INITIATED_BY_ENABLE_CEC); 1842 } else { 1843 disableDevices(new PendingActionClearedCallback() { 1844 @Override 1845 public void onCleared(HdmiCecLocalDevice device) { 1846 assertRunOnServiceThread(); 1847 clearLocalDevices(); 1848 } 1849 }); 1850 } 1851 } 1852 1853 @ServiceThreadOnly 1854 void setActivePortId(int portId) { 1855 assertRunOnServiceThread(); 1856 mActivePortId = portId; 1857 1858 // Resets last input for MHL, which stays valid only after the MHL device was selected, 1859 // and no further switching is done. 1860 setLastInputForMhl(Constants.INVALID_PORT_ID); 1861 } 1862 1863 @ServiceThreadOnly 1864 void setLastInputForMhl(int portId) { 1865 assertRunOnServiceThread(); 1866 mLastInputMhl = portId; 1867 } 1868 1869 @ServiceThreadOnly 1870 int getLastInputForMhl() { 1871 assertRunOnServiceThread(); 1872 return mLastInputMhl; 1873 } 1874 1875 /** 1876 * Performs input change, routing control for MHL device. 1877 * 1878 * @param portId MHL port, or the last port to go back to if {@code contentOn} is false 1879 * @param contentOn {@code true} if RAP data is content on; otherwise false 1880 */ 1881 @ServiceThreadOnly 1882 void changeInputForMhl(int portId, boolean contentOn) { 1883 assertRunOnServiceThread(); 1884 final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID; 1885 tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() { 1886 @Override 1887 public void onComplete(int result) throws RemoteException { 1888 // Keep the last input to switch back later when RAP[ContentOff] is received. 1889 // This effectively sets the port to invalid one if the switching is for 1890 // RAP[ContentOff]. 1891 setLastInputForMhl(lastInput); 1892 } 1893 }); 1894 1895 // MHL device is always directly connected to the port. Update the active port ID to avoid 1896 // unnecessary post-routing control task. 1897 tv().setActivePortId(portId); 1898 1899 // The port is either the MHL-enabled port where the mobile device is connected, or 1900 // the last port to go back to when RAP[ContentOff] is received. Note that the last port 1901 // may not be the MHL-enabled one. In this case the device info to be passed to 1902 // input change listener should be the one describing the corresponding HDMI port. 1903 HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId); 1904 HdmiDeviceInfo info = (device != null && device.getInfo() != null) 1905 ? device.getInfo() 1906 : mPortDeviceMap.get(portId); 1907 invokeInputChangeListener(info); 1908 } 1909 1910 void setMhlInputChangeEnabled(boolean enabled) { 1911 if (mMhlController != null) { 1912 mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled)); 1913 } 1914 1915 synchronized (mLock) { 1916 mMhlInputChangeEnabled = enabled; 1917 } 1918 } 1919 1920 boolean isMhlInputChangeEnabled() { 1921 synchronized (mLock) { 1922 return mMhlInputChangeEnabled; 1923 } 1924 } 1925} 1926