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