HdmiCecLocalDeviceTv.java revision 7cd4a589af7cc6e6880799e86ef6febca5add46d
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 static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CEC_DISABLE;
20import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION;
21import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE;
22import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CEC_DISABLED;
23import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION;
24import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN;
25import static android.hardware.hdmi.HdmiControlManager.OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT;
26import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED;
27import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION;
28import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE;
29import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE;
30import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL;
31import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL;
32
33import android.annotation.Nullable;
34import android.content.Context;
35import android.hardware.hdmi.HdmiControlManager;
36import android.hardware.hdmi.HdmiDeviceInfo;
37import android.hardware.hdmi.HdmiRecordSources;
38import android.hardware.hdmi.HdmiTimerRecordSources;
39import android.hardware.hdmi.IHdmiControlCallback;
40import android.media.AudioManager;
41import android.media.AudioSystem;
42import android.media.tv.TvInputInfo;
43import android.media.tv.TvInputManager;
44import android.media.tv.TvInputManager.TvInputCallback;
45import android.os.RemoteException;
46import android.os.SystemProperties;
47import android.provider.Settings.Global;
48import android.util.ArraySet;
49import android.util.Slog;
50import android.util.SparseArray;
51
52import com.android.internal.annotations.GuardedBy;
53import com.android.internal.util.IndentingPrintWriter;
54import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
55import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
56import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
57import com.android.server.SystemService;
58
59import java.io.UnsupportedEncodingException;
60import java.util.ArrayList;
61import java.util.Arrays;
62import java.util.Collection;
63import java.util.Collections;
64import java.util.Iterator;
65import java.util.List;
66import java.util.HashMap;
67
68/**
69 * Represent a logical device of type TV residing in Android system.
70 */
71final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
72    private static final String TAG = "HdmiCecLocalDeviceTv";
73
74    // Whether ARC is available or not. "true" means that ARC is established between TV and
75    // AVR as audio receiver.
76    @ServiceThreadOnly
77    private boolean mArcEstablished = false;
78
79    // Whether ARC feature is enabled or not. The default value is true.
80    // TODO: once adding system setting for it, read the value to it.
81    private boolean mArcFeatureEnabled = true;
82
83    // Whether System audio mode is activated or not.
84    // This becomes true only when all system audio sequences are finished.
85    @GuardedBy("mLock")
86    private boolean mSystemAudioActivated = false;
87
88    // The previous port id (input) before switching to the new one. This is remembered in order to
89    // be able to switch to it upon receiving <Inactive Source> from currently active source.
90    // This remains valid only when the active source was switched via one touch play operation
91    // (either by TV or source device). Manual port switching invalidates this value to
92    // Constants.PORT_INVALID, for which case <Inactive Source> does not do anything.
93    @GuardedBy("mLock")
94    private int mPrevPortId;
95
96    @GuardedBy("mLock")
97    private int mSystemAudioVolume = Constants.UNKNOWN_VOLUME;
98
99    @GuardedBy("mLock")
100    private boolean mSystemAudioMute = false;
101
102    // Copy of mDeviceInfos to guarantee thread-safety.
103    @GuardedBy("mLock")
104    private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList();
105    // All external cec input(source) devices. Does not include system audio device.
106    @GuardedBy("mLock")
107    private List<HdmiDeviceInfo> mSafeExternalInputs = Collections.emptyList();
108
109    // Map-like container of all cec devices including local ones.
110    // device id is used as key of container.
111    // This is not thread-safe. For external purpose use mSafeDeviceInfos.
112    private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>();
113
114    // If true, TV going to standby mode puts other devices also to standby.
115    private boolean mAutoDeviceOff;
116
117    // If true, TV wakes itself up when receiving <Text/Image View On>.
118    private boolean mAutoWakeup;
119
120    // List of the logical address of local CEC devices. Unmodifiable, thread-safe.
121    private List<Integer> mLocalDeviceAddresses;
122
123    private final HdmiCecStandbyModeHandler mStandbyHandler;
124
125    // If true, do not do routing control/send active source for internal source.
126    // Set to true when the device was woken up by <Text/Image View On>.
127    private boolean mSkipRoutingControl;
128
129    // Set of physical addresses of CEC switches on the CEC bus. Managed independently from
130    // other CEC devices since they might not have logical address.
131    private final ArraySet<Integer> mCecSwitches = new ArraySet<Integer>();
132
133    // Message buffer used to buffer selected messages to process later. <Active Source>
134    // from a source device, for instance, needs to be buffered if the device is not
135    // discovered yet. The buffered commands are taken out and when they are ready to
136    // handle.
137    private final DelayedMessageBuffer mDelayedMessageBuffer = new DelayedMessageBuffer(this);
138
139    // Defines the callback invoked when TV input framework is updated with input status.
140    // We are interested in the notification for HDMI input addition event, in order to
141    // process any CEC commands that arrived before the input is added.
142    private final TvInputCallback mTvInputCallback = new TvInputCallback() {
143        @Override
144        public void onInputAdded(String inputId) {
145            TvInputInfo tvInfo = mService.getTvInputManager().getTvInputInfo(inputId);
146            HdmiDeviceInfo info = tvInfo.getHdmiDeviceInfo();
147            if (info == null) return;
148            addTvInput(inputId, info.getId());
149            if (info.isCecDevice()) {
150                processDelayedActiveSource(info.getLogicalAddress());
151            }
152        }
153
154        @Override
155        public void onInputRemoved(String inputId) {
156            removeTvInput(inputId);
157        }
158    };
159
160    // Keeps the mapping (TV input ID, HDMI device ID) to keep track of the TV inputs ready to
161    // accept input switching request from HDMI devices. Requests for which the corresponding
162    // input ID is not yet registered by TV input framework need to be buffered for delayed
163    // processing.
164    private final HashMap<String, Integer> mTvInputs = new HashMap<>();
165
166    @ServiceThreadOnly
167    private void addTvInput(String inputId, int deviceId) {
168        assertRunOnServiceThread();
169        mTvInputs.put(inputId, deviceId);
170    }
171
172    @ServiceThreadOnly
173    private void removeTvInput(String inputId) {
174        assertRunOnServiceThread();
175        mTvInputs.remove(inputId);
176    }
177
178    @Override
179    @ServiceThreadOnly
180    protected boolean isInputReady(int deviceId) {
181        assertRunOnServiceThread();
182        return mTvInputs.containsValue(deviceId);
183    }
184
185    HdmiCecLocalDeviceTv(HdmiControlService service) {
186        super(service, HdmiDeviceInfo.DEVICE_TV);
187        mPrevPortId = Constants.INVALID_PORT_ID;
188        mAutoDeviceOff = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
189                true);
190        mAutoWakeup = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, true);
191        mStandbyHandler = new HdmiCecStandbyModeHandler(service, this);
192    }
193
194    @Override
195    @ServiceThreadOnly
196    protected void onAddressAllocated(int logicalAddress, int reason) {
197        assertRunOnServiceThread();
198        mService.registerTvInputCallback(mTvInputCallback);
199        mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
200                mAddress, mService.getPhysicalAddress(), mDeviceType));
201        mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
202                mAddress, mService.getVendorId()));
203        mCecSwitches.add(mService.getPhysicalAddress());  // TV is a CEC switch too.
204        mTvInputs.clear();
205        mSkipRoutingControl = (reason == HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE);
206        launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC &&
207                reason != HdmiControlService.INITIATED_BY_BOOT_UP);
208        mLocalDeviceAddresses = initLocalDeviceAddresses();
209        launchDeviceDiscovery();
210        startQueuedActions();
211    }
212
213
214    @ServiceThreadOnly
215    private List<Integer> initLocalDeviceAddresses() {
216        assertRunOnServiceThread();
217        List<Integer> addresses = new ArrayList<>();
218        for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
219            addresses.add(device.getDeviceInfo().getLogicalAddress());
220        }
221        return Collections.unmodifiableList(addresses);
222    }
223
224    @Override
225    protected int getPreferredAddress() {
226        return Constants.ADDR_TV;
227    }
228
229    @Override
230    protected void setPreferredAddress(int addr) {
231        Slog.w(TAG, "Preferred addres will not be stored for TV");
232    }
233
234    @Override
235    @ServiceThreadOnly
236    boolean dispatchMessage(HdmiCecMessage message) {
237        assertRunOnServiceThread();
238        if (mService.isPowerStandby() && mStandbyHandler.handleCommand(message)) {
239            return true;
240        }
241        return super.onMessage(message);
242    }
243
244    /**
245     * Performs the action 'device select', or 'one touch play' initiated by TV.
246     *
247     * @param id id of HDMI device to select
248     * @param callback callback object to report the result with
249     */
250    @ServiceThreadOnly
251    void deviceSelect(int id, IHdmiControlCallback callback) {
252        assertRunOnServiceThread();
253        HdmiDeviceInfo targetDevice = mDeviceInfos.get(id);
254        if (targetDevice == null) {
255            invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
256            return;
257        }
258        int targetAddress = targetDevice.getLogicalAddress();
259        ActiveSource active = getActiveSource();
260        if (active.isValid() && targetAddress == active.logicalAddress) {
261            invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
262            return;
263        }
264        if (targetAddress == Constants.ADDR_INTERNAL) {
265            handleSelectInternalSource();
266            // Switching to internal source is always successful even when CEC control is disabled.
267            setActiveSource(targetAddress, mService.getPhysicalAddress());
268            setActivePath(mService.getPhysicalAddress());
269            invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
270            return;
271        }
272        if (!mService.isControlEnabled()) {
273            setActiveSource(targetDevice);
274            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
275            return;
276        }
277        removeAction(DeviceSelectAction.class);
278        addAndStartAction(new DeviceSelectAction(this, targetDevice, callback));
279    }
280
281    @ServiceThreadOnly
282    private void handleSelectInternalSource() {
283        assertRunOnServiceThread();
284        // Seq #18
285        if (mService.isControlEnabled() && mActiveSource.logicalAddress != mAddress) {
286            updateActiveSource(mAddress, mService.getPhysicalAddress());
287            if (mSkipRoutingControl) {
288                mSkipRoutingControl = false;
289                return;
290            }
291            HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
292                    mAddress, mService.getPhysicalAddress());
293            mService.sendCecCommand(activeSource);
294        }
295    }
296
297    @ServiceThreadOnly
298    void updateActiveSource(int logicalAddress, int physicalAddress) {
299        assertRunOnServiceThread();
300        updateActiveSource(ActiveSource.of(logicalAddress, physicalAddress));
301    }
302
303    @ServiceThreadOnly
304    void updateActiveSource(ActiveSource newActive) {
305        assertRunOnServiceThread();
306        // Seq #14
307        if (mActiveSource.equals(newActive)) {
308            return;
309        }
310        setActiveSource(newActive);
311        int logicalAddress = newActive.logicalAddress;
312        if (getCecDeviceInfo(logicalAddress) != null && logicalAddress != mAddress) {
313            if (mService.pathToPortId(newActive.physicalAddress) == getActivePortId()) {
314                setPrevPortId(getActivePortId());
315            }
316            // TODO: Show the OSD banner related to the new active source device.
317        } else {
318            // TODO: If displayed, remove the OSD banner related to the previous
319            //       active source device.
320        }
321    }
322
323    int getPortId(int physicalAddress) {
324        return mService.pathToPortId(physicalAddress);
325    }
326
327    /**
328     * Returns the previous port id kept to handle input switching on <Inactive Source>.
329     */
330    int getPrevPortId() {
331        synchronized (mLock) {
332            return mPrevPortId;
333        }
334    }
335
336    /**
337     * Sets the previous port id. INVALID_PORT_ID invalidates it, hence no actions will be
338     * taken for <Inactive Source>.
339     */
340    void setPrevPortId(int portId) {
341        synchronized (mLock) {
342            mPrevPortId = portId;
343        }
344    }
345
346    @ServiceThreadOnly
347    void updateActiveInput(int path, boolean notifyInputChange) {
348        assertRunOnServiceThread();
349        // Seq #15
350        setPrevPortId(getActivePortId());
351        setActivePath(path);
352        // TODO: Handle PAP/PIP case.
353        // Show OSD port change banner
354        if (notifyInputChange) {
355            ActiveSource activeSource = getActiveSource();
356            HdmiDeviceInfo info = getCecDeviceInfo(activeSource.logicalAddress);
357            if (info == null) {
358                info = mService.getDeviceInfoByPort(getActivePortId());
359                if (info == null) {
360                    // No CEC/MHL device is present at the port. Attempt to switch to
361                    // the hardware port itself for non-CEC devices that may be connected.
362                    info = new HdmiDeviceInfo(path, getActivePortId());
363                }
364            }
365            mService.invokeInputChangeListener(info);
366        }
367    }
368
369    @ServiceThreadOnly
370    void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
371        assertRunOnServiceThread();
372        // Seq #20
373        if (!mService.isValidPortId(portId)) {
374            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
375            return;
376        }
377        if (portId == getActivePortId()) {
378            invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
379            return;
380        }
381        mActiveSource.invalidate();
382        if (!mService.isControlEnabled()) {
383            setActivePortId(portId);
384            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
385            return;
386        }
387        int oldPath = getActivePortId() != Constants.INVALID_PORT_ID
388                ? mService.portIdToPath(getActivePortId()) : getDeviceInfo().getPhysicalAddress();
389        setActivePath(oldPath);
390        if (mSkipRoutingControl) {
391            mSkipRoutingControl = false;
392            return;
393        }
394        int newPath = mService.portIdToPath(portId);
395        startRoutingControl(oldPath, newPath, true, callback);
396    }
397
398    @ServiceThreadOnly
399    void startRoutingControl(int oldPath, int newPath, boolean queryDevicePowerStatus,
400            IHdmiControlCallback callback) {
401        assertRunOnServiceThread();
402        if (oldPath == newPath) {
403            return;
404        }
405        HdmiCecMessage routingChange =
406                HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath);
407        mService.sendCecCommand(routingChange);
408        removeAction(RoutingControlAction.class);
409        addAndStartAction(
410                new RoutingControlAction(this, newPath, queryDevicePowerStatus, callback));
411    }
412
413    @ServiceThreadOnly
414    int getPowerStatus() {
415        assertRunOnServiceThread();
416        return mService.getPowerStatus();
417    }
418
419    /**
420     * Sends key to a target CEC device.
421     *
422     * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}.
423     * @param isPressed true if this is key press event
424     */
425    @Override
426    @ServiceThreadOnly
427    protected void sendKeyEvent(int keyCode, boolean isPressed) {
428        assertRunOnServiceThread();
429        if (!HdmiCecKeycode.isSupportedKeycode(keyCode)) {
430            Slog.w(TAG, "Unsupported key: " + keyCode);
431            return;
432        }
433        List<SendKeyAction> action = getActions(SendKeyAction.class);
434        if (!action.isEmpty()) {
435            action.get(0).processKeyEvent(keyCode, isPressed);
436        } else {
437            if (isPressed) {
438                int logicalAddress = findKeyReceiverAddress();
439                if (logicalAddress != Constants.ADDR_INVALID) {
440                    addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode));
441                    return;
442                }
443            }
444            Slog.w(TAG, "Discard key event: " + keyCode + " pressed:" + isPressed);
445        }
446    }
447
448    private int findKeyReceiverAddress() {
449        if (getActiveSource().isValid()) {
450            return getActiveSource().logicalAddress;
451        }
452        HdmiDeviceInfo info = getDeviceInfoByPath(getActivePath());
453        if (info != null) {
454            return info.getLogicalAddress();
455        }
456        return Constants.ADDR_INVALID;
457    }
458
459    private static void invokeCallback(IHdmiControlCallback callback, int result) {
460        if (callback == null) {
461            return;
462        }
463        try {
464            callback.onComplete(result);
465        } catch (RemoteException e) {
466            Slog.e(TAG, "Invoking callback failed:" + e);
467        }
468    }
469
470    @Override
471    @ServiceThreadOnly
472    protected boolean handleActiveSource(HdmiCecMessage message) {
473        assertRunOnServiceThread();
474        int logicalAddress = message.getSource();
475        int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
476        HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
477        if (info == null) {
478            if (!handleNewDeviceAtTheTailOfActivePath(physicalAddress)) {
479                HdmiLogger.debug("Device info %X not found; buffering the command", logicalAddress);
480                mDelayedMessageBuffer.add(message);
481            }
482        } else if (!isInputReady(info.getId())) {
483            HdmiLogger.debug("Input not ready for device: %X; buffering the command", info.getId());
484            mDelayedMessageBuffer.add(message);
485        } else {
486            ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
487            ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType());
488        }
489        return true;
490    }
491
492    @Override
493    @ServiceThreadOnly
494    protected boolean handleInactiveSource(HdmiCecMessage message) {
495        assertRunOnServiceThread();
496        // Seq #10
497
498        // Ignore <Inactive Source> from non-active source device.
499        if (getActiveSource().logicalAddress != message.getSource()) {
500            return true;
501        }
502        if (isProhibitMode()) {
503            return true;
504        }
505        int portId = getPrevPortId();
506        if (portId != Constants.INVALID_PORT_ID) {
507            // TODO: Do this only if TV is not showing multiview like PIP/PAP.
508
509            HdmiDeviceInfo inactiveSource = getCecDeviceInfo(message.getSource());
510            if (inactiveSource == null) {
511                return true;
512            }
513            if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) {
514                return true;
515            }
516            // TODO: Switch the TV freeze mode off
517
518            doManualPortSwitching(portId, null);
519            setPrevPortId(Constants.INVALID_PORT_ID);
520        } else {
521            // No HDMI port to switch to was found. Notify the input change listers to
522            // switch to the lastly shown internal input.
523            mActiveSource.invalidate();
524            setActivePath(Constants.INVALID_PHYSICAL_ADDRESS);
525            mService.invokeInputChangeListener(HdmiDeviceInfo.INACTIVE_DEVICE);
526        }
527        return true;
528    }
529
530    @Override
531    @ServiceThreadOnly
532    protected boolean handleRequestActiveSource(HdmiCecMessage message) {
533        assertRunOnServiceThread();
534        // Seq #19
535        if (mAddress == getActiveSource().logicalAddress) {
536            mService.sendCecCommand(
537                    HdmiCecMessageBuilder.buildActiveSource(mAddress, getActivePath()));
538        }
539        return true;
540    }
541
542    @Override
543    @ServiceThreadOnly
544    protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
545        assertRunOnServiceThread();
546        if (!broadcastMenuLanguage(mService.getLanguage())) {
547            Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString());
548        }
549        return true;
550    }
551
552    @ServiceThreadOnly
553    boolean broadcastMenuLanguage(String language) {
554        assertRunOnServiceThread();
555        HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand(
556                mAddress, language);
557        if (command != null) {
558            mService.sendCecCommand(command);
559            return true;
560        }
561        return false;
562    }
563
564    @Override
565    @ServiceThreadOnly
566    protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
567        assertRunOnServiceThread();
568        int path = HdmiUtils.twoBytesToInt(message.getParams());
569        int address = message.getSource();
570        int type = message.getParams()[2];
571
572        if (updateCecSwitchInfo(address, type, path)) return true;
573
574        // Ignore if [Device Discovery Action] is going on.
575        if (hasAction(DeviceDiscoveryAction.class)) {
576            Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message);
577            return true;
578        }
579
580        if (!isInDeviceList(address, path)) {
581            handleNewDeviceAtTheTailOfActivePath(path);
582        }
583
584        // Add the device ahead with default information to handle <Active Source>
585        // promptly, rather than waiting till the new device action is finished.
586        HdmiDeviceInfo deviceInfo = new HdmiDeviceInfo(address, path, getPortId(path), type,
587                Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(address));
588        addCecDevice(deviceInfo);
589        startNewDeviceAction(ActiveSource.of(address, path), type);
590        return true;
591    }
592
593    @Override
594    protected boolean handleReportPowerStatus(HdmiCecMessage command) {
595        int newStatus = command.getParams()[0] & 0xFF;
596        updateDevicePowerStatus(command.getSource(), newStatus);
597        return true;
598    }
599
600    @Override
601    protected boolean handleTimerStatus(HdmiCecMessage message) {
602        // Do nothing.
603        return true;
604    }
605
606    @Override
607    protected boolean handleRecordStatus(HdmiCecMessage message) {
608        // Do nothing.
609        return true;
610    }
611
612    boolean updateCecSwitchInfo(int address, int type, int path) {
613        if (address == Constants.ADDR_UNREGISTERED
614                && type == HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH) {
615            mCecSwitches.add(path);
616            updateSafeDeviceInfoList();
617            return true;  // Pure switch does not need further processing. Return here.
618        }
619        if (type == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
620            mCecSwitches.add(path);
621        }
622        return false;
623    }
624
625    void startNewDeviceAction(ActiveSource activeSource, int deviceType) {
626        for (NewDeviceAction action : getActions(NewDeviceAction.class)) {
627            // If there is new device action which has the same logical address and path
628            // ignore new request.
629            // NewDeviceAction is created whenever it receives <Report Physical Address>.
630            // And there is a chance starting NewDeviceAction for the same source.
631            // Usually, new device sends <Report Physical Address> when it's plugged
632            // in. However, TV can detect a new device from HotPlugDetectionAction,
633            // which sends <Give Physical Address> to the source for newly detected
634            // device.
635            if (action.isActionOf(activeSource)) {
636                return;
637            }
638        }
639
640        addAndStartAction(new NewDeviceAction(this, activeSource.logicalAddress,
641                activeSource.physicalAddress, deviceType));
642    }
643
644    private boolean handleNewDeviceAtTheTailOfActivePath(int path) {
645        // Seq #22
646        if (isTailOfActivePath(path, getActivePath())) {
647            int newPath = mService.portIdToPath(getActivePortId());
648            setActivePath(newPath);
649            startRoutingControl(getActivePath(), newPath, false, null);
650            return true;
651        }
652        return false;
653    }
654
655    /**
656     * Whether the given path is located in the tail of current active path.
657     *
658     * @param path to be tested
659     * @param activePath current active path
660     * @return true if the given path is located in the tail of current active path; otherwise,
661     *         false
662     */
663    static boolean isTailOfActivePath(int path, int activePath) {
664        // If active routing path is internal source, return false.
665        if (activePath == 0) {
666            return false;
667        }
668        for (int i = 12; i >= 0; i -= 4) {
669            int curActivePath = (activePath >> i) & 0xF;
670            if (curActivePath == 0) {
671                return true;
672            } else {
673                int curPath = (path >> i) & 0xF;
674                if (curPath != curActivePath) {
675                    return false;
676                }
677            }
678        }
679        return false;
680    }
681
682    @Override
683    @ServiceThreadOnly
684    protected boolean handleRoutingChange(HdmiCecMessage message) {
685        assertRunOnServiceThread();
686        // Seq #21
687        byte[] params = message.getParams();
688        int currentPath = HdmiUtils.twoBytesToInt(params);
689        if (HdmiUtils.isAffectingActiveRoutingPath(getActivePath(), currentPath)) {
690            mActiveSource.invalidate();
691            removeAction(RoutingControlAction.class);
692            int newPath = HdmiUtils.twoBytesToInt(params, 2);
693            addAndStartAction(new RoutingControlAction(this, newPath, true, null));
694        }
695        return true;
696    }
697
698    @Override
699    @ServiceThreadOnly
700    protected boolean handleReportAudioStatus(HdmiCecMessage message) {
701        assertRunOnServiceThread();
702
703        byte params[] = message.getParams();
704        int mute = params[0] & 0x80;
705        int volume = params[0] & 0x7F;
706        setAudioStatus(mute == 0x80, volume);
707        return true;
708    }
709
710    @Override
711    @ServiceThreadOnly
712    protected boolean handleTextViewOn(HdmiCecMessage message) {
713        assertRunOnServiceThread();
714        if (mService.isPowerStandbyOrTransient() && mAutoWakeup) {
715            mService.wakeUp();
716        }
717        return true;
718    }
719
720    @Override
721    @ServiceThreadOnly
722    protected boolean handleImageViewOn(HdmiCecMessage message) {
723        assertRunOnServiceThread();
724        // Currently, it's the same as <Text View On>.
725        return handleTextViewOn(message);
726    }
727
728    @Override
729    @ServiceThreadOnly
730    protected boolean handleSetOsdName(HdmiCecMessage message) {
731        int source = message.getSource();
732        HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source);
733        // If the device is not in device list, ignore it.
734        if (deviceInfo == null) {
735            Slog.e(TAG, "No source device info for <Set Osd Name>." + message);
736            return true;
737        }
738        String osdName = null;
739        try {
740            osdName = new String(message.getParams(), "US-ASCII");
741        } catch (UnsupportedEncodingException e) {
742            Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e);
743            return true;
744        }
745
746        if (deviceInfo.getDisplayName().equals(osdName)) {
747            Slog.i(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
748            return true;
749        }
750
751        addCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
752                deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(),
753                deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName));
754        return true;
755    }
756
757    @ServiceThreadOnly
758    private void launchDeviceDiscovery() {
759        assertRunOnServiceThread();
760        clearDeviceInfoList();
761        DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
762                new DeviceDiscoveryCallback() {
763                    @Override
764                    public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
765                        for (HdmiDeviceInfo info : deviceInfos) {
766                            addCecDevice(info);
767                        }
768
769                        // Since we removed all devices when it's start and
770                        // device discovery action does not poll local devices,
771                        // we should put device info of local device manually here
772                        for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
773                            addCecDevice(device.getDeviceInfo());
774                        }
775
776                        addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
777                        addAndStartAction(new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this));
778
779                        // If there is AVR, initiate System Audio Auto initiation action,
780                        // which turns on and off system audio according to last system
781                        // audio setting.
782                        HdmiDeviceInfo avr = getAvrDeviceInfo();
783                        if (avr != null) {
784                            onNewAvrAdded(avr);
785                        }
786                    }
787                });
788        addAndStartAction(action);
789    }
790
791    @ServiceThreadOnly
792    void onNewAvrAdded(HdmiDeviceInfo avr) {
793        assertRunOnServiceThread();
794        if (getSystemAudioModeSetting() && !isSystemAudioActivated()) {
795            addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
796        }
797        if (isArcFeatureEnabled() && !hasAction(SetArcTransmissionStateAction.class)) {
798            startArcAction(true);
799        }
800    }
801
802    // Clear all device info.
803    @ServiceThreadOnly
804    private void clearDeviceInfoList() {
805        assertRunOnServiceThread();
806        for (HdmiDeviceInfo info : mSafeExternalInputs) {
807            invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
808        }
809        mDeviceInfos.clear();
810        updateSafeDeviceInfoList();
811    }
812
813    @ServiceThreadOnly
814    // Seq #32
815    void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {
816        assertRunOnServiceThread();
817        if (!mService.isControlEnabled() || hasAction(DeviceDiscoveryAction.class)) {
818            setSystemAudioMode(false, true);
819            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
820            return;
821        }
822        HdmiDeviceInfo avr = getAvrDeviceInfo();
823        if (avr == null) {
824            setSystemAudioMode(false, true);
825            invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
826            return;
827        }
828
829        addAndStartAction(
830                new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback));
831    }
832
833    // # Seq 25
834    void setSystemAudioMode(boolean on, boolean updateSetting) {
835        HdmiLogger.debug("System Audio Mode change[old:%b new:%b]", mSystemAudioActivated, on);
836
837        if (updateSetting) {
838            mService.writeBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, on);
839        }
840        updateAudioManagerForSystemAudio(on);
841        synchronized (mLock) {
842            if (mSystemAudioActivated != on) {
843                mSystemAudioActivated = on;
844                mService.announceSystemAudioModeChange(on);
845            }
846        }
847    }
848
849    private void updateAudioManagerForSystemAudio(boolean on) {
850        int device = mService.getAudioManager().setHdmiSystemAudioSupported(on);
851        HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device);
852    }
853
854    boolean isSystemAudioActivated() {
855        if (!hasSystemAudioDevice()) {
856            return false;
857        }
858        synchronized (mLock) {
859            return mSystemAudioActivated;
860        }
861    }
862
863    boolean getSystemAudioModeSetting() {
864        return mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, false);
865    }
866
867    /**
868     * Change ARC status into the given {@code enabled} status.
869     *
870     * @return {@code true} if ARC was in "Enabled" status
871     */
872    @ServiceThreadOnly
873    boolean setArcStatus(boolean enabled) {
874        assertRunOnServiceThread();
875
876        HdmiLogger.debug("Set Arc Status[old:%b new:%b]", mArcEstablished, enabled);
877        boolean oldStatus = mArcEstablished;
878        // 1. Enable/disable ARC circuit.
879        mService.setAudioReturnChannel(getAvrDeviceInfo().getPortId(), enabled);
880        // 2. Notify arc status to audio service.
881        notifyArcStatusToAudioService(enabled);
882        // 3. Update arc status;
883        mArcEstablished = enabled;
884        return oldStatus;
885    }
886
887    private void notifyArcStatusToAudioService(boolean enabled) {
888        // Note that we don't set any name to ARC.
889        mService.getAudioManager().setWiredDeviceConnectionState(
890                AudioSystem.DEVICE_OUT_HDMI_ARC,
891                enabled ? 1 : 0, "");
892    }
893
894    /**
895     * Returns whether ARC is enabled or not.
896     */
897    @ServiceThreadOnly
898    boolean isArcEstabilished() {
899        assertRunOnServiceThread();
900        return mArcFeatureEnabled && mArcEstablished;
901    }
902
903    @ServiceThreadOnly
904    void changeArcFeatureEnabled(boolean enabled) {
905        assertRunOnServiceThread();
906
907        if (mArcFeatureEnabled != enabled) {
908            mArcFeatureEnabled = enabled;
909            if (enabled) {
910                if (!mArcEstablished) {
911                    startArcAction(true);
912                }
913            } else {
914                if (mArcEstablished) {
915                    startArcAction(false);
916                }
917            }
918        }
919    }
920
921    @ServiceThreadOnly
922    boolean isArcFeatureEnabled() {
923        assertRunOnServiceThread();
924        return mArcFeatureEnabled;
925    }
926
927    @ServiceThreadOnly
928    void startArcAction(boolean enabled) {
929        assertRunOnServiceThread();
930        HdmiDeviceInfo info = getAvrDeviceInfo();
931        if (info == null) {
932            Slog.w(TAG, "Failed to start arc action; No AVR device.");
933            return;
934        }
935        if (!canStartArcUpdateAction(info.getLogicalAddress(), enabled)) {
936            Slog.w(TAG, "Failed to start arc action; ARC configuration check failed.");
937            if (enabled && !isConnectedToArcPort(info.getPhysicalAddress())) {
938                displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
939            }
940            return;
941        }
942
943        // Terminate opposite action and start action if not exist.
944        if (enabled) {
945            removeAction(RequestArcTerminationAction.class);
946            if (!hasAction(RequestArcInitiationAction.class)) {
947                addAndStartAction(new RequestArcInitiationAction(this, info.getLogicalAddress()));
948            }
949        } else {
950            removeAction(RequestArcInitiationAction.class);
951            if (!hasAction(RequestArcTerminationAction.class)) {
952                addAndStartAction(new RequestArcTerminationAction(this, info.getLogicalAddress()));
953            }
954        }
955    }
956
957    private boolean isDirectConnectAddress(int physicalAddress) {
958        return (physicalAddress & Constants.ROUTING_PATH_TOP_MASK) == physicalAddress;
959    }
960
961    void setAudioStatus(boolean mute, int volume) {
962        synchronized (mLock) {
963            mSystemAudioMute = mute;
964            mSystemAudioVolume = volume;
965            int maxVolume = mService.getAudioManager().getStreamMaxVolume(
966                    AudioManager.STREAM_MUSIC);
967            mService.setAudioStatus(mute,
968                    VolumeControlAction.scaleToCustomVolume(volume, maxVolume));
969            displayOsd(HdmiControlManager.OSD_MESSAGE_AVR_VOLUME_CHANGED,
970                    mute ? HdmiControlManager.AVR_VOLUME_MUTED : volume);
971        }
972    }
973
974    @ServiceThreadOnly
975    void changeVolume(int curVolume, int delta, int maxVolume) {
976        assertRunOnServiceThread();
977        if (delta == 0 || !isSystemAudioActivated()) {
978            return;
979        }
980
981        int targetVolume = curVolume + delta;
982        int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume);
983        synchronized (mLock) {
984            // If new volume is the same as current system audio volume, just ignore it.
985            // Note that UNKNOWN_VOLUME is not in range of cec volume scale.
986            if (cecVolume == mSystemAudioVolume) {
987                // Update tv volume with system volume value.
988                mService.setAudioStatus(false,
989                        VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume));
990                return;
991            }
992        }
993
994        List<VolumeControlAction> actions = getActions(VolumeControlAction.class);
995        if (actions.isEmpty()) {
996            addAndStartAction(new VolumeControlAction(this,
997                    getAvrDeviceInfo().getLogicalAddress(), delta > 0));
998        } else {
999            actions.get(0).handleVolumeChange(delta > 0);
1000        }
1001    }
1002
1003    @ServiceThreadOnly
1004    void changeMute(boolean mute) {
1005        assertRunOnServiceThread();
1006        HdmiLogger.debug("[A]:Change mute:%b", mute);
1007        synchronized (mLock) {
1008            if (mSystemAudioMute == mute) {
1009                HdmiLogger.debug("No need to change mute.");
1010                return;
1011            }
1012        }
1013        if (!isSystemAudioActivated()) {
1014            HdmiLogger.debug("[A]:System audio is not activated.");
1015            return;
1016        }
1017
1018        // Remove existing volume action.
1019        removeAction(VolumeControlAction.class);
1020        sendUserControlPressedAndReleased(getAvrDeviceInfo().getLogicalAddress(),
1021                mute ? HdmiCecKeycode.CEC_KEYCODE_MUTE_FUNCTION :
1022                        HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION);
1023    }
1024
1025    @Override
1026    @ServiceThreadOnly
1027    protected boolean handleInitiateArc(HdmiCecMessage message) {
1028        assertRunOnServiceThread();
1029
1030        if (!canStartArcUpdateAction(message.getSource(), true)) {
1031            if (getAvrDeviceInfo() == null) {
1032                // AVR may not have been discovered yet. Delay the message processing.
1033                mDelayedMessageBuffer.add(message);
1034                return true;
1035            }
1036            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
1037            if (!isConnectedToArcPort(message.getSource())) {
1038                displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
1039            }
1040            return true;
1041        }
1042
1043        // In case where <Initiate Arc> is started by <Request ARC Initiation>
1044        // need to clean up RequestArcInitiationAction.
1045        removeAction(RequestArcInitiationAction.class);
1046        SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
1047                message.getSource(), true);
1048        addAndStartAction(action);
1049        return true;
1050    }
1051
1052    private boolean canStartArcUpdateAction(int avrAddress, boolean shouldCheckArcFeatureEnabled) {
1053        HdmiDeviceInfo avr = getAvrDeviceInfo();
1054        if (avr != null
1055                && (avrAddress == avr.getLogicalAddress())
1056                && isConnectedToArcPort(avr.getPhysicalAddress())
1057                && isDirectConnectAddress(avr.getPhysicalAddress())) {
1058            if (shouldCheckArcFeatureEnabled) {
1059                return isArcFeatureEnabled();
1060            } else {
1061                return true;
1062            }
1063        } else {
1064            return false;
1065        }
1066    }
1067
1068    @Override
1069    @ServiceThreadOnly
1070    protected boolean handleTerminateArc(HdmiCecMessage message) {
1071        assertRunOnServiceThread();
1072        // In cast of termination, do not check ARC configuration in that AVR device
1073        // might be removed already.
1074
1075        // In case where <Terminate Arc> is started by <Request ARC Termination>
1076        // need to clean up RequestArcInitiationAction.
1077        removeAction(RequestArcTerminationAction.class);
1078        SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
1079                message.getSource(), false);
1080        addAndStartAction(action);
1081        return true;
1082    }
1083
1084    @Override
1085    @ServiceThreadOnly
1086    protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
1087        assertRunOnServiceThread();
1088        if (!isMessageForSystemAudio(message)) {
1089            if (getAvrDeviceInfo() == null) {
1090                // AVR may not have been discovered yet. Delay the message processing.
1091                mDelayedMessageBuffer.add(message);
1092                return true;
1093            }
1094            HdmiLogger.warning("Invalid <Set System Audio Mode> message:" + message);
1095            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
1096            return true;
1097        }
1098        SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this,
1099                message.getSource(), HdmiUtils.parseCommandParamSystemAudioStatus(message), null);
1100        addAndStartAction(action);
1101        return true;
1102    }
1103
1104    @Override
1105    @ServiceThreadOnly
1106    protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
1107        assertRunOnServiceThread();
1108        if (!isMessageForSystemAudio(message)) {
1109            HdmiLogger.warning("Invalid <System Audio Mode Status> message:" + message);
1110            // Ignore this message.
1111            return true;
1112        }
1113        setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message), true);
1114        return true;
1115    }
1116
1117    // Seq #53
1118    @Override
1119    @ServiceThreadOnly
1120    protected boolean handleRecordTvScreen(HdmiCecMessage message) {
1121        List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class);
1122        if (!actions.isEmpty()) {
1123            // Assumes only one OneTouchRecordAction.
1124            OneTouchRecordAction action = actions.get(0);
1125            if (action.getRecorderAddress() != message.getSource()) {
1126                announceOneTouchRecordResult(
1127                        message.getSource(),
1128                        HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS);
1129            }
1130            return super.handleRecordTvScreen(message);
1131        }
1132
1133        int recorderAddress = message.getSource();
1134        byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress);
1135        int reason = startOneTouchRecord(recorderAddress, recordSource);
1136        if (reason != Constants.ABORT_NO_ERROR) {
1137            mService.maySendFeatureAbortCommand(message, reason);
1138        }
1139        return true;
1140    }
1141
1142    @Override
1143    protected boolean handleTimerClearedStatus(HdmiCecMessage message) {
1144        byte[] params = message.getParams();
1145        int timerClearedStatusData = params[0] & 0xFF;
1146        announceTimerRecordingResult(message.getSource(), timerClearedStatusData);
1147        return true;
1148    }
1149
1150    void announceOneTouchRecordResult(int recorderAddress, int result) {
1151        mService.invokeOneTouchRecordResult(recorderAddress, result);
1152    }
1153
1154    void announceTimerRecordingResult(int recorderAddress, int result) {
1155        mService.invokeTimerRecordingResult(recorderAddress, result);
1156    }
1157
1158    void announceClearTimerRecordingResult(int recorderAddress, int result) {
1159        mService.invokeClearTimerRecordingResult(recorderAddress, result);
1160    }
1161
1162    private boolean isMessageForSystemAudio(HdmiCecMessage message) {
1163        return mService.isControlEnabled()
1164                && message.getSource() == Constants.ADDR_AUDIO_SYSTEM
1165                && (message.getDestination() == Constants.ADDR_TV
1166                        || message.getDestination() == Constants.ADDR_BROADCAST)
1167                && getAvrDeviceInfo() != null;
1168    }
1169
1170    /**
1171     * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same
1172     * logical address as new device info's.
1173     *
1174     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
1175     *
1176     * @param deviceInfo a new {@link HdmiDeviceInfo} to be added.
1177     * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo}
1178     *         that has the same logical address as new one has.
1179     */
1180    @ServiceThreadOnly
1181    private HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) {
1182        assertRunOnServiceThread();
1183        HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress());
1184        if (oldDeviceInfo != null) {
1185            removeDeviceInfo(deviceInfo.getId());
1186        }
1187        mDeviceInfos.append(deviceInfo.getId(), deviceInfo);
1188        updateSafeDeviceInfoList();
1189        return oldDeviceInfo;
1190    }
1191
1192    /**
1193     * Remove a device info corresponding to the given {@code logicalAddress}.
1194     * It returns removed {@link HdmiDeviceInfo} if exists.
1195     *
1196     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
1197     *
1198     * @param id id of device to be removed
1199     * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null}
1200     */
1201    @ServiceThreadOnly
1202    private HdmiDeviceInfo removeDeviceInfo(int id) {
1203        assertRunOnServiceThread();
1204        HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id);
1205        if (deviceInfo != null) {
1206            mDeviceInfos.remove(id);
1207        }
1208        updateSafeDeviceInfoList();
1209        return deviceInfo;
1210    }
1211
1212    /**
1213     * Return a list of all {@link HdmiDeviceInfo}.
1214     *
1215     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
1216     * This is not thread-safe. For thread safety, call {@link #getSafeExternalInputsLocked} which
1217     * does not include local device.
1218     */
1219    @ServiceThreadOnly
1220    List<HdmiDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) {
1221        assertRunOnServiceThread();
1222        if (includeLocalDevice) {
1223            return HdmiUtils.sparseArrayToList(mDeviceInfos);
1224        } else {
1225            ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
1226            for (int i = 0; i < mDeviceInfos.size(); ++i) {
1227                HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
1228                if (!isLocalDeviceAddress(info.getLogicalAddress())) {
1229                    infoList.add(info);
1230                }
1231            }
1232            return infoList;
1233        }
1234    }
1235
1236    /**
1237     * Return external input devices.
1238     */
1239    List<HdmiDeviceInfo> getSafeExternalInputsLocked() {
1240        return mSafeExternalInputs;
1241    }
1242
1243    @ServiceThreadOnly
1244    private void updateSafeDeviceInfoList() {
1245        assertRunOnServiceThread();
1246        List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
1247        List<HdmiDeviceInfo> externalInputs = getInputDevices();
1248        synchronized (mLock) {
1249            mSafeAllDeviceInfos = copiedDevices;
1250            mSafeExternalInputs = externalInputs;
1251        }
1252    }
1253
1254    /**
1255     * Return a list of external cec input (source) devices.
1256     *
1257     * <p>Note that this effectively excludes non-source devices like system audio,
1258     * secondary TV.
1259     */
1260    private List<HdmiDeviceInfo> getInputDevices() {
1261        ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
1262        for (int i = 0; i < mDeviceInfos.size(); ++i) {
1263            HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
1264            if (isLocalDeviceAddress(info.getLogicalAddress())) {
1265                continue;
1266            }
1267            if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) {
1268                infoList.add(info);
1269            }
1270        }
1271        return infoList;
1272    }
1273
1274    // Check if we are hiding CEC devices connected to a legacy (non-CEC) switch.
1275    // Returns true if the policy is set to true, and the device to check does not have
1276    // a parent CEC device (which should be the CEC-enabled switch) in the list.
1277    private boolean hideDevicesBehindLegacySwitch(HdmiDeviceInfo info) {
1278        return HdmiConfig.HIDE_DEVICES_BEHIND_LEGACY_SWITCH
1279                && !isConnectedToCecSwitch(info.getPhysicalAddress(), mCecSwitches);
1280    }
1281
1282    private static boolean isConnectedToCecSwitch(int path, Collection<Integer> switches) {
1283        for (int switchPath : switches) {
1284            if (isParentPath(switchPath, path)) {
1285                return true;
1286            }
1287        }
1288        return false;
1289    }
1290
1291    private static boolean isParentPath(int parentPath, int childPath) {
1292        // (A000, AB00) (AB00, ABC0), (ABC0, ABCD)
1293        // If child's last non-zero nibble is removed, the result equals to the parent.
1294        for (int i = 0; i <= 12; i += 4) {
1295            int nibble = (childPath >> i) & 0xF;
1296            if (nibble != 0) {
1297                int parentNibble = (parentPath >> i) & 0xF;
1298                return parentNibble == 0 && (childPath >> i+4) == (parentPath >> i+4);
1299            }
1300        }
1301        return false;
1302    }
1303
1304    private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) {
1305        if (!hideDevicesBehindLegacySwitch(info)) {
1306            mService.invokeDeviceEventListeners(info, status);
1307        }
1308    }
1309
1310    private boolean isLocalDeviceAddress(int address) {
1311        return mLocalDeviceAddresses.contains(address);
1312    }
1313
1314    @ServiceThreadOnly
1315    HdmiDeviceInfo getAvrDeviceInfo() {
1316        assertRunOnServiceThread();
1317        return getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
1318    }
1319
1320    /**
1321     * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}.
1322     *
1323     * This is not thread-safe. For thread safety, call {@link #getSafeCecDeviceInfo(int)}.
1324     *
1325     * @param logicalAddress logical address of the device to be retrieved
1326     * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
1327     *         Returns null if no logical address matched
1328     */
1329    @ServiceThreadOnly
1330    HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) {
1331        assertRunOnServiceThread();
1332        return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress));
1333    }
1334
1335    boolean hasSystemAudioDevice() {
1336        return getSafeAvrDeviceInfo() != null;
1337    }
1338
1339    HdmiDeviceInfo getSafeAvrDeviceInfo() {
1340        return getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
1341    }
1342
1343    /**
1344     * Thread safe version of {@link #getCecDeviceInfo(int)}.
1345     *
1346     * @param logicalAddress logical address to be retrieved
1347     * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
1348     *         Returns null if no logical address matched
1349     */
1350    HdmiDeviceInfo getSafeCecDeviceInfo(int logicalAddress) {
1351        synchronized (mLock) {
1352            for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
1353                if (info.isCecDevice() && info.getLogicalAddress() == logicalAddress) {
1354                    return info;
1355                }
1356            }
1357            return null;
1358        }
1359    }
1360
1361    List<HdmiDeviceInfo> getSafeCecDevicesLocked() {
1362        ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
1363        for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
1364            if (isLocalDeviceAddress(info.getLogicalAddress())) {
1365                continue;
1366            }
1367            infoList.add(info);
1368        }
1369        return infoList;
1370    }
1371
1372    /**
1373     * Called when a device is newly added or a new device is detected or
1374     * existing device is updated.
1375     *
1376     * @param info device info of a new device.
1377     */
1378    @ServiceThreadOnly
1379    final void addCecDevice(HdmiDeviceInfo info) {
1380        assertRunOnServiceThread();
1381        HdmiDeviceInfo old = addDeviceInfo(info);
1382        if (info.getLogicalAddress() == mAddress) {
1383            // The addition of TV device itself should not be notified.
1384            return;
1385        }
1386        if (old == null) {
1387            invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
1388        } else if (!old.equals(info)) {
1389            invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
1390            invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
1391        }
1392    }
1393
1394    /**
1395     * Called when a device is removed or removal of device is detected.
1396     *
1397     * @param address a logical address of a device to be removed
1398     */
1399    @ServiceThreadOnly
1400    final void removeCecDevice(int address) {
1401        assertRunOnServiceThread();
1402        HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
1403
1404        mCecMessageCache.flushMessagesFrom(address);
1405        invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
1406    }
1407
1408    @ServiceThreadOnly
1409    void handleRemoveActiveRoutingPath(int path) {
1410        assertRunOnServiceThread();
1411        // Seq #23
1412        if (isTailOfActivePath(path, getActivePath())) {
1413            int newPath = mService.portIdToPath(getActivePortId());
1414            startRoutingControl(getActivePath(), newPath, true, null);
1415        }
1416    }
1417
1418    /**
1419     * Launch routing control process.
1420     *
1421     * @param routingForBootup true if routing control is initiated due to One Touch Play
1422     *        or TV power on
1423     */
1424    @ServiceThreadOnly
1425    void launchRoutingControl(boolean routingForBootup) {
1426        assertRunOnServiceThread();
1427        // Seq #24
1428        if (getActivePortId() != Constants.INVALID_PORT_ID) {
1429            if (!routingForBootup && !isProhibitMode()) {
1430                int newPath = mService.portIdToPath(getActivePortId());
1431                setActivePath(newPath);
1432                startRoutingControl(getActivePath(), newPath, routingForBootup, null);
1433            }
1434        } else {
1435            int activePath = mService.getPhysicalAddress();
1436            setActivePath(activePath);
1437            if (!routingForBootup
1438                    && !mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) {
1439                mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(mAddress,
1440                        activePath));
1441            }
1442        }
1443    }
1444
1445    /**
1446     * Returns the {@link HdmiDeviceInfo} instance whose physical address matches
1447     * the given routing path. CEC devices use routing path for its physical address to
1448     * describe the hierarchy of the devices in the network.
1449     *
1450     * @param path routing path or physical address
1451     * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null
1452     */
1453    @ServiceThreadOnly
1454    final HdmiDeviceInfo getDeviceInfoByPath(int path) {
1455        assertRunOnServiceThread();
1456        for (HdmiDeviceInfo info : getDeviceInfoList(false)) {
1457            if (info.getPhysicalAddress() == path) {
1458                return info;
1459            }
1460        }
1461        return null;
1462    }
1463
1464    /**
1465     * Whether a device of the specified physical address and logical address exists
1466     * in a device info list. However, both are minimal condition and it could
1467     * be different device from the original one.
1468     *
1469     * @param logicalAddress logical address of a device to be searched
1470     * @param physicalAddress physical address of a device to be searched
1471     * @return true if exist; otherwise false
1472     */
1473    @ServiceThreadOnly
1474    boolean isInDeviceList(int logicalAddress, int physicalAddress) {
1475        assertRunOnServiceThread();
1476        HdmiDeviceInfo device = getCecDeviceInfo(logicalAddress);
1477        if (device == null) {
1478            return false;
1479        }
1480        return device.getPhysicalAddress() == physicalAddress;
1481    }
1482
1483    @Override
1484    @ServiceThreadOnly
1485    void onHotplug(int portId, boolean connected) {
1486        assertRunOnServiceThread();
1487
1488        if (!connected) {
1489            removeCecSwitches(portId);
1490        }
1491        // Tv device will have permanent HotplugDetectionAction.
1492        List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class);
1493        if (!hotplugActions.isEmpty()) {
1494            // Note that hotplug action is single action running on a machine.
1495            // "pollAllDevicesNow" cleans up timer and start poll action immediately.
1496            // It covers seq #40, #43.
1497            hotplugActions.get(0).pollAllDevicesNow();
1498        }
1499    }
1500
1501    private void removeCecSwitches(int portId) {
1502        Iterator<Integer> it = mCecSwitches.iterator();
1503        while (!it.hasNext()) {
1504            int path = it.next();
1505            if (pathToPortId(path) == portId) {
1506                it.remove();
1507            }
1508        }
1509    }
1510
1511    @ServiceThreadOnly
1512    void setAutoDeviceOff(boolean enabled) {
1513        assertRunOnServiceThread();
1514        mAutoDeviceOff = enabled;
1515    }
1516
1517    @ServiceThreadOnly
1518    void setAutoWakeup(boolean enabled) {
1519        assertRunOnServiceThread();
1520        mAutoWakeup = enabled;
1521    }
1522
1523    @ServiceThreadOnly
1524    boolean getAutoWakeup() {
1525        assertRunOnServiceThread();
1526        return mAutoWakeup;
1527    }
1528
1529    @Override
1530    @ServiceThreadOnly
1531    protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
1532        super.disableDevice(initiatedByCec, callback);
1533        assertRunOnServiceThread();
1534        mService.unregisterTvInputCallback(mTvInputCallback);
1535        // Remove any repeated working actions.
1536        // HotplugDetectionAction will be reinstated during the wake up process.
1537        // HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
1538        //     LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery().
1539        removeAction(DeviceDiscoveryAction.class);
1540        removeAction(HotplugDetectionAction.class);
1541        removeAction(PowerStatusMonitorAction.class);
1542        // Remove recording actions.
1543        removeAction(OneTouchRecordAction.class);
1544        removeAction(TimerRecordingAction.class);
1545
1546        disableSystemAudioIfExist();
1547        disableArcIfExist();
1548        clearDeviceInfoList();
1549        checkIfPendingActionsCleared();
1550    }
1551
1552    @ServiceThreadOnly
1553    private void disableSystemAudioIfExist() {
1554        assertRunOnServiceThread();
1555        if (getAvrDeviceInfo() == null) {
1556            return;
1557        }
1558
1559        // Seq #31.
1560        removeAction(SystemAudioActionFromAvr.class);
1561        removeAction(SystemAudioActionFromTv.class);
1562        removeAction(SystemAudioAutoInitiationAction.class);
1563        removeAction(SystemAudioStatusAction.class);
1564        removeAction(VolumeControlAction.class);
1565
1566        // Turn off the mode but do not write it the settings, so that the next time TV powers on
1567        // the system audio mode setting can be restored automatically.
1568        setSystemAudioMode(false, false);
1569    }
1570
1571    @ServiceThreadOnly
1572    private void disableArcIfExist() {
1573        assertRunOnServiceThread();
1574        HdmiDeviceInfo avr = getAvrDeviceInfo();
1575        if (avr == null) {
1576            return;
1577        }
1578
1579        // Seq #44.
1580        removeAction(RequestArcInitiationAction.class);
1581        if (!hasAction(RequestArcTerminationAction.class) && isArcEstabilished()) {
1582            addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress()));
1583        }
1584    }
1585
1586    @Override
1587    @ServiceThreadOnly
1588    protected void onStandby(boolean initiatedByCec) {
1589        assertRunOnServiceThread();
1590        // Seq #11
1591        if (!mService.isControlEnabled()) {
1592            return;
1593        }
1594        if (!initiatedByCec && mAutoDeviceOff) {
1595            mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(
1596                    mAddress, Constants.ADDR_BROADCAST));
1597        }
1598    }
1599
1600    boolean isProhibitMode() {
1601        return mService.isProhibitMode();
1602    }
1603
1604    boolean isPowerStandbyOrTransient() {
1605        return mService.isPowerStandbyOrTransient();
1606    }
1607
1608    @ServiceThreadOnly
1609    void displayOsd(int messageId) {
1610        assertRunOnServiceThread();
1611        mService.displayOsd(messageId);
1612    }
1613
1614    @ServiceThreadOnly
1615    void displayOsd(int messageId, int extra) {
1616        assertRunOnServiceThread();
1617        mService.displayOsd(messageId, extra);
1618    }
1619
1620    // Seq #54 and #55
1621    @ServiceThreadOnly
1622    int startOneTouchRecord(int recorderAddress, byte[] recordSource) {
1623        assertRunOnServiceThread();
1624        if (!mService.isControlEnabled()) {
1625            Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1626            announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
1627            return Constants.ABORT_NOT_IN_CORRECT_MODE;
1628        }
1629
1630        if (!checkRecorder(recorderAddress)) {
1631            Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1632            announceOneTouchRecordResult(recorderAddress,
1633                    ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
1634            return Constants.ABORT_NOT_IN_CORRECT_MODE;
1635        }
1636
1637        if (!checkRecordSource(recordSource)) {
1638            Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1639            announceOneTouchRecordResult(recorderAddress,
1640                    ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN);
1641            return Constants.ABORT_CANNOT_PROVIDE_SOURCE;
1642        }
1643
1644        addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource));
1645        Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:"
1646                + Arrays.toString(recordSource));
1647        return Constants.ABORT_NO_ERROR;
1648    }
1649
1650    @ServiceThreadOnly
1651    void stopOneTouchRecord(int recorderAddress) {
1652        assertRunOnServiceThread();
1653        if (!mService.isControlEnabled()) {
1654            Slog.w(TAG, "Can not stop one touch record. CEC control is disabled.");
1655            announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
1656            return;
1657        }
1658
1659        if (!checkRecorder(recorderAddress)) {
1660            Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1661            announceOneTouchRecordResult(recorderAddress,
1662                    ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
1663            return;
1664        }
1665
1666        // Remove one touch record action so that other one touch record can be started.
1667        removeAction(OneTouchRecordAction.class);
1668        mService.sendCecCommand(HdmiCecMessageBuilder.buildRecordOff(mAddress, recorderAddress));
1669        Slog.i(TAG, "Stop [One Touch Record]-Target:" + recorderAddress);
1670    }
1671
1672    private boolean checkRecorder(int recorderAddress) {
1673        HdmiDeviceInfo device = getCecDeviceInfo(recorderAddress);
1674        return (device != null)
1675                && (HdmiUtils.getTypeFromAddress(recorderAddress)
1676                        == HdmiDeviceInfo.DEVICE_RECORDER);
1677    }
1678
1679    private boolean checkRecordSource(byte[] recordSource) {
1680        return (recordSource != null) && HdmiRecordSources.checkRecordSource(recordSource);
1681    }
1682
1683    @ServiceThreadOnly
1684    void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
1685        assertRunOnServiceThread();
1686        if (!mService.isControlEnabled()) {
1687            Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1688            announceTimerRecordingResult(recorderAddress,
1689                    TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED);
1690            return;
1691        }
1692
1693        if (!checkRecorder(recorderAddress)) {
1694            Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1695            announceTimerRecordingResult(recorderAddress,
1696                    TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION);
1697            return;
1698        }
1699
1700        if (!checkTimerRecordingSource(sourceType, recordSource)) {
1701            Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1702            announceTimerRecordingResult(
1703                    recorderAddress,
1704                    TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE);
1705            return;
1706        }
1707
1708        addAndStartAction(
1709                new TimerRecordingAction(this, recorderAddress, sourceType, recordSource));
1710        Slog.i(TAG, "Start [Timer Recording]-Target:" + recorderAddress + ", SourceType:"
1711                + sourceType + ", RecordSource:" + Arrays.toString(recordSource));
1712    }
1713
1714    private boolean checkTimerRecordingSource(int sourceType, byte[] recordSource) {
1715        return (recordSource != null)
1716                && HdmiTimerRecordSources.checkTimerRecordSource(sourceType, recordSource);
1717    }
1718
1719    @ServiceThreadOnly
1720    void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
1721        assertRunOnServiceThread();
1722        if (!mService.isControlEnabled()) {
1723            Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1724            announceClearTimerRecordingResult(recorderAddress, CLEAR_TIMER_STATUS_CEC_DISABLE);
1725            return;
1726        }
1727
1728        if (!checkRecorder(recorderAddress)) {
1729            Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1730            announceClearTimerRecordingResult(recorderAddress,
1731                    CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION);
1732            return;
1733        }
1734
1735        if (!checkTimerRecordingSource(sourceType, recordSource)) {
1736            Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1737            announceClearTimerRecordingResult(recorderAddress,
1738                    CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1739            return;
1740        }
1741
1742        sendClearTimerMessage(recorderAddress, sourceType, recordSource);
1743    }
1744
1745    private void sendClearTimerMessage(final int recorderAddress, int sourceType,
1746            byte[] recordSource) {
1747        HdmiCecMessage message = null;
1748        switch (sourceType) {
1749            case TIMER_RECORDING_TYPE_DIGITAL:
1750                message = HdmiCecMessageBuilder.buildClearDigitalTimer(mAddress, recorderAddress,
1751                        recordSource);
1752                break;
1753            case TIMER_RECORDING_TYPE_ANALOGUE:
1754                message = HdmiCecMessageBuilder.buildClearAnalogueTimer(mAddress, recorderAddress,
1755                        recordSource);
1756                break;
1757            case TIMER_RECORDING_TYPE_EXTERNAL:
1758                message = HdmiCecMessageBuilder.buildClearExternalTimer(mAddress, recorderAddress,
1759                        recordSource);
1760                break;
1761            default:
1762                Slog.w(TAG, "Invalid source type:" + recorderAddress);
1763                announceClearTimerRecordingResult(recorderAddress,
1764                        CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1765                return;
1766
1767        }
1768        mService.sendCecCommand(message, new SendMessageCallback() {
1769            @Override
1770            public void onSendCompleted(int error) {
1771                if (error != Constants.SEND_RESULT_SUCCESS) {
1772                    announceClearTimerRecordingResult(recorderAddress,
1773                            CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1774                }
1775            }
1776        });
1777    }
1778
1779    void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
1780        HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
1781        if (info == null) {
1782            Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress);
1783            return;
1784        }
1785
1786        if (info.getDevicePowerStatus() == newPowerStatus) {
1787            return;
1788        }
1789
1790        HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus);
1791        // addDeviceInfo replaces old device info with new one if exists.
1792        addDeviceInfo(newInfo);
1793
1794        invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
1795    }
1796
1797    @Override
1798    protected boolean handleMenuStatus(HdmiCecMessage message) {
1799        // Do nothing and just return true not to prevent from responding <Feature Abort>.
1800        return true;
1801    }
1802
1803    @Override
1804    protected void sendStandby(int deviceId) {
1805        HdmiDeviceInfo targetDevice = mDeviceInfos.get(deviceId);
1806        if (targetDevice == null) {
1807            return;
1808        }
1809        int targetAddress = targetDevice.getLogicalAddress();
1810        mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
1811    }
1812
1813    @ServiceThreadOnly
1814    void processAllDelayedMessages() {
1815        assertRunOnServiceThread();
1816        mDelayedMessageBuffer.processAllMessages();
1817    }
1818
1819    @ServiceThreadOnly
1820    void processDelayedMessages(int address) {
1821        assertRunOnServiceThread();
1822        mDelayedMessageBuffer.processMessagesForDevice(address);
1823    }
1824
1825    @ServiceThreadOnly
1826    void processDelayedActiveSource(int address) {
1827        assertRunOnServiceThread();
1828        mDelayedMessageBuffer.processActiveSource(address);
1829    }
1830
1831    @Override
1832    protected void dump(final IndentingPrintWriter pw) {
1833        super.dump(pw);
1834        pw.println("mArcEstablished: " + mArcEstablished);
1835        pw.println("mArcFeatureEnabled: " + mArcFeatureEnabled);
1836        pw.println("mSystemAudioActivated: " + mSystemAudioActivated);
1837        pw.println("mSystemAudioMute: " + mSystemAudioMute);
1838        pw.println("mAutoDeviceOff: " + mAutoDeviceOff);
1839        pw.println("mAutoWakeup: " + mAutoWakeup);
1840        pw.println("mSkipRoutingControl: " + mSkipRoutingControl);
1841        pw.println("mPrevPortId: " + mPrevPortId);
1842        pw.println("CEC devices:");
1843        pw.increaseIndent();
1844        for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
1845            pw.println(info);
1846        }
1847        pw.decreaseIndent();
1848    }
1849}
1850