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