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