HdmiControlService.java revision a466929979a92a578d4ba00093fefa57cfb982b4
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 android.annotation.Nullable; 20import android.content.Context; 21import android.hardware.hdmi.HdmiCec; 22import android.hardware.hdmi.HdmiCecDeviceInfo; 23import android.hardware.hdmi.HdmiCecMessage; 24import android.hardware.hdmi.IHdmiControlCallback; 25import android.hardware.hdmi.IHdmiControlService; 26import android.hardware.hdmi.IHdmiHotplugEventListener; 27import android.os.Handler; 28import android.os.HandlerThread; 29import android.os.IBinder; 30import android.os.Looper; 31import android.os.RemoteException; 32import android.util.Slog; 33import android.util.SparseArray; 34import android.util.SparseIntArray; 35 36import com.android.internal.annotations.GuardedBy; 37import com.android.server.SystemService; 38import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback; 39import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; 40 41import java.util.ArrayList; 42import java.util.Iterator; 43import java.util.LinkedList; 44import java.util.List; 45import java.util.Locale; 46 47/** 48 * Provides a service for sending and processing HDMI control messages, 49 * HDMI-CEC and MHL control command, and providing the information on both standard. 50 */ 51public final class HdmiControlService extends SystemService { 52 private static final String TAG = "HdmiControlService"; 53 54 // TODO: Rename the permission to HDMI_CONTROL. 55 private static final String PERMISSION = "android.permission.HDMI_CEC"; 56 57 static final int SEND_RESULT_SUCCESS = 0; 58 static final int SEND_RESULT_NAK = -1; 59 static final int SEND_RESULT_FAILURE = -2; 60 61 static final int POLL_STRATEGY_MASK = 0x3; // first and second bit. 62 static final int POLL_STRATEGY_REMOTES_DEVICES = 0x1; 63 static final int POLL_STRATEGY_SYSTEM_AUDIO = 0x2; 64 65 static final int POLL_ITERATION_STRATEGY_MASK = 0x30000; // first and second bit. 66 static final int POLL_ITERATION_IN_ORDER = 0x10000; 67 static final int POLL_ITERATION_REVERSE_ORDER = 0x20000; 68 69 /** 70 * Interface to report send result. 71 */ 72 interface SendMessageCallback { 73 /** 74 * Called when {@link HdmiControlService#sendCecCommand} is completed. 75 * 76 * @param error result of send request. 77 * @see {@link #SEND_RESULT_SUCCESS} 78 * @see {@link #SEND_RESULT_NAK} 79 * @see {@link #SEND_RESULT_FAILURE} 80 */ 81 void onSendCompleted(int error); 82 } 83 84 /** 85 * Interface to get a list of available logical devices. 86 */ 87 interface DevicePollingCallback { 88 /** 89 * Called when device polling is finished. 90 * 91 * @param ackedAddress a list of logical addresses of available devices 92 */ 93 void onPollingFinished(List<Integer> ackedAddress); 94 } 95 96 // A thread to handle synchronous IO of CEC and MHL control service. 97 // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms) 98 // and sparse call it shares a thread to handle IO operations. 99 private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread"); 100 101 // A collection of FeatureAction. 102 // Note that access to this collection should happen in service thread. 103 private final LinkedList<FeatureAction> mActions = new LinkedList<>(); 104 105 // Used to synchronize the access to the service. 106 private final Object mLock = new Object(); 107 108 // Type of logical devices hosted in the system. 109 @GuardedBy("mLock") 110 private final int[] mLocalDevices; 111 112 // List of listeners registered by callers that want to get notified of 113 // hotplug events. 114 private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>(); 115 116 // List of records for hotplug event listener to handle the the caller killed in action. 117 private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords = 118 new ArrayList<>(); 119 120 private final HdmiCecMessageCache mCecMessageCache = new HdmiCecMessageCache(); 121 122 @Nullable 123 private HdmiCecController mCecController; 124 125 @Nullable 126 private HdmiMhlController mMhlController; 127 128 @GuardedBy("mLock") 129 // Whether ARC is "enabled" or not. 130 // TODO: it may need to hold lock if it's accessed from others. 131 private boolean mArcStatusEnabled = false; 132 133 @GuardedBy("mLock") 134 // Whether SystemAudioMode is "On" or not. 135 private boolean mSystemAudioMode; 136 137 // Handler running on service thread. It's used to run a task in service thread. 138 private final Handler mHandler = new Handler(); 139 140 public HdmiControlService(Context context) { 141 super(context); 142 mLocalDevices = getContext().getResources().getIntArray( 143 com.android.internal.R.array.config_hdmiCecLogicalDeviceType); 144 } 145 146 @Override 147 public void onStart() { 148 mIoThread.start(); 149 mCecController = HdmiCecController.create(this); 150 151 if (mCecController != null) { 152 initializeLocalDevices(mLocalDevices); 153 } else { 154 Slog.i(TAG, "Device does not support HDMI-CEC."); 155 } 156 157 mMhlController = HdmiMhlController.create(this); 158 if (mMhlController == null) { 159 Slog.i(TAG, "Device does not support MHL-control."); 160 } 161 162 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); 163 164 // TODO: Read the preference for SystemAudioMode and initialize mSystemAudioMode and 165 // start to monitor the preference value and invoke SystemAudioActionFromTv if needed. 166 } 167 168 private void initializeLocalDevices(final int[] deviceTypes) { 169 // A container for [Logical Address, Local device info]. 170 final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>(); 171 final SparseIntArray finished = new SparseIntArray(); 172 for (int type : deviceTypes) { 173 final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type); 174 localDevice.init(); 175 mCecController.allocateLogicalAddress(type, 176 localDevice.getPreferredAddress(), new AllocateAddressCallback() { 177 @Override 178 public void onAllocated(int deviceType, int logicalAddress) { 179 if (logicalAddress == HdmiCec.ADDR_UNREGISTERED) { 180 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]"); 181 } else { 182 HdmiCecDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType); 183 localDevice.setDeviceInfo(deviceInfo); 184 mCecController.addLocalDevice(deviceType, localDevice); 185 mCecController.addLogicalAddress(logicalAddress); 186 devices.append(logicalAddress, localDevice); 187 } 188 finished.append(deviceType, logicalAddress); 189 190 // Once finish address allocation for all devices, notify 191 // it to each device. 192 if (deviceTypes.length == finished.size()) { 193 notifyAddressAllocated(devices); 194 } 195 } 196 }); 197 } 198 } 199 200 private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices) { 201 for (int i = 0; i < devices.size(); ++i) { 202 int address = devices.keyAt(i); 203 HdmiCecLocalDevice device = devices.valueAt(i); 204 device.onAddressAllocated(address); 205 } 206 } 207 208 /** 209 * Returns {@link Looper} for IO operation. 210 * 211 * <p>Declared as package-private. 212 */ 213 Looper getIoLooper() { 214 return mIoThread.getLooper(); 215 } 216 217 /** 218 * Returns {@link Looper} of main thread. Use this {@link Looper} instance 219 * for tasks that are running on main service thread. 220 * 221 * <p>Declared as package-private. 222 */ 223 Looper getServiceLooper() { 224 return mHandler.getLooper(); 225 } 226 227 /** 228 * Returns physical address of the device. 229 */ 230 int getPhysicalAddress() { 231 return mCecController.getPhysicalAddress(); 232 } 233 234 /** 235 * Returns vendor id of CEC service. 236 */ 237 int getVendorId() { 238 return mCecController.getVendorId(); 239 } 240 241 /** 242 * Returns a list of {@link HdmiCecDeviceInfo}. 243 * 244 * @param includeLocalDevice whether to include local devices 245 */ 246 List<HdmiCecDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) { 247 assertRunOnServiceThread(); 248 return mCecController.getDeviceInfoList(includeLocalDevice); 249 } 250 251 /** 252 * Add and start a new {@link FeatureAction} to the action queue. 253 * 254 * @param action {@link FeatureAction} to add and start 255 */ 256 void addAndStartAction(final FeatureAction action) { 257 // TODO: may need to check the number of stale actions. 258 runOnServiceThread(new Runnable() { 259 @Override 260 public void run() { 261 mActions.add(action); 262 action.start(); 263 } 264 }); 265 } 266 267 void setSystemAudioMode(boolean on) { 268 synchronized (mLock) { 269 mSystemAudioMode = on; 270 } 271 } 272 273 boolean getSystemAudioMode() { 274 synchronized (mLock) { 275 return mSystemAudioMode; 276 } 277 } 278 279 // See if we have an action of a given type in progress. 280 private <T extends FeatureAction> boolean hasAction(final Class<T> clazz) { 281 for (FeatureAction action : mActions) { 282 if (action.getClass().equals(clazz)) { 283 return true; 284 } 285 } 286 return false; 287 } 288 289 /** 290 * Remove the given {@link FeatureAction} object from the action queue. 291 * 292 * @param action {@link FeatureAction} to remove 293 */ 294 void removeAction(final FeatureAction action) { 295 assertRunOnServiceThread(); 296 mActions.remove(action); 297 } 298 299 // Remove all actions matched with the given Class type. 300 private <T extends FeatureAction> void removeAction(final Class<T> clazz) { 301 removeActionExcept(clazz, null); 302 } 303 304 // Remove all actions matched with the given Class type besides |exception|. 305 <T extends FeatureAction> void removeActionExcept(final Class<T> clazz, 306 final FeatureAction exception) { 307 assertRunOnServiceThread(); 308 Iterator<FeatureAction> iter = mActions.iterator(); 309 while (iter.hasNext()) { 310 FeatureAction action = iter.next(); 311 if (action != exception && action.getClass().equals(clazz)) { 312 action.clear(); 313 mActions.remove(action); 314 } 315 } 316 } 317 318 private void runOnServiceThread(Runnable runnable) { 319 mHandler.post(runnable); 320 } 321 322 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) { 323 mHandler.postAtFrontOfQueue(runnable); 324 } 325 326 private void assertRunOnServiceThread() { 327 if (Looper.myLooper() != mHandler.getLooper()) { 328 throw new IllegalStateException("Should run on service thread."); 329 } 330 } 331 332 /** 333 * Change ARC status into the given {@code enabled} status. 334 * 335 * @return {@code true} if ARC was in "Enabled" status 336 */ 337 boolean setArcStatus(boolean enabled) { 338 synchronized (mLock) { 339 boolean oldStatus = mArcStatusEnabled; 340 // 1. Enable/disable ARC circuit. 341 // TODO: call set_audio_return_channel of hal interface. 342 343 // 2. Update arc status; 344 mArcStatusEnabled = enabled; 345 return oldStatus; 346 } 347 } 348 349 /** 350 * Transmit a CEC command to CEC bus. 351 * 352 * @param command CEC command to send out 353 * @param callback interface used to the result of send command 354 */ 355 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) { 356 mCecController.sendCommand(command, callback); 357 } 358 359 void sendCecCommand(HdmiCecMessage command) { 360 mCecController.sendCommand(command, null); 361 } 362 363 boolean handleCecCommand(HdmiCecMessage message) { 364 // Cache incoming message. Note that it caches only white-listed one. 365 mCecMessageCache.cacheMessage(message); 366 367 // Commands that queries system information replies directly instead 368 // of creating FeatureAction because they are state-less. 369 switch (message.getOpcode()) { 370 case HdmiCec.MESSAGE_GET_MENU_LANGUAGE: 371 handleGetMenuLanguage(message); 372 return true; 373 case HdmiCec.MESSAGE_GIVE_OSD_NAME: 374 handleGiveOsdName(message); 375 return true; 376 case HdmiCec.MESSAGE_GIVE_PHYSICAL_ADDRESS: 377 handleGivePhysicalAddress(message); 378 return true; 379 case HdmiCec.MESSAGE_GIVE_DEVICE_VENDOR_ID: 380 handleGiveDeviceVendorId(message); 381 return true; 382 case HdmiCec.MESSAGE_GET_CEC_VERSION: 383 handleGetCecVersion(message); 384 return true; 385 case HdmiCec.MESSAGE_INITIATE_ARC: 386 handleInitiateArc(message); 387 return true; 388 case HdmiCec.MESSAGE_TERMINATE_ARC: 389 handleTerminateArc(message); 390 return true; 391 case HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS: 392 handleReportPhysicalAddress(message); 393 return true; 394 case HdmiCec.MESSAGE_SET_SYSTEM_AUDIO_MODE: 395 handleSetSystemAudioMode(message); 396 return true; 397 case HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_STATUS: 398 handleSystemAudioModeStatus(message); 399 return true; 400 default: 401 return dispatchMessageToAction(message); 402 } 403 } 404 405 /** 406 * Called when a new hotplug event is issued. 407 * 408 * @param portNo hdmi port number where hot plug event issued. 409 * @param connected whether to be plugged in or not 410 */ 411 void onHotplug(int portNo, boolean connected) { 412 // TODO: Start "RequestArcInitiationAction" if ARC port. 413 } 414 415 /** 416 * Poll all remote devices. It sends <Polling Message> to all remote 417 * devices. 418 * 419 * @param callback an interface used to get a list of all remote devices' address 420 * @param pickStrategy strategy how to pick polling candidates 421 * @param retryCount the number of retry used to send polling message to remote devices 422 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value 423 */ 424 void pollDevices(DevicePollingCallback callback, int pickStrategy, int retryCount) { 425 mCecController.pollDevices(callback, checkPollStrategy(pickStrategy), retryCount); 426 } 427 428 private int checkPollStrategy(int pickStrategy) { 429 int strategy = pickStrategy & POLL_STRATEGY_MASK; 430 if (strategy == 0) { 431 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy); 432 } 433 int iterationStrategy = pickStrategy & POLL_ITERATION_STRATEGY_MASK; 434 if (iterationStrategy == 0) { 435 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy); 436 } 437 return strategy | iterationStrategy; 438 } 439 440 /** 441 * Launch device discovery sequence. It starts with clearing the existing device info list. 442 * Note that it assumes that logical address of all local devices is already allocated. 443 * 444 * @param sourceAddress a logical address of tv 445 */ 446 void launchDeviceDiscovery(final int sourceAddress) { 447 // At first, clear all existing device infos. 448 mCecController.clearDeviceInfoList(); 449 mCecMessageCache.flushAll(); 450 451 // TODO: check whether TV is one of local devices. 452 DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, sourceAddress, 453 new DeviceDiscoveryCallback() { 454 @Override 455 public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) { 456 for (HdmiCecDeviceInfo info : deviceInfos) { 457 addCecDevice(info); 458 } 459 460 // Add device info of all local devices. 461 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 462 addCecDevice(device.getDeviceInfo()); 463 } 464 465 addAndStartAction(new HotplugDetectionAction(HdmiControlService.this, 466 sourceAddress)); 467 } 468 }); 469 addAndStartAction(action); 470 } 471 472 private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) { 473 // TODO: get device name read from system configuration. 474 String displayName = HdmiCec.getDefaultDeviceName(logicalAddress); 475 return new HdmiCecDeviceInfo(logicalAddress, 476 getPhysicalAddress(), deviceType, getVendorId(), displayName); 477 } 478 479 private void handleReportPhysicalAddress(HdmiCecMessage message) { 480 // At first, try to consume it. 481 if (dispatchMessageToAction(message)) { 482 return; 483 } 484 485 // Ignore if [Device Discovery Action] is going on. 486 if (hasAction(DeviceDiscoveryAction.class)) { 487 Slog.i(TAG, "Ignore unrecognizable <Report Physical Address> " 488 + "because Device Discovery Action is on-going:" + message); 489 return; 490 } 491 492 // TODO: start new device action. 493 } 494 495 private void handleInitiateArc(HdmiCecMessage message){ 496 // In case where <Initiate Arc> is started by <Request ARC Initiation> 497 // need to clean up RequestArcInitiationAction. 498 removeAction(RequestArcInitiationAction.class); 499 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 500 message.getDestination(), message.getSource(), true); 501 addAndStartAction(action); 502 } 503 504 private void handleTerminateArc(HdmiCecMessage message) { 505 // In case where <Terminate Arc> is started by <Request ARC Termination> 506 // need to clean up RequestArcInitiationAction. 507 // TODO: check conditions of power status by calling is_connected api 508 // to be added soon. 509 removeAction(RequestArcTerminationAction.class); 510 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 511 message.getDestination(), message.getSource(), false); 512 addAndStartAction(action); 513 } 514 515 private void handleGetCecVersion(HdmiCecMessage message) { 516 int version = mCecController.getVersion(); 517 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(), 518 message.getSource(), 519 version); 520 sendCecCommand(cecMessage); 521 } 522 523 private void handleGiveDeviceVendorId(HdmiCecMessage message) { 524 int vendorId = mCecController.getVendorId(); 525 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand( 526 message.getDestination(), vendorId); 527 sendCecCommand(cecMessage); 528 } 529 530 private void handleGivePhysicalAddress(HdmiCecMessage message) { 531 int physicalAddress = mCecController.getPhysicalAddress(); 532 int deviceType = HdmiCec.getTypeFromAddress(message.getDestination()); 533 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( 534 message.getDestination(), physicalAddress, deviceType); 535 sendCecCommand(cecMessage); 536 } 537 538 private void handleGiveOsdName(HdmiCecMessage message) { 539 // TODO: read device name from settings or property. 540 String name = HdmiCec.getDefaultDeviceName(message.getDestination()); 541 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand( 542 message.getDestination(), message.getSource(), name); 543 if (cecMessage != null) { 544 sendCecCommand(cecMessage); 545 } else { 546 Slog.w(TAG, "Failed to build <Get Osd Name>:" + name); 547 } 548 } 549 550 private void handleGetMenuLanguage(HdmiCecMessage message) { 551 // Only 0 (TV), 14 (specific use) can answer. 552 if (message.getDestination() != HdmiCec.ADDR_TV 553 && message.getDestination() != HdmiCec.ADDR_SPECIFIC_USE) { 554 Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString()); 555 sendCecCommand( 556 HdmiCecMessageBuilder.buildFeatureAbortCommand(message.getDestination(), 557 message.getSource(), HdmiCec.MESSAGE_GET_MENU_LANGUAGE, 558 HdmiConstants.ABORT_UNRECOGNIZED_MODE)); 559 return; 560 } 561 562 HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand( 563 message.getDestination(), 564 Locale.getDefault().getISO3Language()); 565 // TODO: figure out how to handle failed to get language code. 566 if (command != null) { 567 sendCecCommand(command); 568 } else { 569 Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString()); 570 } 571 } 572 573 private boolean dispatchMessageToAction(HdmiCecMessage message) { 574 for (FeatureAction action : mActions) { 575 if (action.processCommand(message)) { 576 return true; 577 } 578 } 579 Slog.w(TAG, "Unsupported cec command:" + message); 580 return false; 581 } 582 583 private void handleSetSystemAudioMode(HdmiCecMessage message) { 584 if (dispatchMessageToAction(message) || !isMessageForSystemAudio(message)) { 585 return; 586 } 587 SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this, 588 message.getDestination(), message.getSource(), 589 HdmiUtils.parseCommandParamSystemAudioStatus(message)); 590 addAndStartAction(action); 591 } 592 593 private void handleSystemAudioModeStatus(HdmiCecMessage message) { 594 if (!isMessageForSystemAudio(message)) { 595 return; 596 } 597 setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message)); 598 } 599 600 private boolean isMessageForSystemAudio(HdmiCecMessage message) { 601 if (message.getSource() != HdmiCec.ADDR_AUDIO_SYSTEM 602 || message.getDestination() != HdmiCec.ADDR_TV 603 || getAvrDeviceInfo() == null) { 604 Slog.w(TAG, "Skip abnormal CecMessage: " + message); 605 return false; 606 } 607 return true; 608 } 609 610 // Record class that monitors the event of the caller of being killed. Used to clean up 611 // the listener list and record list accordingly. 612 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { 613 private final IHdmiHotplugEventListener mListener; 614 615 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) { 616 mListener = listener; 617 } 618 619 @Override 620 public void binderDied() { 621 synchronized (mLock) { 622 mHotplugEventListenerRecords.remove(this); 623 mHotplugEventListeners.remove(mListener); 624 } 625 } 626 } 627 628 void addCecDevice(HdmiCecDeviceInfo info) { 629 mCecController.addDeviceInfo(info); 630 } 631 632 private void enforceAccessPermission() { 633 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); 634 } 635 636 private final class BinderService extends IHdmiControlService.Stub { 637 @Override 638 public int[] getSupportedTypes() { 639 enforceAccessPermission(); 640 synchronized (mLock) { 641 return mLocalDevices; 642 } 643 } 644 645 @Override 646 public void oneTouchPlay(final IHdmiControlCallback callback) { 647 enforceAccessPermission(); 648 runOnServiceThread(new Runnable() { 649 @Override 650 public void run() { 651 HdmiControlService.this.oneTouchPlay(callback); 652 } 653 }); 654 } 655 656 @Override 657 public void queryDisplayStatus(final IHdmiControlCallback callback) { 658 enforceAccessPermission(); 659 runOnServiceThread(new Runnable() { 660 @Override 661 public void run() { 662 HdmiControlService.this.queryDisplayStatus(callback); 663 } 664 }); 665 } 666 667 @Override 668 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 669 enforceAccessPermission(); 670 runOnServiceThread(new Runnable() { 671 @Override 672 public void run() { 673 HdmiControlService.this.addHotplugEventListener(listener); 674 } 675 }); 676 } 677 678 @Override 679 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) { 680 enforceAccessPermission(); 681 runOnServiceThread(new Runnable() { 682 @Override 683 public void run() { 684 HdmiControlService.this.removeHotplugEventListener(listener); 685 } 686 }); 687 } 688 } 689 690 private void oneTouchPlay(IHdmiControlCallback callback) { 691 if (hasAction(OneTouchPlayAction.class)) { 692 Slog.w(TAG, "oneTouchPlay already in progress"); 693 invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS); 694 return; 695 } 696 HdmiCecLocalDevice source = mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK); 697 if (source == null) { 698 Slog.w(TAG, "Local playback device not available"); 699 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 700 return; 701 } 702 // TODO: Consider the case of multiple TV sets. For now we always direct the command 703 // to the primary one. 704 OneTouchPlayAction action = OneTouchPlayAction.create(this, 705 source.getDeviceInfo().getLogicalAddress(), 706 source.getDeviceInfo().getPhysicalAddress(), HdmiCec.ADDR_TV, callback); 707 if (action == null) { 708 Slog.w(TAG, "Cannot initiate oneTouchPlay"); 709 invokeCallback(callback, HdmiCec.RESULT_EXCEPTION); 710 return; 711 } 712 addAndStartAction(action); 713 } 714 715 private void queryDisplayStatus(IHdmiControlCallback callback) { 716 if (hasAction(DevicePowerStatusAction.class)) { 717 Slog.w(TAG, "queryDisplayStatus already in progress"); 718 invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS); 719 return; 720 } 721 HdmiCecLocalDevice source = mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK); 722 if (source == null) { 723 Slog.w(TAG, "Local playback device not available"); 724 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 725 return; 726 } 727 DevicePowerStatusAction action = DevicePowerStatusAction.create(this, 728 source.getDeviceInfo().getLogicalAddress(), HdmiCec.ADDR_TV, callback); 729 if (action == null) { 730 Slog.w(TAG, "Cannot initiate queryDisplayStatus"); 731 invokeCallback(callback, HdmiCec.RESULT_EXCEPTION); 732 return; 733 } 734 addAndStartAction(action); 735 } 736 737 private void addHotplugEventListener(IHdmiHotplugEventListener listener) { 738 HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); 739 try { 740 listener.asBinder().linkToDeath(record, 0); 741 } catch (RemoteException e) { 742 Slog.w(TAG, "Listener already died"); 743 return; 744 } 745 synchronized (mLock) { 746 mHotplugEventListenerRecords.add(record); 747 mHotplugEventListeners.add(listener); 748 } 749 } 750 751 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) { 752 synchronized (mLock) { 753 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 754 if (record.mListener.asBinder() == listener.asBinder()) { 755 listener.asBinder().unlinkToDeath(record, 0); 756 mHotplugEventListenerRecords.remove(record); 757 break; 758 } 759 } 760 mHotplugEventListeners.remove(listener); 761 } 762 } 763 764 private void invokeCallback(IHdmiControlCallback callback, int result) { 765 try { 766 callback.onComplete(result); 767 } catch (RemoteException e) { 768 Slog.e(TAG, "Invoking callback failed:" + e); 769 } 770 } 771 772 HdmiCecDeviceInfo getAvrDeviceInfo() { 773 return mCecController.getDeviceInfo(HdmiCec.ADDR_AUDIO_SYSTEM); 774 } 775 776 void setAudioStatus(boolean mute, int volume) { 777 // TODO: Hook up with AudioManager. 778 } 779 780 boolean isInPresetInstallationMode() { 781 // TODO: Implement this. 782 return false; 783 } 784 785 /** 786 * Called when a device is removed or removal of device is detected. 787 * 788 * @param address a logical address of a device to be removed 789 */ 790 void removeCecDevice(int address) { 791 mCecController.removeDeviceInfo(address); 792 mCecMessageCache.flushMessagesFrom(address); 793 } 794 795 HdmiCecMessageCache getCecMessageCache() { 796 return mCecMessageCache; 797 } 798} 799