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