HdmiControlService.java revision 2738e2d872a22fe95a99941139863ff642fbd8e7
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 List<HdmiPortInfo> getPortInfo() { 499 return mPortInfo; 500 } 501 502 /** 503 * Returns HDMI port information for the given port id. 504 * 505 * @param portId HDMI port id 506 * @return {@link HdmiPortInfo} for the given port 507 */ 508 HdmiPortInfo getPortInfo(int portId) { 509 return mPortInfoMap.get(portId, null); 510 } 511 512 /** 513 * Returns the routing path (physical address) of the HDMI port for the given 514 * port id. 515 */ 516 int portIdToPath(int portId) { 517 HdmiPortInfo portInfo = getPortInfo(portId); 518 if (portInfo == null) { 519 Slog.e(TAG, "Cannot find the port info: " + portId); 520 return Constants.INVALID_PHYSICAL_ADDRESS; 521 } 522 return portInfo.getAddress(); 523 } 524 525 /** 526 * Returns the id of HDMI port located at the top of the hierarchy of 527 * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance, 528 * the port id to be returned is the ID associated with the port address 529 * 0x1000 (1.0.0.0) which is the topmost path of the given routing path. 530 */ 531 int pathToPortId(int path) { 532 int portAddress = path & Constants.ROUTING_PATH_TOP_MASK; 533 return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID); 534 } 535 536 boolean isValidPortId(int portId) { 537 return getPortInfo(portId) != null; 538 } 539 540 /** 541 * Returns {@link Looper} for IO operation. 542 * 543 * <p>Declared as package-private. 544 */ 545 Looper getIoLooper() { 546 return mIoThread.getLooper(); 547 } 548 549 /** 550 * Returns {@link Looper} of main thread. Use this {@link Looper} instance 551 * for tasks that are running on main service thread. 552 * 553 * <p>Declared as package-private. 554 */ 555 Looper getServiceLooper() { 556 return mHandler.getLooper(); 557 } 558 559 /** 560 * Returns physical address of the device. 561 */ 562 int getPhysicalAddress() { 563 return mCecController.getPhysicalAddress(); 564 } 565 566 /** 567 * Returns vendor id of CEC service. 568 */ 569 int getVendorId() { 570 return mCecController.getVendorId(); 571 } 572 573 @ServiceThreadOnly 574 HdmiDeviceInfo getDeviceInfo(int logicalAddress) { 575 assertRunOnServiceThread(); 576 HdmiCecLocalDeviceTv tv = tv(); 577 if (tv == null) { 578 return null; 579 } 580 return tv.getCecDeviceInfo(logicalAddress); 581 } 582 583 /** 584 * Returns version of CEC. 585 */ 586 int getCecVersion() { 587 return mCecController.getVersion(); 588 } 589 590 /** 591 * Whether a device of the specified physical address is connected to ARC enabled port. 592 */ 593 boolean isConnectedToArcPort(int physicalAddress) { 594 int portId = mPortIdMap.get(physicalAddress); 595 if (portId != Constants.INVALID_PORT_ID) { 596 return mPortInfoMap.get(portId).isArcSupported(); 597 } 598 return false; 599 } 600 601 void runOnServiceThread(Runnable runnable) { 602 mHandler.post(runnable); 603 } 604 605 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) { 606 mHandler.postAtFrontOfQueue(runnable); 607 } 608 609 private void assertRunOnServiceThread() { 610 if (Looper.myLooper() != mHandler.getLooper()) { 611 throw new IllegalStateException("Should run on service thread."); 612 } 613 } 614 615 /** 616 * Transmit a CEC command to CEC bus. 617 * 618 * @param command CEC command to send out 619 * @param callback interface used to the result of send command 620 */ 621 @ServiceThreadOnly 622 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) { 623 assertRunOnServiceThread(); 624 if (mMessageValidator.isValid(command)) { 625 mCecController.sendCommand(command, callback); 626 } else { 627 Slog.e(TAG, "Invalid message type:" + command); 628 if (callback != null) { 629 callback.onSendCompleted(Constants.SEND_RESULT_FAILURE); 630 } 631 } 632 } 633 634 @ServiceThreadOnly 635 void sendCecCommand(HdmiCecMessage command) { 636 assertRunOnServiceThread(); 637 sendCecCommand(command, null); 638 } 639 640 @ServiceThreadOnly 641 void sendMhlSubcommand(int portId, HdmiMhlSubcommand command) { 642 assertRunOnServiceThread(); 643 sendMhlSubcommand(portId, command, null); 644 } 645 646 @ServiceThreadOnly 647 void sendMhlSubcommand(int portId, HdmiMhlSubcommand command, SendMessageCallback callback) { 648 assertRunOnServiceThread(); 649 mMhlController.sendSubcommand(portId, command, callback); 650 } 651 652 /** 653 * Send <Feature Abort> command on the given CEC message if possible. 654 * If the aborted message is invalid, then it wont send the message. 655 * @param command original command to be aborted 656 * @param reason reason of feature abort 657 */ 658 @ServiceThreadOnly 659 void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) { 660 assertRunOnServiceThread(); 661 mCecController.maySendFeatureAbortCommand(command, reason); 662 } 663 664 @ServiceThreadOnly 665 boolean handleCecCommand(HdmiCecMessage message) { 666 assertRunOnServiceThread(); 667 if (!mMessageValidator.isValid(message)) { 668 return false; 669 } 670 return dispatchMessageToLocalDevice(message); 671 } 672 673 void setAudioReturnChannel(boolean enabled) { 674 mCecController.setAudioReturnChannel(enabled); 675 } 676 677 @ServiceThreadOnly 678 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) { 679 assertRunOnServiceThread(); 680 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 681 if (device.dispatchMessage(message) 682 && message.getDestination() != Constants.ADDR_BROADCAST) { 683 return true; 684 } 685 } 686 687 if (message.getDestination() != Constants.ADDR_BROADCAST) { 688 Slog.w(TAG, "Unhandled cec command:" + message); 689 } 690 return false; 691 } 692 693 /** 694 * Called when a new hotplug event is issued. 695 * 696 * @param portId hdmi port number where hot plug event issued. 697 * @param connected whether to be plugged in or not 698 */ 699 @ServiceThreadOnly 700 void onHotplug(int portId, boolean connected) { 701 assertRunOnServiceThread(); 702 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 703 device.onHotplug(portId, connected); 704 } 705 announceHotplugEvent(portId, connected); 706 } 707 708 /** 709 * Poll all remote devices. It sends <Polling Message> to all remote 710 * devices. 711 * 712 * @param callback an interface used to get a list of all remote devices' address 713 * @param sourceAddress a logical address of source device where sends polling message 714 * @param pickStrategy strategy how to pick polling candidates 715 * @param retryCount the number of retry used to send polling message to remote devices 716 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value 717 */ 718 @ServiceThreadOnly 719 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, 720 int retryCount) { 721 assertRunOnServiceThread(); 722 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy), 723 retryCount); 724 } 725 726 private int checkPollStrategy(int pickStrategy) { 727 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK; 728 if (strategy == 0) { 729 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy); 730 } 731 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK; 732 if (iterationStrategy == 0) { 733 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy); 734 } 735 return strategy | iterationStrategy; 736 } 737 738 List<HdmiCecLocalDevice> getAllLocalDevices() { 739 assertRunOnServiceThread(); 740 return mCecController.getLocalDeviceList(); 741 } 742 743 Object getServiceLock() { 744 return mLock; 745 } 746 747 void setAudioStatus(boolean mute, int volume) { 748 AudioManager audioManager = getAudioManager(); 749 boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC); 750 if (mute) { 751 if (!muted) { 752 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true); 753 } 754 } else { 755 if (muted) { 756 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false); 757 } 758 // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing 759 // volume change notification back to hdmi control service. 760 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 761 AudioManager.FLAG_SHOW_UI | 762 AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME); 763 } 764 } 765 766 void announceSystemAudioModeChange(boolean enabled) { 767 for (IHdmiSystemAudioModeChangeListener listener : mSystemAudioModeChangeListeners) { 768 invokeSystemAudioModeChange(listener, enabled); 769 } 770 } 771 772 private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) { 773 // TODO: find better name instead of model name. 774 String displayName = Build.MODEL; 775 return new HdmiDeviceInfo(logicalAddress, 776 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType, 777 getVendorId(), displayName); 778 } 779 780 @ServiceThreadOnly 781 boolean handleMhlSubcommand(int portId, HdmiMhlSubcommand message) { 782 assertRunOnServiceThread(); 783 784 HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId); 785 if (device != null) { 786 return device.handleSubcommand(message); 787 } 788 Slog.w(TAG, "No mhl device exists[portId:" + portId + ", message:" + message); 789 return false; 790 } 791 792 @ServiceThreadOnly 793 void handleMhlHotplugEvent(int portId, boolean connected) { 794 assertRunOnServiceThread(); 795 if (connected) { 796 HdmiMhlLocalDevice newDevice = new HdmiMhlLocalDevice(this, portId); 797 HdmiMhlLocalDevice oldDevice = mMhlController.addLocalDevice(newDevice); 798 if (oldDevice != null) { 799 oldDevice.onDeviceRemoved(); 800 Slog.i(TAG, "Old device of port " + portId + " is removed"); 801 } 802 } else { 803 HdmiMhlLocalDevice device = mMhlController.removeLocalDevice(portId); 804 if (device != null) { 805 device.onDeviceRemoved(); 806 // There is no explicit event for device removal unlike capability register event 807 // used for device addition . Hence we remove the device on hotplug event. 808 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE); 809 updateSafeMhlInput(); 810 } else { 811 Slog.w(TAG, "No device to remove:[portId=" + portId); 812 } 813 } 814 announceHotplugEvent(portId, connected); 815 } 816 817 @ServiceThreadOnly 818 void handleMhlCbusModeChanged(int portId, int cbusmode) { 819 assertRunOnServiceThread(); 820 HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId); 821 if (device != null) { 822 device.setCbusMode(cbusmode); 823 } else { 824 Slog.w(TAG, "No mhl device exists for cbus mode change[portId:" + portId + 825 ", cbusmode:" + cbusmode + "]"); 826 } 827 } 828 829 @ServiceThreadOnly 830 void handleMhlVbusOvercurrent(int portId, boolean on) { 831 assertRunOnServiceThread(); 832 HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId); 833 if (device != null) { 834 device.onVbusOvercurrentDetected(on); 835 } else { 836 Slog.w(TAG, "No mhl device exists for vbus overcurrent event[portId:" + portId + "]"); 837 } 838 } 839 840 @ServiceThreadOnly 841 void handleMhlCapabilityRegisterChanged(int portId, int adopterId, int deviceId) { 842 assertRunOnServiceThread(); 843 HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId); 844 845 // Hotplug event should already have been called before capability register change event. 846 if (device != null) { 847 device.setCapabilityRegister(adopterId, deviceId); 848 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_ADD_DEVICE); 849 updateSafeMhlInput(); 850 } else { 851 Slog.w(TAG, "No mhl device exists for capability register change event[portId:" 852 + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]"); 853 } 854 } 855 856 @ServiceThreadOnly 857 private void updateSafeMhlInput() { 858 assertRunOnServiceThread(); 859 List<HdmiDeviceInfo> inputs = Collections.emptyList(); 860 SparseArray<HdmiMhlLocalDevice> devices = mMhlController.getAllLocalDevices(); 861 for (int i = 0; i < devices.size(); ++i) { 862 HdmiMhlLocalDevice device = devices.valueAt(i); 863 HdmiDeviceInfo info = device.getInfo(); 864 if (info != null) { 865 if (inputs.isEmpty()) { 866 inputs = new ArrayList<>(); 867 } 868 inputs.add(device.getInfo()); 869 } 870 } 871 synchronized (mLock) { 872 mMhlDevices = inputs; 873 } 874 } 875 876 private List<HdmiDeviceInfo> getMhlDevicesLocked() { 877 return mMhlDevices; 878 } 879 880 // Record class that monitors the event of the caller of being killed. Used to clean up 881 // the listener list and record list accordingly. 882 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { 883 private final IHdmiHotplugEventListener mListener; 884 885 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) { 886 mListener = listener; 887 } 888 889 @Override 890 public void binderDied() { 891 synchronized (mLock) { 892 mHotplugEventListenerRecords.remove(this); 893 mHotplugEventListeners.remove(mListener); 894 } 895 } 896 } 897 898 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient { 899 private final IHdmiDeviceEventListener mListener; 900 901 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) { 902 mListener = listener; 903 } 904 905 @Override 906 public void binderDied() { 907 synchronized (mLock) { 908 mDeviceEventListenerRecords.remove(this); 909 mDeviceEventListeners.remove(mListener); 910 } 911 } 912 } 913 914 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient { 915 private final IHdmiSystemAudioModeChangeListener mListener; 916 917 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) { 918 mListener = listener; 919 } 920 921 @Override 922 public void binderDied() { 923 synchronized (mLock) { 924 mSystemAudioModeChangeListenerRecords.remove(this); 925 mSystemAudioModeChangeListeners.remove(mListener); 926 } 927 } 928 } 929 930 class VendorCommandListenerRecord implements IBinder.DeathRecipient { 931 private final IHdmiVendorCommandListener mListener; 932 private final int mDeviceType; 933 934 public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) { 935 mListener = listener; 936 mDeviceType = deviceType; 937 } 938 939 @Override 940 public void binderDied() { 941 synchronized (mLock) { 942 mVendorCommandListenerRecords.remove(this); 943 } 944 } 945 } 946 947 private class HdmiRecordListenerRecord implements IBinder.DeathRecipient { 948 @Override 949 public void binderDied() { 950 synchronized (mLock) { 951 mRecordListener = null; 952 } 953 } 954 } 955 956 private void enforceAccessPermission() { 957 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); 958 } 959 960 private final class BinderService extends IHdmiControlService.Stub { 961 @Override 962 public int[] getSupportedTypes() { 963 enforceAccessPermission(); 964 // mLocalDevices is an unmodifiable list - no lock necesary. 965 int[] localDevices = new int[mLocalDevices.size()]; 966 for (int i = 0; i < localDevices.length; ++i) { 967 localDevices[i] = mLocalDevices.get(i); 968 } 969 return localDevices; 970 } 971 972 @Override 973 public HdmiDeviceInfo getActiveSource() { 974 HdmiCecLocalDeviceTv tv = tv(); 975 if (tv == null) { 976 Slog.w(TAG, "Local tv device not available"); 977 return null; 978 } 979 ActiveSource activeSource = tv.getActiveSource(); 980 if (activeSource.isValid()) { 981 return new HdmiDeviceInfo(activeSource.logicalAddress, 982 activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID, 983 HdmiDeviceInfo.DEVICE_INACTIVE, 0, ""); 984 } 985 int activePath = tv.getActivePath(); 986 if (activePath != HdmiDeviceInfo.PATH_INVALID) { 987 return new HdmiDeviceInfo(activePath, tv.getActivePortId()); 988 } 989 return null; 990 } 991 992 @Override 993 public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) { 994 enforceAccessPermission(); 995 runOnServiceThread(new Runnable() { 996 @Override 997 public void run() { 998 if (callback == null) { 999 Slog.e(TAG, "Callback cannot be null"); 1000 return; 1001 } 1002 HdmiCecLocalDeviceTv tv = tv(); 1003 if (tv == null) { 1004 Slog.w(TAG, "Local tv device not available"); 1005 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1006 return; 1007 } 1008 tv.deviceSelect(deviceId, callback); 1009 } 1010 }); 1011 } 1012 1013 @Override 1014 public void portSelect(final int portId, final IHdmiControlCallback callback) { 1015 enforceAccessPermission(); 1016 runOnServiceThread(new Runnable() { 1017 @Override 1018 public void run() { 1019 if (callback == null) { 1020 Slog.e(TAG, "Callback cannot be null"); 1021 return; 1022 } 1023 HdmiCecLocalDeviceTv tv = tv(); 1024 if (tv == null) { 1025 Slog.w(TAG, "Local tv device not available"); 1026 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1027 return; 1028 } 1029 tv.doManualPortSwitching(portId, callback); 1030 } 1031 }); 1032 } 1033 1034 @Override 1035 public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) { 1036 enforceAccessPermission(); 1037 runOnServiceThread(new Runnable() { 1038 @Override 1039 public void run() { 1040 if (mMhlController != null) { 1041 HdmiMhlLocalDevice device = mMhlController.getLocalDevice(mActivePortId); 1042 if (device != null) { 1043 device.sendKeyEvent(keyCode, isPressed); 1044 return; 1045 } 1046 } 1047 if (mCecController != null) { 1048 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType); 1049 if (localDevice == null) { 1050 Slog.w(TAG, "Local device not available"); 1051 return; 1052 } 1053 localDevice.sendKeyEvent(keyCode, isPressed); 1054 } 1055 } 1056 }); 1057 } 1058 1059 @Override 1060 public void oneTouchPlay(final IHdmiControlCallback callback) { 1061 enforceAccessPermission(); 1062 runOnServiceThread(new Runnable() { 1063 @Override 1064 public void run() { 1065 HdmiControlService.this.oneTouchPlay(callback); 1066 } 1067 }); 1068 } 1069 1070 @Override 1071 public void queryDisplayStatus(final IHdmiControlCallback callback) { 1072 enforceAccessPermission(); 1073 runOnServiceThread(new Runnable() { 1074 @Override 1075 public void run() { 1076 HdmiControlService.this.queryDisplayStatus(callback); 1077 } 1078 }); 1079 } 1080 1081 @Override 1082 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 1083 enforceAccessPermission(); 1084 runOnServiceThread(new Runnable() { 1085 @Override 1086 public void run() { 1087 HdmiControlService.this.addHotplugEventListener(listener); 1088 } 1089 }); 1090 } 1091 1092 @Override 1093 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) { 1094 enforceAccessPermission(); 1095 runOnServiceThread(new Runnable() { 1096 @Override 1097 public void run() { 1098 HdmiControlService.this.removeHotplugEventListener(listener); 1099 } 1100 }); 1101 } 1102 1103 @Override 1104 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) { 1105 enforceAccessPermission(); 1106 runOnServiceThread(new Runnable() { 1107 @Override 1108 public void run() { 1109 HdmiControlService.this.addDeviceEventListener(listener); 1110 } 1111 }); 1112 } 1113 1114 @Override 1115 public List<HdmiPortInfo> getPortInfo() { 1116 enforceAccessPermission(); 1117 return HdmiControlService.this.getPortInfo(); 1118 } 1119 1120 @Override 1121 public boolean canChangeSystemAudioMode() { 1122 enforceAccessPermission(); 1123 HdmiCecLocalDeviceTv tv = tv(); 1124 if (tv == null) { 1125 return false; 1126 } 1127 return tv.hasSystemAudioDevice(); 1128 } 1129 1130 @Override 1131 public boolean getSystemAudioMode() { 1132 enforceAccessPermission(); 1133 HdmiCecLocalDeviceTv tv = tv(); 1134 if (tv == null) { 1135 return false; 1136 } 1137 return tv.isSystemAudioActivated(); 1138 } 1139 1140 @Override 1141 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) { 1142 enforceAccessPermission(); 1143 runOnServiceThread(new Runnable() { 1144 @Override 1145 public void run() { 1146 HdmiCecLocalDeviceTv tv = tv(); 1147 if (tv == null) { 1148 Slog.w(TAG, "Local tv device not available"); 1149 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1150 return; 1151 } 1152 tv.changeSystemAudioMode(enabled, callback); 1153 } 1154 }); 1155 } 1156 1157 @Override 1158 public void addSystemAudioModeChangeListener( 1159 final IHdmiSystemAudioModeChangeListener listener) { 1160 enforceAccessPermission(); 1161 HdmiControlService.this.addSystemAudioModeChangeListner(listener); 1162 } 1163 1164 @Override 1165 public void removeSystemAudioModeChangeListener( 1166 final IHdmiSystemAudioModeChangeListener listener) { 1167 enforceAccessPermission(); 1168 HdmiControlService.this.removeSystemAudioModeChangeListener(listener); 1169 } 1170 1171 @Override 1172 public void setInputChangeListener(final IHdmiInputChangeListener listener) { 1173 enforceAccessPermission(); 1174 HdmiControlService.this.setInputChangeListener(listener); 1175 } 1176 1177 @Override 1178 public List<HdmiDeviceInfo> getInputDevices() { 1179 enforceAccessPermission(); 1180 // No need to hold the lock for obtaining TV device as the local device instance 1181 // is preserved while the HDMI control is enabled. 1182 HdmiCecLocalDeviceTv tv = tv(); 1183 synchronized (mLock) { 1184 List<HdmiDeviceInfo> cecDevices = (tv == null) 1185 ? Collections.<HdmiDeviceInfo>emptyList() 1186 : tv.getSafeExternalInputsLocked(); 1187 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked()); 1188 } 1189 } 1190 1191 @Override 1192 public void setSystemAudioVolume(final int oldIndex, final int newIndex, 1193 final int maxIndex) { 1194 enforceAccessPermission(); 1195 runOnServiceThread(new Runnable() { 1196 @Override 1197 public void run() { 1198 HdmiCecLocalDeviceTv tv = tv(); 1199 if (tv == null) { 1200 Slog.w(TAG, "Local tv device not available"); 1201 return; 1202 } 1203 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex); 1204 } 1205 }); 1206 } 1207 1208 @Override 1209 public void setSystemAudioMute(final boolean mute) { 1210 enforceAccessPermission(); 1211 runOnServiceThread(new Runnable() { 1212 @Override 1213 public void run() { 1214 HdmiCecLocalDeviceTv tv = tv(); 1215 if (tv == null) { 1216 Slog.w(TAG, "Local tv device not available"); 1217 return; 1218 } 1219 tv.changeMute(mute); 1220 } 1221 }); 1222 } 1223 1224 @Override 1225 public void setArcMode(final boolean enabled) { 1226 enforceAccessPermission(); 1227 runOnServiceThread(new Runnable() { 1228 @Override 1229 public void run() { 1230 HdmiCecLocalDeviceTv tv = tv(); 1231 if (tv == null) { 1232 Slog.w(TAG, "Local tv device not available to change arc mode."); 1233 return; 1234 } 1235 } 1236 }); 1237 } 1238 1239 @Override 1240 public void setProhibitMode(final boolean enabled) { 1241 enforceAccessPermission(); 1242 if (!isTvDevice()) { 1243 return; 1244 } 1245 HdmiControlService.this.setProhibitMode(enabled); 1246 } 1247 1248 @Override 1249 public void addVendorCommandListener(final IHdmiVendorCommandListener listener, 1250 final int deviceType) { 1251 enforceAccessPermission(); 1252 runOnServiceThread(new Runnable() { 1253 @Override 1254 public void run() { 1255 HdmiControlService.this.addVendorCommandListener(listener, deviceType); 1256 } 1257 }); 1258 } 1259 1260 @Override 1261 public void sendVendorCommand(final int deviceType, final int targetAddress, 1262 final byte[] params, final boolean hasVendorId) { 1263 enforceAccessPermission(); 1264 runOnServiceThread(new Runnable() { 1265 @Override 1266 public void run() { 1267 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType); 1268 if (device == null) { 1269 Slog.w(TAG, "Local device not available"); 1270 return; 1271 } 1272 if (hasVendorId) { 1273 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId( 1274 device.getDeviceInfo().getLogicalAddress(), targetAddress, 1275 getVendorId(), params)); 1276 } else { 1277 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand( 1278 device.getDeviceInfo().getLogicalAddress(), targetAddress, params)); 1279 } 1280 } 1281 }); 1282 } 1283 1284 @Override 1285 public void setHdmiRecordListener(IHdmiRecordListener listener) { 1286 HdmiControlService.this.setHdmiRecordListener(listener); 1287 } 1288 1289 @Override 1290 public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) { 1291 runOnServiceThread(new Runnable() { 1292 @Override 1293 public void run() { 1294 if (!isTvDevice()) { 1295 Slog.w(TAG, "No TV is available."); 1296 return; 1297 } 1298 tv().startOneTouchRecord(recorderAddress, recordSource); 1299 } 1300 }); 1301 } 1302 1303 @Override 1304 public void stopOneTouchRecord(final int recorderAddress) { 1305 runOnServiceThread(new Runnable() { 1306 @Override 1307 public void run() { 1308 if (!isTvDevice()) { 1309 Slog.w(TAG, "No TV is available."); 1310 return; 1311 } 1312 tv().stopOneTouchRecord(recorderAddress); 1313 } 1314 }); 1315 } 1316 1317 @Override 1318 public void startTimerRecording(final int recorderAddress, final int sourceType, 1319 final byte[] recordSource) { 1320 runOnServiceThread(new Runnable() { 1321 @Override 1322 public void run() { 1323 if (!isTvDevice()) { 1324 Slog.w(TAG, "No TV is available."); 1325 return; 1326 } 1327 tv().startTimerRecording(recorderAddress, sourceType, recordSource); 1328 } 1329 }); 1330 } 1331 1332 @Override 1333 public void clearTimerRecording(final int recorderAddress, final int sourceType, 1334 final byte[] recordSource) { 1335 runOnServiceThread(new Runnable() { 1336 @Override 1337 public void run() { 1338 if (!isTvDevice()) { 1339 Slog.w(TAG, "No TV is available."); 1340 return; 1341 } 1342 tv().clearTimerRecording(recorderAddress, sourceType, recordSource); 1343 } 1344 }); 1345 } 1346 } 1347 1348 @ServiceThreadOnly 1349 private void oneTouchPlay(final IHdmiControlCallback callback) { 1350 assertRunOnServiceThread(); 1351 HdmiCecLocalDevicePlayback source = playback(); 1352 if (source == null) { 1353 Slog.w(TAG, "Local playback device not available"); 1354 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1355 return; 1356 } 1357 source.oneTouchPlay(callback); 1358 } 1359 1360 @ServiceThreadOnly 1361 private void queryDisplayStatus(final IHdmiControlCallback callback) { 1362 assertRunOnServiceThread(); 1363 HdmiCecLocalDevicePlayback source = playback(); 1364 if (source == null) { 1365 Slog.w(TAG, "Local playback device not available"); 1366 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1367 return; 1368 } 1369 source.queryDisplayStatus(callback); 1370 } 1371 1372 private void addHotplugEventListener(IHdmiHotplugEventListener listener) { 1373 HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); 1374 try { 1375 listener.asBinder().linkToDeath(record, 0); 1376 } catch (RemoteException e) { 1377 Slog.w(TAG, "Listener already died"); 1378 return; 1379 } 1380 synchronized (mLock) { 1381 mHotplugEventListenerRecords.add(record); 1382 mHotplugEventListeners.add(listener); 1383 } 1384 } 1385 1386 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) { 1387 synchronized (mLock) { 1388 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 1389 if (record.mListener.asBinder() == listener.asBinder()) { 1390 listener.asBinder().unlinkToDeath(record, 0); 1391 mHotplugEventListenerRecords.remove(record); 1392 break; 1393 } 1394 } 1395 mHotplugEventListeners.remove(listener); 1396 } 1397 } 1398 1399 private void addDeviceEventListener(IHdmiDeviceEventListener listener) { 1400 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener); 1401 try { 1402 listener.asBinder().linkToDeath(record, 0); 1403 } catch (RemoteException e) { 1404 Slog.w(TAG, "Listener already died"); 1405 return; 1406 } 1407 synchronized (mLock) { 1408 mDeviceEventListeners.add(listener); 1409 mDeviceEventListenerRecords.add(record); 1410 } 1411 } 1412 1413 void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) { 1414 synchronized (mLock) { 1415 for (IHdmiDeviceEventListener listener : mDeviceEventListeners) { 1416 try { 1417 listener.onStatusChanged(device, status); 1418 } catch (RemoteException e) { 1419 Slog.e(TAG, "Failed to report device event:" + e); 1420 } 1421 } 1422 } 1423 } 1424 1425 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) { 1426 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord( 1427 listener); 1428 try { 1429 listener.asBinder().linkToDeath(record, 0); 1430 } catch (RemoteException e) { 1431 Slog.w(TAG, "Listener already died"); 1432 return; 1433 } 1434 synchronized (mLock) { 1435 mSystemAudioModeChangeListeners.add(listener); 1436 mSystemAudioModeChangeListenerRecords.add(record); 1437 } 1438 } 1439 1440 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) { 1441 synchronized (mLock) { 1442 for (SystemAudioModeChangeListenerRecord record : 1443 mSystemAudioModeChangeListenerRecords) { 1444 if (record.mListener.asBinder() == listener) { 1445 listener.asBinder().unlinkToDeath(record, 0); 1446 mSystemAudioModeChangeListenerRecords.remove(record); 1447 break; 1448 } 1449 } 1450 mSystemAudioModeChangeListeners.remove(listener); 1451 } 1452 } 1453 1454 private final class InputChangeListenerRecord implements IBinder.DeathRecipient { 1455 @Override 1456 public void binderDied() { 1457 synchronized (mLock) { 1458 mInputChangeListener = null; 1459 } 1460 } 1461 } 1462 1463 private void setInputChangeListener(IHdmiInputChangeListener listener) { 1464 synchronized (mLock) { 1465 mInputChangeListenerRecord = new InputChangeListenerRecord(); 1466 try { 1467 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0); 1468 } catch (RemoteException e) { 1469 Slog.w(TAG, "Listener already died"); 1470 return; 1471 } 1472 mInputChangeListener = listener; 1473 } 1474 } 1475 1476 void invokeInputChangeListener(HdmiDeviceInfo info) { 1477 synchronized (mLock) { 1478 if (mInputChangeListener != null) { 1479 try { 1480 mInputChangeListener.onChanged(info); 1481 } catch (RemoteException e) { 1482 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e); 1483 } 1484 } 1485 } 1486 } 1487 1488 private void setHdmiRecordListener(IHdmiRecordListener listener) { 1489 synchronized (mLock) { 1490 mRecordListenerRecord = new HdmiRecordListenerRecord(); 1491 try { 1492 listener.asBinder().linkToDeath(mRecordListenerRecord, 0); 1493 } catch (RemoteException e) { 1494 Slog.w(TAG, "Listener already died.", e); 1495 } 1496 mRecordListener = listener; 1497 } 1498 } 1499 1500 byte[] invokeRecordRequestListener(int recorderAddress) { 1501 synchronized (mLock) { 1502 if (mRecordListener != null) { 1503 try { 1504 return mRecordListener.getOneTouchRecordSource(recorderAddress); 1505 } catch (RemoteException e) { 1506 Slog.w(TAG, "Failed to start record.", e); 1507 } 1508 } 1509 return EmptyArray.BYTE; 1510 } 1511 } 1512 1513 void invokeOneTouchRecordResult(int result) { 1514 synchronized (mLock) { 1515 if (mRecordListener != null) { 1516 try { 1517 mRecordListener.onOneTouchRecordResult(result); 1518 } catch (RemoteException e) { 1519 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e); 1520 } 1521 } 1522 } 1523 } 1524 1525 void invokeTimerRecordingResult(int result) { 1526 synchronized (mLock) { 1527 if (mRecordListener != null) { 1528 try { 1529 mRecordListener.onTimerRecordingResult(result); 1530 } catch (RemoteException e) { 1531 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e); 1532 } 1533 } 1534 } 1535 } 1536 1537 void invokeClearTimerRecordingResult(int result) { 1538 synchronized (mLock) { 1539 if (mRecordListener != null) { 1540 try { 1541 mRecordListener.onClearTimerRecordingResult(result); 1542 } catch (RemoteException e) { 1543 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e); 1544 } 1545 } 1546 } 1547 } 1548 1549 private void invokeCallback(IHdmiControlCallback callback, int result) { 1550 try { 1551 callback.onComplete(result); 1552 } catch (RemoteException e) { 1553 Slog.e(TAG, "Invoking callback failed:" + e); 1554 } 1555 } 1556 1557 private void invokeSystemAudioModeChange(IHdmiSystemAudioModeChangeListener listener, 1558 boolean enabled) { 1559 try { 1560 listener.onStatusChanged(enabled); 1561 } catch (RemoteException e) { 1562 Slog.e(TAG, "Invoking callback failed:" + e); 1563 } 1564 } 1565 1566 private void announceHotplugEvent(int portId, boolean connected) { 1567 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected); 1568 synchronized (mLock) { 1569 for (IHdmiHotplugEventListener listener : mHotplugEventListeners) { 1570 invokeHotplugEventListenerLocked(listener, event); 1571 } 1572 } 1573 } 1574 1575 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener, 1576 HdmiHotplugEvent event) { 1577 try { 1578 listener.onReceived(event); 1579 } catch (RemoteException e) { 1580 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e); 1581 } 1582 } 1583 1584 private HdmiCecLocalDeviceTv tv() { 1585 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV); 1586 } 1587 1588 boolean isTvDevice() { 1589 return tv() != null; 1590 } 1591 1592 private HdmiCecLocalDevicePlayback playback() { 1593 return (HdmiCecLocalDevicePlayback) 1594 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK); 1595 } 1596 1597 AudioManager getAudioManager() { 1598 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); 1599 } 1600 1601 boolean isControlEnabled() { 1602 synchronized (mLock) { 1603 return mHdmiControlEnabled; 1604 } 1605 } 1606 1607 int getPowerStatus() { 1608 return mPowerStatus; 1609 } 1610 1611 boolean isPowerOnOrTransient() { 1612 return mPowerStatus == HdmiControlManager.POWER_STATUS_ON 1613 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; 1614 } 1615 1616 boolean isPowerStandbyOrTransient() { 1617 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY 1618 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY; 1619 } 1620 1621 boolean isPowerStandby() { 1622 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY; 1623 } 1624 1625 @ServiceThreadOnly 1626 void wakeUp() { 1627 assertRunOnServiceThread(); 1628 mWakeUpMessageReceived = true; 1629 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); 1630 pm.wakeUp(SystemClock.uptimeMillis()); 1631 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets 1632 // the intent, the sequence will continue at onWakeUp(). 1633 } 1634 1635 @ServiceThreadOnly 1636 void standby() { 1637 assertRunOnServiceThread(); 1638 mStandbyMessageReceived = true; 1639 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); 1640 pm.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0); 1641 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets 1642 // the intent, the sequence will continue at onStandby(). 1643 } 1644 1645 void nap() { 1646 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); 1647 pm.nap(SystemClock.uptimeMillis()); 1648 } 1649 1650 @ServiceThreadOnly 1651 private void onWakeUp() { 1652 assertRunOnServiceThread(); 1653 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; 1654 if (mCecController != null) { 1655 if (mHdmiControlEnabled) { 1656 int startReason = INITIATED_BY_SCREEN_ON; 1657 if (mWakeUpMessageReceived) { 1658 startReason = INITIATED_BY_WAKE_UP_MESSAGE; 1659 } 1660 initializeCec(startReason); 1661 } 1662 } else { 1663 Slog.i(TAG, "Device does not support HDMI-CEC."); 1664 } 1665 // TODO: Initialize MHL local devices. 1666 } 1667 1668 @ServiceThreadOnly 1669 private void onStandby() { 1670 assertRunOnServiceThread(); 1671 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY; 1672 1673 final List<HdmiCecLocalDevice> devices = getAllLocalDevices(); 1674 disableDevices(new PendingActionClearedCallback() { 1675 @Override 1676 public void onCleared(HdmiCecLocalDevice device) { 1677 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType); 1678 devices.remove(device); 1679 if (devices.isEmpty()) { 1680 onStandbyCompleted(); 1681 // We will not clear local devices here, since some OEM/SOC will keep passing 1682 // the received packets until the application processor enters to the sleep 1683 // actually. 1684 } 1685 } 1686 }); 1687 } 1688 1689 private void disableDevices(PendingActionClearedCallback callback) { 1690 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 1691 device.disableDevice(mStandbyMessageReceived, callback); 1692 } 1693 if (isTvDevice()) { 1694 unregisterSettingsObserver(); 1695 } 1696 } 1697 1698 @ServiceThreadOnly 1699 private void clearLocalDevices() { 1700 assertRunOnServiceThread(); 1701 if (mCecController == null) { 1702 return; 1703 } 1704 mCecController.clearLogicalAddress(); 1705 mCecController.clearLocalDevices(); 1706 } 1707 1708 @ServiceThreadOnly 1709 private void onStandbyCompleted() { 1710 assertRunOnServiceThread(); 1711 Slog.v(TAG, "onStandbyCompleted"); 1712 1713 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) { 1714 return; 1715 } 1716 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; 1717 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 1718 device.onStandby(mStandbyMessageReceived); 1719 } 1720 mStandbyMessageReceived = false; 1721 mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, DISABLED); 1722 } 1723 1724 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) { 1725 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType); 1726 try { 1727 listener.asBinder().linkToDeath(record, 0); 1728 } catch (RemoteException e) { 1729 Slog.w(TAG, "Listener already died"); 1730 return; 1731 } 1732 synchronized (mLock) { 1733 mVendorCommandListenerRecords.add(record); 1734 } 1735 } 1736 1737 void invokeVendorCommandListeners(int deviceType, int srcAddress, byte[] params, 1738 boolean hasVendorId) { 1739 synchronized (mLock) { 1740 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) { 1741 if (record.mDeviceType != deviceType) { 1742 continue; 1743 } 1744 try { 1745 record.mListener.onReceived(srcAddress, params, hasVendorId); 1746 } catch (RemoteException e) { 1747 Slog.e(TAG, "Failed to notify vendor command reception", e); 1748 } 1749 } 1750 } 1751 } 1752 1753 boolean isProhibitMode() { 1754 synchronized (mLock) { 1755 return mProhibitMode; 1756 } 1757 } 1758 1759 void setProhibitMode(boolean enabled) { 1760 synchronized (mLock) { 1761 mProhibitMode = enabled; 1762 } 1763 } 1764 1765 @ServiceThreadOnly 1766 void setOption(int key, int value) { 1767 assertRunOnServiceThread(); 1768 mCecController.setOption(key, value); 1769 } 1770 1771 @ServiceThreadOnly 1772 void setControlEnabled(boolean enabled) { 1773 assertRunOnServiceThread(); 1774 1775 int value = toInt(enabled); 1776 mCecController.setOption(OPTION_CEC_ENABLE, value); 1777 if (mMhlController != null) { 1778 mMhlController.setOption(OPTION_MHL_ENABLE, value); 1779 } 1780 1781 synchronized (mLock) { 1782 mHdmiControlEnabled = enabled; 1783 } 1784 1785 if (enabled) { 1786 initializeCec(INITIATED_BY_ENABLE_CEC); 1787 } else { 1788 disableDevices(new PendingActionClearedCallback() { 1789 @Override 1790 public void onCleared(HdmiCecLocalDevice device) { 1791 assertRunOnServiceThread(); 1792 clearLocalDevices(); 1793 } 1794 }); 1795 } 1796 } 1797 1798 @ServiceThreadOnly 1799 void setActivePortId(int portId) { 1800 assertRunOnServiceThread(); 1801 mActivePortId = portId; 1802 } 1803 1804 void setMhlInputChangeEnabled(boolean enabled) { 1805 if (mMhlController != null) { 1806 mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled)); 1807 } 1808 1809 synchronized (mLock) { 1810 mMhlInputChangeEnabled = enabled; 1811 } 1812 } 1813 1814 boolean isMhlInputChangeEnabled() { 1815 synchronized (mLock) { 1816 return mMhlInputChangeEnabled; 1817 } 1818 } 1819} 1820