HdmiControlService.java revision e81e108c4035ea8933525baa8108cb392f8abf5d
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 /** 364 * Add a new {@link HdmiCecDeviceInfo} to controller. 365 * 366 * @param deviceInfo new device information object to add 367 */ 368 void addDeviceInfo(HdmiCecDeviceInfo deviceInfo) { 369 // TODO: Implement this. 370 } 371 372 boolean handleCecCommand(HdmiCecMessage message) { 373 // Cache incoming message. Note that it caches only white-listed one. 374 mCecMessageCache.cacheMessage(message); 375 376 // Commands that queries system information replies directly instead 377 // of creating FeatureAction because they are state-less. 378 switch (message.getOpcode()) { 379 case HdmiCec.MESSAGE_GET_MENU_LANGUAGE: 380 handleGetMenuLanguage(message); 381 return true; 382 case HdmiCec.MESSAGE_GIVE_OSD_NAME: 383 handleGiveOsdName(message); 384 return true; 385 case HdmiCec.MESSAGE_GIVE_PHYSICAL_ADDRESS: 386 handleGivePhysicalAddress(message); 387 return true; 388 case HdmiCec.MESSAGE_GIVE_DEVICE_VENDOR_ID: 389 handleGiveDeviceVendorId(message); 390 return true; 391 case HdmiCec.MESSAGE_GET_CEC_VERSION: 392 handleGetCecVersion(message); 393 return true; 394 case HdmiCec.MESSAGE_INITIATE_ARC: 395 handleInitiateArc(message); 396 return true; 397 case HdmiCec.MESSAGE_TERMINATE_ARC: 398 handleTerminateArc(message); 399 return true; 400 case HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS: 401 handleReportPhysicalAddress(message); 402 return true; 403 case HdmiCec.MESSAGE_SET_SYSTEM_AUDIO_MODE: 404 handleSetSystemAudioMode(message); 405 return true; 406 case HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_STATUS: 407 handleSystemAudioModeStatus(message); 408 return true; 409 default: 410 return dispatchMessageToAction(message); 411 } 412 } 413 414 /** 415 * Called when a new hotplug event is issued. 416 * 417 * @param portNo hdmi port number where hot plug event issued. 418 * @param connected whether to be plugged in or not 419 */ 420 void onHotplug(int portNo, boolean connected) { 421 // TODO: Start "RequestArcInitiationAction" if ARC port. 422 } 423 424 /** 425 * Poll all remote devices. It sends <Polling Message> to all remote 426 * devices. 427 * 428 * @param callback an interface used to get a list of all remote devices' address 429 * @param pickStrategy strategy how to pick polling candidates 430 * @param retryCount the number of retry used to send polling message to remote devices 431 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value 432 */ 433 void pollDevices(DevicePollingCallback callback, int pickStrategy, int retryCount) { 434 mCecController.pollDevices(callback, checkPollStrategy(pickStrategy), retryCount); 435 } 436 437 private int checkPollStrategy(int pickStrategy) { 438 int strategy = pickStrategy & POLL_STRATEGY_MASK; 439 if (strategy == 0) { 440 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy); 441 } 442 int iterationStrategy = pickStrategy & POLL_ITERATION_STRATEGY_MASK; 443 if (iterationStrategy == 0) { 444 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy); 445 } 446 return strategy | iterationStrategy; 447 } 448 449 /** 450 * Launch device discovery sequence. It starts with clearing the existing device info list. 451 * Note that it assumes that logical address of all local devices is already allocated. 452 * 453 * @param sourceAddress a logical address of tv 454 */ 455 void launchDeviceDiscovery(final int sourceAddress) { 456 // At first, clear all existing device infos. 457 mCecController.clearDeviceInfoList(); 458 mCecMessageCache.flushAll(); 459 460 // TODO: check whether TV is one of local devices. 461 DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, sourceAddress, 462 new DeviceDiscoveryCallback() { 463 @Override 464 public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) { 465 for (HdmiCecDeviceInfo info : deviceInfos) { 466 mCecController.addDeviceInfo(info); 467 } 468 469 // Add device info of all local devices. 470 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 471 mCecController.addDeviceInfo(device.getDeviceInfo()); 472 } 473 474 addAndStartAction(new HotplugDetectionAction(HdmiControlService.this, 475 sourceAddress)); 476 } 477 }); 478 addAndStartAction(action); 479 } 480 481 private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) { 482 // TODO: get device name read from system configuration. 483 String displayName = HdmiCec.getDefaultDeviceName(logicalAddress); 484 return new HdmiCecDeviceInfo(logicalAddress, 485 getPhysicalAddress(), deviceType, getVendorId(), displayName); 486 } 487 488 private void handleReportPhysicalAddress(HdmiCecMessage message) { 489 // At first, try to consume it. 490 if (dispatchMessageToAction(message)) { 491 return; 492 } 493 494 // Ignore if [Device Discovery Action] is going on. 495 if (hasAction(DeviceDiscoveryAction.class)) { 496 Slog.i(TAG, "Ignore unrecognizable <Report Physical Address> " 497 + "because Device Discovery Action is on-going:" + message); 498 return; 499 } 500 501 // TODO: start new device action. 502 } 503 504 private void handleInitiateArc(HdmiCecMessage message){ 505 // In case where <Initiate Arc> is started by <Request ARC Initiation> 506 // need to clean up RequestArcInitiationAction. 507 removeAction(RequestArcInitiationAction.class); 508 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 509 message.getDestination(), message.getSource(), true); 510 addAndStartAction(action); 511 } 512 513 private void handleTerminateArc(HdmiCecMessage message) { 514 // In case where <Terminate Arc> is started by <Request ARC Termination> 515 // need to clean up RequestArcInitiationAction. 516 // TODO: check conditions of power status by calling is_connected api 517 // to be added soon. 518 removeAction(RequestArcTerminationAction.class); 519 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 520 message.getDestination(), message.getSource(), false); 521 addAndStartAction(action); 522 } 523 524 private void handleGetCecVersion(HdmiCecMessage message) { 525 int version = mCecController.getVersion(); 526 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(), 527 message.getSource(), 528 version); 529 sendCecCommand(cecMessage); 530 } 531 532 private void handleGiveDeviceVendorId(HdmiCecMessage message) { 533 int vendorId = mCecController.getVendorId(); 534 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand( 535 message.getDestination(), vendorId); 536 sendCecCommand(cecMessage); 537 } 538 539 private void handleGivePhysicalAddress(HdmiCecMessage message) { 540 int physicalAddress = mCecController.getPhysicalAddress(); 541 int deviceType = HdmiCec.getTypeFromAddress(message.getDestination()); 542 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( 543 message.getDestination(), physicalAddress, deviceType); 544 sendCecCommand(cecMessage); 545 } 546 547 private void handleGiveOsdName(HdmiCecMessage message) { 548 // TODO: read device name from settings or property. 549 String name = HdmiCec.getDefaultDeviceName(message.getDestination()); 550 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand( 551 message.getDestination(), message.getSource(), name); 552 if (cecMessage != null) { 553 sendCecCommand(cecMessage); 554 } else { 555 Slog.w(TAG, "Failed to build <Get Osd Name>:" + name); 556 } 557 } 558 559 private void handleGetMenuLanguage(HdmiCecMessage message) { 560 // Only 0 (TV), 14 (specific use) can answer. 561 if (message.getDestination() != HdmiCec.ADDR_TV 562 && message.getDestination() != HdmiCec.ADDR_SPECIFIC_USE) { 563 Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString()); 564 sendCecCommand( 565 HdmiCecMessageBuilder.buildFeatureAbortCommand(message.getDestination(), 566 message.getSource(), HdmiCec.MESSAGE_GET_MENU_LANGUAGE, 567 HdmiConstants.ABORT_UNRECOGNIZED_MODE)); 568 return; 569 } 570 571 HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand( 572 message.getDestination(), 573 Locale.getDefault().getISO3Language()); 574 // TODO: figure out how to handle failed to get language code. 575 if (command != null) { 576 sendCecCommand(command); 577 } else { 578 Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString()); 579 } 580 } 581 582 private boolean dispatchMessageToAction(HdmiCecMessage message) { 583 for (FeatureAction action : mActions) { 584 if (action.processCommand(message)) { 585 return true; 586 } 587 } 588 Slog.w(TAG, "Unsupported cec command:" + message); 589 return false; 590 } 591 592 private void handleSetSystemAudioMode(HdmiCecMessage message) { 593 if (dispatchMessageToAction(message) || !isMessageForSystemAudio(message)) { 594 return; 595 } 596 SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this, 597 message.getDestination(), message.getSource(), 598 HdmiUtils.parseCommandParamSystemAudioStatus(message)); 599 addAndStartAction(action); 600 } 601 602 private void handleSystemAudioModeStatus(HdmiCecMessage message) { 603 if (!isMessageForSystemAudio(message)) { 604 return; 605 } 606 setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message)); 607 } 608 609 private boolean isMessageForSystemAudio(HdmiCecMessage message) { 610 if (message.getSource() != HdmiCec.ADDR_AUDIO_SYSTEM 611 || message.getDestination() != HdmiCec.ADDR_TV 612 || getAvrDeviceInfo() == null) { 613 Slog.w(TAG, "Skip abnormal CecMessage: " + message); 614 return false; 615 } 616 return true; 617 } 618 619 // Record class that monitors the event of the caller of being killed. Used to clean up 620 // the listener list and record list accordingly. 621 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { 622 private final IHdmiHotplugEventListener mListener; 623 624 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) { 625 mListener = listener; 626 } 627 628 @Override 629 public void binderDied() { 630 synchronized (mLock) { 631 mHotplugEventListenerRecords.remove(this); 632 mHotplugEventListeners.remove(mListener); 633 } 634 } 635 } 636 637 void addCecDevice(HdmiCecDeviceInfo info) { 638 mCecController.addDeviceInfo(info); 639 } 640 641 private void enforceAccessPermission() { 642 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); 643 } 644 645 private final class BinderService extends IHdmiControlService.Stub { 646 @Override 647 public int[] getSupportedTypes() { 648 enforceAccessPermission(); 649 synchronized (mLock) { 650 return mLocalDevices; 651 } 652 } 653 654 @Override 655 public void oneTouchPlay(final IHdmiControlCallback callback) { 656 enforceAccessPermission(); 657 runOnServiceThread(new Runnable() { 658 @Override 659 public void run() { 660 HdmiControlService.this.oneTouchPlay(callback); 661 } 662 }); 663 } 664 665 @Override 666 public void queryDisplayStatus(final IHdmiControlCallback callback) { 667 enforceAccessPermission(); 668 runOnServiceThread(new Runnable() { 669 @Override 670 public void run() { 671 HdmiControlService.this.queryDisplayStatus(callback); 672 } 673 }); 674 } 675 676 @Override 677 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 678 enforceAccessPermission(); 679 runOnServiceThread(new Runnable() { 680 @Override 681 public void run() { 682 HdmiControlService.this.addHotplugEventListener(listener); 683 } 684 }); 685 } 686 687 @Override 688 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) { 689 enforceAccessPermission(); 690 runOnServiceThread(new Runnable() { 691 @Override 692 public void run() { 693 HdmiControlService.this.removeHotplugEventListener(listener); 694 } 695 }); 696 } 697 } 698 699 private void oneTouchPlay(IHdmiControlCallback callback) { 700 if (hasAction(OneTouchPlayAction.class)) { 701 Slog.w(TAG, "oneTouchPlay already in progress"); 702 invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS); 703 return; 704 } 705 HdmiCecLocalDevice source = mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK); 706 if (source == null) { 707 Slog.w(TAG, "Local playback device not available"); 708 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 709 return; 710 } 711 // TODO: Consider the case of multiple TV sets. For now we always direct the command 712 // to the primary one. 713 OneTouchPlayAction action = OneTouchPlayAction.create(this, 714 source.getDeviceInfo().getLogicalAddress(), 715 source.getDeviceInfo().getPhysicalAddress(), HdmiCec.ADDR_TV, callback); 716 if (action == null) { 717 Slog.w(TAG, "Cannot initiate oneTouchPlay"); 718 invokeCallback(callback, HdmiCec.RESULT_EXCEPTION); 719 return; 720 } 721 addAndStartAction(action); 722 } 723 724 private void queryDisplayStatus(IHdmiControlCallback callback) { 725 if (hasAction(DevicePowerStatusAction.class)) { 726 Slog.w(TAG, "queryDisplayStatus already in progress"); 727 invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS); 728 return; 729 } 730 HdmiCecLocalDevice source = mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK); 731 if (source == null) { 732 Slog.w(TAG, "Local playback device not available"); 733 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 734 return; 735 } 736 DevicePowerStatusAction action = DevicePowerStatusAction.create(this, 737 source.getDeviceInfo().getLogicalAddress(), HdmiCec.ADDR_TV, callback); 738 if (action == null) { 739 Slog.w(TAG, "Cannot initiate queryDisplayStatus"); 740 invokeCallback(callback, HdmiCec.RESULT_EXCEPTION); 741 return; 742 } 743 addAndStartAction(action); 744 } 745 746 private void addHotplugEventListener(IHdmiHotplugEventListener listener) { 747 HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); 748 try { 749 listener.asBinder().linkToDeath(record, 0); 750 } catch (RemoteException e) { 751 Slog.w(TAG, "Listener already died"); 752 return; 753 } 754 synchronized (mLock) { 755 mHotplugEventListenerRecords.add(record); 756 mHotplugEventListeners.add(listener); 757 } 758 } 759 760 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) { 761 synchronized (mLock) { 762 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 763 if (record.mListener.asBinder() == listener.asBinder()) { 764 listener.asBinder().unlinkToDeath(record, 0); 765 mHotplugEventListenerRecords.remove(record); 766 break; 767 } 768 } 769 mHotplugEventListeners.remove(listener); 770 } 771 } 772 773 private void invokeCallback(IHdmiControlCallback callback, int result) { 774 try { 775 callback.onComplete(result); 776 } catch (RemoteException e) { 777 Slog.e(TAG, "Invoking callback failed:" + e); 778 } 779 } 780 781 HdmiCecDeviceInfo getAvrDeviceInfo() { 782 return mCecController.getDeviceInfo(HdmiCec.ADDR_AUDIO_SYSTEM); 783 } 784 785 void setAudioStatus(boolean mute, int volume) { 786 // TODO: Hook up with AudioManager. 787 } 788 789 boolean isInPresetInstallationMode() { 790 // TODO: Implement this. 791 return false; 792 } 793 794 /** 795 * Called when a device is removed or removal of device is detected. 796 * 797 * @param address a logical address of a device to be removed 798 */ 799 void removeCecDevice(int address) { 800 mCecController.removeDeviceInfo(address); 801 mCecMessageCache.flushMessagesFrom(address); 802 } 803 804 HdmiCecMessageCache getCecMessageCache() { 805 return mCecMessageCache; 806 } 807} 808