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