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 &lt;Polling Message&gt; 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