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