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