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