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