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