HdmiCecLocalDeviceTv.java revision 454fab52195f86d08d0b7626ed170af113e44695
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        int logicalAddress = findKeyReceiverAddress();
435        if (logicalAddress == mAddress) {
436            Slog.w(TAG, "Discard key event to itself :" + keyCode + " pressed:" + isPressed);
437            return;
438        }
439        if (!action.isEmpty()) {
440            action.get(0).processKeyEvent(keyCode, isPressed);
441        } else {
442            if (isPressed) {
443                if (logicalAddress != Constants.ADDR_INVALID) {
444                    addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode));
445                    return;
446                }
447            }
448            Slog.w(TAG, "Discard key event: " + keyCode + " pressed:" + isPressed);
449        }
450    }
451
452    private int findKeyReceiverAddress() {
453        if (getActiveSource().isValid()) {
454            return getActiveSource().logicalAddress;
455        }
456        HdmiDeviceInfo info = getDeviceInfoByPath(getActivePath());
457        if (info != null) {
458            return info.getLogicalAddress();
459        }
460        return Constants.ADDR_INVALID;
461    }
462
463    private static void invokeCallback(IHdmiControlCallback callback, int result) {
464        if (callback == null) {
465            return;
466        }
467        try {
468            callback.onComplete(result);
469        } catch (RemoteException e) {
470            Slog.e(TAG, "Invoking callback failed:" + e);
471        }
472    }
473
474    @Override
475    @ServiceThreadOnly
476    protected boolean handleActiveSource(HdmiCecMessage message) {
477        assertRunOnServiceThread();
478        int logicalAddress = message.getSource();
479        int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
480        HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
481        if (info == null) {
482            if (!handleNewDeviceAtTheTailOfActivePath(physicalAddress)) {
483                HdmiLogger.debug("Device info %X not found; buffering the command", logicalAddress);
484                mDelayedMessageBuffer.add(message);
485            }
486        } else if (!isInputReady(info.getId())) {
487            HdmiLogger.debug("Input not ready for device: %X; buffering the command", info.getId());
488            mDelayedMessageBuffer.add(message);
489        } else {
490            ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
491            ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType());
492        }
493        return true;
494    }
495
496    @Override
497    @ServiceThreadOnly
498    protected boolean handleInactiveSource(HdmiCecMessage message) {
499        assertRunOnServiceThread();
500        // Seq #10
501
502        // Ignore <Inactive Source> from non-active source device.
503        if (getActiveSource().logicalAddress != message.getSource()) {
504            return true;
505        }
506        if (isProhibitMode()) {
507            return true;
508        }
509        int portId = getPrevPortId();
510        if (portId != Constants.INVALID_PORT_ID) {
511            // TODO: Do this only if TV is not showing multiview like PIP/PAP.
512
513            HdmiDeviceInfo inactiveSource = getCecDeviceInfo(message.getSource());
514            if (inactiveSource == null) {
515                return true;
516            }
517            if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) {
518                return true;
519            }
520            // TODO: Switch the TV freeze mode off
521
522            doManualPortSwitching(portId, null);
523            setPrevPortId(Constants.INVALID_PORT_ID);
524        } else {
525            // No HDMI port to switch to was found. Notify the input change listers to
526            // switch to the lastly shown internal input.
527            mActiveSource.invalidate();
528            setActivePath(Constants.INVALID_PHYSICAL_ADDRESS);
529            mService.invokeInputChangeListener(HdmiDeviceInfo.INACTIVE_DEVICE);
530        }
531        return true;
532    }
533
534    @Override
535    @ServiceThreadOnly
536    protected boolean handleRequestActiveSource(HdmiCecMessage message) {
537        assertRunOnServiceThread();
538        // Seq #19
539        if (mAddress == getActiveSource().logicalAddress) {
540            mService.sendCecCommand(
541                    HdmiCecMessageBuilder.buildActiveSource(mAddress, getActivePath()));
542        }
543        return true;
544    }
545
546    @Override
547    @ServiceThreadOnly
548    protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
549        assertRunOnServiceThread();
550        if (!broadcastMenuLanguage(mService.getLanguage())) {
551            Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString());
552        }
553        return true;
554    }
555
556    @ServiceThreadOnly
557    boolean broadcastMenuLanguage(String language) {
558        assertRunOnServiceThread();
559        HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand(
560                mAddress, language);
561        if (command != null) {
562            mService.sendCecCommand(command);
563            return true;
564        }
565        return false;
566    }
567
568    @Override
569    @ServiceThreadOnly
570    protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
571        assertRunOnServiceThread();
572        int path = HdmiUtils.twoBytesToInt(message.getParams());
573        int address = message.getSource();
574        int type = message.getParams()[2];
575
576        if (updateCecSwitchInfo(address, type, path)) return true;
577
578        // Ignore if [Device Discovery Action] is going on.
579        if (hasAction(DeviceDiscoveryAction.class)) {
580            Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message);
581            return true;
582        }
583
584        if (!isInDeviceList(address, path)) {
585            handleNewDeviceAtTheTailOfActivePath(path);
586        }
587        startNewDeviceAction(ActiveSource.of(address, path), type);
588        return true;
589    }
590
591    @Override
592    protected boolean handleReportPowerStatus(HdmiCecMessage command) {
593        int newStatus = command.getParams()[0] & 0xFF;
594        updateDevicePowerStatus(command.getSource(), newStatus);
595        return true;
596    }
597
598    @Override
599    protected boolean handleTimerStatus(HdmiCecMessage message) {
600        // Do nothing.
601        return true;
602    }
603
604    @Override
605    protected boolean handleRecordStatus(HdmiCecMessage message) {
606        // Do nothing.
607        return true;
608    }
609
610    boolean updateCecSwitchInfo(int address, int type, int path) {
611        if (address == Constants.ADDR_UNREGISTERED
612                && type == HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH) {
613            mCecSwitches.add(path);
614            updateSafeDeviceInfoList();
615            return true;  // Pure switch does not need further processing. Return here.
616        }
617        if (type == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
618            mCecSwitches.add(path);
619        }
620        return false;
621    }
622
623    void startNewDeviceAction(ActiveSource activeSource, int deviceType) {
624        for (NewDeviceAction action : getActions(NewDeviceAction.class)) {
625            // If there is new device action which has the same logical address and path
626            // ignore new request.
627            // NewDeviceAction is created whenever it receives <Report Physical Address>.
628            // And there is a chance starting NewDeviceAction for the same source.
629            // Usually, new device sends <Report Physical Address> when it's plugged
630            // in. However, TV can detect a new device from HotPlugDetectionAction,
631            // which sends <Give Physical Address> to the source for newly detected
632            // device.
633            if (action.isActionOf(activeSource)) {
634                return;
635            }
636        }
637
638        addAndStartAction(new NewDeviceAction(this, activeSource.logicalAddress,
639                activeSource.physicalAddress, deviceType));
640    }
641
642    private boolean handleNewDeviceAtTheTailOfActivePath(int path) {
643        // Seq #22
644        if (isTailOfActivePath(path, getActivePath())) {
645            int newPath = mService.portIdToPath(getActivePortId());
646            setActivePath(newPath);
647            startRoutingControl(getActivePath(), newPath, false, null);
648            return true;
649        }
650        return false;
651    }
652
653    /**
654     * Whether the given path is located in the tail of current active path.
655     *
656     * @param path to be tested
657     * @param activePath current active path
658     * @return true if the given path is located in the tail of current active path; otherwise,
659     *         false
660     */
661    static boolean isTailOfActivePath(int path, int activePath) {
662        // If active routing path is internal source, return false.
663        if (activePath == 0) {
664            return false;
665        }
666        for (int i = 12; i >= 0; i -= 4) {
667            int curActivePath = (activePath >> i) & 0xF;
668            if (curActivePath == 0) {
669                return true;
670            } else {
671                int curPath = (path >> i) & 0xF;
672                if (curPath != curActivePath) {
673                    return false;
674                }
675            }
676        }
677        return false;
678    }
679
680    @Override
681    @ServiceThreadOnly
682    protected boolean handleRoutingChange(HdmiCecMessage message) {
683        assertRunOnServiceThread();
684        // Seq #21
685        byte[] params = message.getParams();
686        int currentPath = HdmiUtils.twoBytesToInt(params);
687        if (HdmiUtils.isAffectingActiveRoutingPath(getActivePath(), currentPath)) {
688            mActiveSource.invalidate();
689            removeAction(RoutingControlAction.class);
690            int newPath = HdmiUtils.twoBytesToInt(params, 2);
691            addAndStartAction(new RoutingControlAction(this, newPath, true, null));
692        }
693        return true;
694    }
695
696    @Override
697    @ServiceThreadOnly
698    protected boolean handleReportAudioStatus(HdmiCecMessage message) {
699        assertRunOnServiceThread();
700
701        byte params[] = message.getParams();
702        int mute = params[0] & 0x80;
703        int volume = params[0] & 0x7F;
704        setAudioStatus(mute == 0x80, volume);
705        return true;
706    }
707
708    @Override
709    @ServiceThreadOnly
710    protected boolean handleTextViewOn(HdmiCecMessage message) {
711        assertRunOnServiceThread();
712        if (mService.isPowerStandbyOrTransient() && mAutoWakeup) {
713            mService.wakeUp();
714        }
715        return true;
716    }
717
718    @Override
719    @ServiceThreadOnly
720    protected boolean handleImageViewOn(HdmiCecMessage message) {
721        assertRunOnServiceThread();
722        // Currently, it's the same as <Text View On>.
723        return handleTextViewOn(message);
724    }
725
726    @Override
727    @ServiceThreadOnly
728    protected boolean handleSetOsdName(HdmiCecMessage message) {
729        int source = message.getSource();
730        HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source);
731        // If the device is not in device list, ignore it.
732        if (deviceInfo == null) {
733            Slog.e(TAG, "No source device info for <Set Osd Name>." + message);
734            return true;
735        }
736        String osdName = null;
737        try {
738            osdName = new String(message.getParams(), "US-ASCII");
739        } catch (UnsupportedEncodingException e) {
740            Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e);
741            return true;
742        }
743
744        if (deviceInfo.getDisplayName().equals(osdName)) {
745            Slog.i(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
746            return true;
747        }
748
749        addCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
750                deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(),
751                deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName));
752        return true;
753    }
754
755    @ServiceThreadOnly
756    private void launchDeviceDiscovery() {
757        assertRunOnServiceThread();
758        clearDeviceInfoList();
759        DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
760                new DeviceDiscoveryCallback() {
761                    @Override
762                    public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
763                        for (HdmiDeviceInfo info : deviceInfos) {
764                            addCecDevice(info);
765                        }
766
767                        // Since we removed all devices when it's start and
768                        // device discovery action does not poll local devices,
769                        // we should put device info of local device manually here
770                        for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
771                            addCecDevice(device.getDeviceInfo());
772                        }
773
774                        addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
775                        addAndStartAction(new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this));
776
777                        // If there is AVR, initiate System Audio Auto initiation action,
778                        // which turns on and off system audio according to last system
779                        // audio setting.
780                        HdmiDeviceInfo avr = getAvrDeviceInfo();
781                        if (avr != null) {
782                            onNewAvrAdded(avr);
783                        }
784                    }
785                });
786        addAndStartAction(action);
787    }
788
789    @ServiceThreadOnly
790    void onNewAvrAdded(HdmiDeviceInfo avr) {
791        assertRunOnServiceThread();
792        if (getSystemAudioModeSetting() && !isSystemAudioActivated()) {
793            addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
794        }
795        if (isArcFeatureEnabled() && !hasAction(SetArcTransmissionStateAction.class)) {
796            startArcAction(true);
797        }
798    }
799
800    // Clear all device info.
801    @ServiceThreadOnly
802    private void clearDeviceInfoList() {
803        assertRunOnServiceThread();
804        for (HdmiDeviceInfo info : mSafeExternalInputs) {
805            invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
806        }
807        mDeviceInfos.clear();
808        updateSafeDeviceInfoList();
809    }
810
811    @ServiceThreadOnly
812    // Seq #32
813    void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {
814        assertRunOnServiceThread();
815        if (!mService.isControlEnabled() || hasAction(DeviceDiscoveryAction.class)) {
816            setSystemAudioMode(false, true);
817            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
818            return;
819        }
820        HdmiDeviceInfo avr = getAvrDeviceInfo();
821        if (avr == null) {
822            setSystemAudioMode(false, true);
823            invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
824            return;
825        }
826
827        addAndStartAction(
828                new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback));
829    }
830
831    // # Seq 25
832    void setSystemAudioMode(boolean on, boolean updateSetting) {
833        HdmiLogger.debug("System Audio Mode change[old:%b new:%b]", mSystemAudioActivated, on);
834
835        if (updateSetting) {
836            mService.writeBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, on);
837        }
838        updateAudioManagerForSystemAudio(on);
839        synchronized (mLock) {
840            if (mSystemAudioActivated != on) {
841                mSystemAudioActivated = on;
842                mService.announceSystemAudioModeChange(on);
843            }
844        }
845    }
846
847    private void updateAudioManagerForSystemAudio(boolean on) {
848        int device = mService.getAudioManager().setHdmiSystemAudioSupported(on);
849        HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device);
850    }
851
852    boolean isSystemAudioActivated() {
853        if (!hasSystemAudioDevice()) {
854            return false;
855        }
856        synchronized (mLock) {
857            return mSystemAudioActivated;
858        }
859    }
860
861    boolean getSystemAudioModeSetting() {
862        return mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, false);
863    }
864
865    /**
866     * Change ARC status into the given {@code enabled} status.
867     *
868     * @return {@code true} if ARC was in "Enabled" status
869     */
870    @ServiceThreadOnly
871    boolean setArcStatus(boolean enabled) {
872        assertRunOnServiceThread();
873
874        HdmiLogger.debug("Set Arc Status[old:%b new:%b]", mArcEstablished, enabled);
875        boolean oldStatus = mArcEstablished;
876        // 1. Enable/disable ARC circuit.
877        mService.setAudioReturnChannel(getAvrDeviceInfo().getPortId(), enabled);
878        // 2. Notify arc status to audio service.
879        notifyArcStatusToAudioService(enabled);
880        // 3. Update arc status;
881        mArcEstablished = enabled;
882        return oldStatus;
883    }
884
885    private void notifyArcStatusToAudioService(boolean enabled) {
886        // Note that we don't set any name to ARC.
887        mService.getAudioManager().setWiredDeviceConnectionState(
888                AudioSystem.DEVICE_OUT_HDMI_ARC,
889                enabled ? 1 : 0, "");
890    }
891
892    /**
893     * Returns whether ARC is enabled or not.
894     */
895    @ServiceThreadOnly
896    boolean isArcEstabilished() {
897        assertRunOnServiceThread();
898        return mArcFeatureEnabled && mArcEstablished;
899    }
900
901    @ServiceThreadOnly
902    void changeArcFeatureEnabled(boolean enabled) {
903        assertRunOnServiceThread();
904
905        if (mArcFeatureEnabled != enabled) {
906            mArcFeatureEnabled = enabled;
907            if (enabled) {
908                if (!mArcEstablished) {
909                    startArcAction(true);
910                }
911            } else {
912                if (mArcEstablished) {
913                    startArcAction(false);
914                }
915            }
916        }
917    }
918
919    @ServiceThreadOnly
920    boolean isArcFeatureEnabled() {
921        assertRunOnServiceThread();
922        return mArcFeatureEnabled;
923    }
924
925    @ServiceThreadOnly
926    void startArcAction(boolean enabled) {
927        assertRunOnServiceThread();
928        HdmiDeviceInfo info = getAvrDeviceInfo();
929        if (info == null) {
930            Slog.w(TAG, "Failed to start arc action; No AVR device.");
931            return;
932        }
933        if (!canStartArcUpdateAction(info.getLogicalAddress(), enabled)) {
934            Slog.w(TAG, "Failed to start arc action; ARC configuration check failed.");
935            if (enabled && !isConnectedToArcPort(info.getPhysicalAddress())) {
936                displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
937            }
938            return;
939        }
940
941        // Terminate opposite action and start action if not exist.
942        if (enabled) {
943            removeAction(RequestArcTerminationAction.class);
944            if (!hasAction(RequestArcInitiationAction.class)) {
945                addAndStartAction(new RequestArcInitiationAction(this, info.getLogicalAddress()));
946            }
947        } else {
948            removeAction(RequestArcInitiationAction.class);
949            if (!hasAction(RequestArcTerminationAction.class)) {
950                addAndStartAction(new RequestArcTerminationAction(this, info.getLogicalAddress()));
951            }
952        }
953    }
954
955    private boolean isDirectConnectAddress(int physicalAddress) {
956        return (physicalAddress & Constants.ROUTING_PATH_TOP_MASK) == physicalAddress;
957    }
958
959    void setAudioStatus(boolean mute, int volume) {
960        synchronized (mLock) {
961            mSystemAudioMute = mute;
962            mSystemAudioVolume = volume;
963            int maxVolume = mService.getAudioManager().getStreamMaxVolume(
964                    AudioManager.STREAM_MUSIC);
965            mService.setAudioStatus(mute,
966                    VolumeControlAction.scaleToCustomVolume(volume, maxVolume));
967            displayOsd(HdmiControlManager.OSD_MESSAGE_AVR_VOLUME_CHANGED,
968                    mute ? HdmiControlManager.AVR_VOLUME_MUTED : volume);
969        }
970    }
971
972    @ServiceThreadOnly
973    void changeVolume(int curVolume, int delta, int maxVolume) {
974        assertRunOnServiceThread();
975        if (delta == 0 || !isSystemAudioActivated()) {
976            return;
977        }
978
979        int targetVolume = curVolume + delta;
980        int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume);
981        synchronized (mLock) {
982            // If new volume is the same as current system audio volume, just ignore it.
983            // Note that UNKNOWN_VOLUME is not in range of cec volume scale.
984            if (cecVolume == mSystemAudioVolume) {
985                // Update tv volume with system volume value.
986                mService.setAudioStatus(false,
987                        VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume));
988                return;
989            }
990        }
991
992        List<VolumeControlAction> actions = getActions(VolumeControlAction.class);
993        if (actions.isEmpty()) {
994            addAndStartAction(new VolumeControlAction(this,
995                    getAvrDeviceInfo().getLogicalAddress(), delta > 0));
996        } else {
997            actions.get(0).handleVolumeChange(delta > 0);
998        }
999    }
1000
1001    @ServiceThreadOnly
1002    void changeMute(boolean mute) {
1003        assertRunOnServiceThread();
1004        HdmiLogger.debug("[A]:Change mute:%b", mute);
1005        synchronized (mLock) {
1006            if (mSystemAudioMute == mute) {
1007                HdmiLogger.debug("No need to change mute.");
1008                return;
1009            }
1010        }
1011        if (!isSystemAudioActivated()) {
1012            HdmiLogger.debug("[A]:System audio is not activated.");
1013            return;
1014        }
1015
1016        // Remove existing volume action.
1017        removeAction(VolumeControlAction.class);
1018        sendUserControlPressedAndReleased(getAvrDeviceInfo().getLogicalAddress(),
1019                mute ? HdmiCecKeycode.CEC_KEYCODE_MUTE_FUNCTION :
1020                        HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION);
1021    }
1022
1023    @Override
1024    @ServiceThreadOnly
1025    protected boolean handleInitiateArc(HdmiCecMessage message) {
1026        assertRunOnServiceThread();
1027
1028        if (!canStartArcUpdateAction(message.getSource(), true)) {
1029            if (getAvrDeviceInfo() == null) {
1030                // AVR may not have been discovered yet. Delay the message processing.
1031                mDelayedMessageBuffer.add(message);
1032                return true;
1033            }
1034            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
1035            if (!isConnectedToArcPort(message.getSource())) {
1036                displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
1037            }
1038            return true;
1039        }
1040
1041        // In case where <Initiate Arc> is started by <Request ARC Initiation>
1042        // need to clean up RequestArcInitiationAction.
1043        removeAction(RequestArcInitiationAction.class);
1044        SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
1045                message.getSource(), true);
1046        addAndStartAction(action);
1047        return true;
1048    }
1049
1050    private boolean canStartArcUpdateAction(int avrAddress, boolean shouldCheckArcFeatureEnabled) {
1051        HdmiDeviceInfo avr = getAvrDeviceInfo();
1052        if (avr != null
1053                && (avrAddress == avr.getLogicalAddress())
1054                && isConnectedToArcPort(avr.getPhysicalAddress())
1055                && isDirectConnectAddress(avr.getPhysicalAddress())) {
1056            if (shouldCheckArcFeatureEnabled) {
1057                return isArcFeatureEnabled();
1058            } else {
1059                return true;
1060            }
1061        } else {
1062            return false;
1063        }
1064    }
1065
1066    @Override
1067    @ServiceThreadOnly
1068    protected boolean handleTerminateArc(HdmiCecMessage message) {
1069        assertRunOnServiceThread();
1070        // In cast of termination, do not check ARC configuration in that AVR device
1071        // might be removed already.
1072
1073        // In case where <Terminate Arc> is started by <Request ARC Termination>
1074        // need to clean up RequestArcInitiationAction.
1075        removeAction(RequestArcTerminationAction.class);
1076        SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
1077                message.getSource(), false);
1078        addAndStartAction(action);
1079        return true;
1080    }
1081
1082    @Override
1083    @ServiceThreadOnly
1084    protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
1085        assertRunOnServiceThread();
1086        if (!isMessageForSystemAudio(message)) {
1087            if (getAvrDeviceInfo() == null) {
1088                // AVR may not have been discovered yet. Delay the message processing.
1089                mDelayedMessageBuffer.add(message);
1090                return true;
1091            }
1092            HdmiLogger.warning("Invalid <Set System Audio Mode> message:" + message);
1093            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
1094            return true;
1095        }
1096        SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this,
1097                message.getSource(), HdmiUtils.parseCommandParamSystemAudioStatus(message), null);
1098        addAndStartAction(action);
1099        return true;
1100    }
1101
1102    @Override
1103    @ServiceThreadOnly
1104    protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
1105        assertRunOnServiceThread();
1106        if (!isMessageForSystemAudio(message)) {
1107            HdmiLogger.warning("Invalid <System Audio Mode Status> message:" + message);
1108            // Ignore this message.
1109            return true;
1110        }
1111        setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message), true);
1112        return true;
1113    }
1114
1115    // Seq #53
1116    @Override
1117    @ServiceThreadOnly
1118    protected boolean handleRecordTvScreen(HdmiCecMessage message) {
1119        List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class);
1120        if (!actions.isEmpty()) {
1121            // Assumes only one OneTouchRecordAction.
1122            OneTouchRecordAction action = actions.get(0);
1123            if (action.getRecorderAddress() != message.getSource()) {
1124                announceOneTouchRecordResult(
1125                        message.getSource(),
1126                        HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS);
1127            }
1128            return super.handleRecordTvScreen(message);
1129        }
1130
1131        int recorderAddress = message.getSource();
1132        byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress);
1133        int reason = startOneTouchRecord(recorderAddress, recordSource);
1134        if (reason != Constants.ABORT_NO_ERROR) {
1135            mService.maySendFeatureAbortCommand(message, reason);
1136        }
1137        return true;
1138    }
1139
1140    @Override
1141    protected boolean handleTimerClearedStatus(HdmiCecMessage message) {
1142        byte[] params = message.getParams();
1143        int timerClearedStatusData = params[0] & 0xFF;
1144        announceTimerRecordingResult(message.getSource(), timerClearedStatusData);
1145        return true;
1146    }
1147
1148    void announceOneTouchRecordResult(int recorderAddress, int result) {
1149        mService.invokeOneTouchRecordResult(recorderAddress, result);
1150    }
1151
1152    void announceTimerRecordingResult(int recorderAddress, int result) {
1153        mService.invokeTimerRecordingResult(recorderAddress, result);
1154    }
1155
1156    void announceClearTimerRecordingResult(int recorderAddress, int result) {
1157        mService.invokeClearTimerRecordingResult(recorderAddress, result);
1158    }
1159
1160    private boolean isMessageForSystemAudio(HdmiCecMessage message) {
1161        return mService.isControlEnabled()
1162                && message.getSource() == Constants.ADDR_AUDIO_SYSTEM
1163                && (message.getDestination() == Constants.ADDR_TV
1164                        || message.getDestination() == Constants.ADDR_BROADCAST)
1165                && getAvrDeviceInfo() != null;
1166    }
1167
1168    /**
1169     * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same
1170     * logical address as new device info's.
1171     *
1172     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
1173     *
1174     * @param deviceInfo a new {@link HdmiDeviceInfo} to be added.
1175     * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo}
1176     *         that has the same logical address as new one has.
1177     */
1178    @ServiceThreadOnly
1179    private HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) {
1180        assertRunOnServiceThread();
1181        HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress());
1182        if (oldDeviceInfo != null) {
1183            removeDeviceInfo(deviceInfo.getId());
1184        }
1185        mDeviceInfos.append(deviceInfo.getId(), deviceInfo);
1186        updateSafeDeviceInfoList();
1187        return oldDeviceInfo;
1188    }
1189
1190    /**
1191     * Remove a device info corresponding to the given {@code logicalAddress}.
1192     * It returns removed {@link HdmiDeviceInfo} if exists.
1193     *
1194     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
1195     *
1196     * @param id id of device to be removed
1197     * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null}
1198     */
1199    @ServiceThreadOnly
1200    private HdmiDeviceInfo removeDeviceInfo(int id) {
1201        assertRunOnServiceThread();
1202        HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id);
1203        if (deviceInfo != null) {
1204            mDeviceInfos.remove(id);
1205        }
1206        updateSafeDeviceInfoList();
1207        return deviceInfo;
1208    }
1209
1210    /**
1211     * Return a list of all {@link HdmiDeviceInfo}.
1212     *
1213     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
1214     * This is not thread-safe. For thread safety, call {@link #getSafeExternalInputsLocked} which
1215     * does not include local device.
1216     */
1217    @ServiceThreadOnly
1218    List<HdmiDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) {
1219        assertRunOnServiceThread();
1220        if (includeLocalDevice) {
1221            return HdmiUtils.sparseArrayToList(mDeviceInfos);
1222        } else {
1223            ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
1224            for (int i = 0; i < mDeviceInfos.size(); ++i) {
1225                HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
1226                if (!isLocalDeviceAddress(info.getLogicalAddress())) {
1227                    infoList.add(info);
1228                }
1229            }
1230            return infoList;
1231        }
1232    }
1233
1234    /**
1235     * Return external input devices.
1236     */
1237    List<HdmiDeviceInfo> getSafeExternalInputsLocked() {
1238        return mSafeExternalInputs;
1239    }
1240
1241    @ServiceThreadOnly
1242    private void updateSafeDeviceInfoList() {
1243        assertRunOnServiceThread();
1244        List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
1245        List<HdmiDeviceInfo> externalInputs = getInputDevices();
1246        synchronized (mLock) {
1247            mSafeAllDeviceInfos = copiedDevices;
1248            mSafeExternalInputs = externalInputs;
1249        }
1250    }
1251
1252    /**
1253     * Return a list of external cec input (source) devices.
1254     *
1255     * <p>Note that this effectively excludes non-source devices like system audio,
1256     * secondary TV.
1257     */
1258    private List<HdmiDeviceInfo> getInputDevices() {
1259        ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
1260        for (int i = 0; i < mDeviceInfos.size(); ++i) {
1261            HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
1262            if (isLocalDeviceAddress(info.getLogicalAddress())) {
1263                continue;
1264            }
1265            if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) {
1266                infoList.add(info);
1267            }
1268        }
1269        return infoList;
1270    }
1271
1272    // Check if we are hiding CEC devices connected to a legacy (non-CEC) switch.
1273    // Returns true if the policy is set to true, and the device to check does not have
1274    // a parent CEC device (which should be the CEC-enabled switch) in the list.
1275    private boolean hideDevicesBehindLegacySwitch(HdmiDeviceInfo info) {
1276        return HdmiConfig.HIDE_DEVICES_BEHIND_LEGACY_SWITCH
1277                && !isConnectedToCecSwitch(info.getPhysicalAddress(), mCecSwitches);
1278    }
1279
1280    private static boolean isConnectedToCecSwitch(int path, Collection<Integer> switches) {
1281        for (int switchPath : switches) {
1282            if (isParentPath(switchPath, path)) {
1283                return true;
1284            }
1285        }
1286        return false;
1287    }
1288
1289    private static boolean isParentPath(int parentPath, int childPath) {
1290        // (A000, AB00) (AB00, ABC0), (ABC0, ABCD)
1291        // If child's last non-zero nibble is removed, the result equals to the parent.
1292        for (int i = 0; i <= 12; i += 4) {
1293            int nibble = (childPath >> i) & 0xF;
1294            if (nibble != 0) {
1295                int parentNibble = (parentPath >> i) & 0xF;
1296                return parentNibble == 0 && (childPath >> i+4) == (parentPath >> i+4);
1297            }
1298        }
1299        return false;
1300    }
1301
1302    private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) {
1303        if (!hideDevicesBehindLegacySwitch(info)) {
1304            mService.invokeDeviceEventListeners(info, status);
1305        }
1306    }
1307
1308    private boolean isLocalDeviceAddress(int address) {
1309        return mLocalDeviceAddresses.contains(address);
1310    }
1311
1312    @ServiceThreadOnly
1313    HdmiDeviceInfo getAvrDeviceInfo() {
1314        assertRunOnServiceThread();
1315        return getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
1316    }
1317
1318    /**
1319     * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}.
1320     *
1321     * This is not thread-safe. For thread safety, call {@link #getSafeCecDeviceInfo(int)}.
1322     *
1323     * @param logicalAddress logical address of the device to be retrieved
1324     * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
1325     *         Returns null if no logical address matched
1326     */
1327    @ServiceThreadOnly
1328    HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) {
1329        assertRunOnServiceThread();
1330        return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress));
1331    }
1332
1333    boolean hasSystemAudioDevice() {
1334        return getSafeAvrDeviceInfo() != null;
1335    }
1336
1337    HdmiDeviceInfo getSafeAvrDeviceInfo() {
1338        return getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
1339    }
1340
1341    /**
1342     * Thread safe version of {@link #getCecDeviceInfo(int)}.
1343     *
1344     * @param logicalAddress logical address to be retrieved
1345     * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
1346     *         Returns null if no logical address matched
1347     */
1348    HdmiDeviceInfo getSafeCecDeviceInfo(int logicalAddress) {
1349        synchronized (mLock) {
1350            for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
1351                if (info.isCecDevice() && info.getLogicalAddress() == logicalAddress) {
1352                    return info;
1353                }
1354            }
1355            return null;
1356        }
1357    }
1358
1359    List<HdmiDeviceInfo> getSafeCecDevicesLocked() {
1360        ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
1361        for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
1362            if (isLocalDeviceAddress(info.getLogicalAddress())) {
1363                continue;
1364            }
1365            infoList.add(info);
1366        }
1367        return infoList;
1368    }
1369
1370    /**
1371     * Called when a device is newly added or a new device is detected or
1372     * existing device is updated.
1373     *
1374     * @param info device info of a new device.
1375     */
1376    @ServiceThreadOnly
1377    final void addCecDevice(HdmiDeviceInfo info) {
1378        assertRunOnServiceThread();
1379        HdmiDeviceInfo old = addDeviceInfo(info);
1380        if (info.getLogicalAddress() == mAddress) {
1381            // The addition of TV device itself should not be notified.
1382            return;
1383        }
1384        if (old == null) {
1385            invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
1386        } else if (!old.equals(info)) {
1387            invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
1388            invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
1389        }
1390    }
1391
1392    /**
1393     * Called when a device is removed or removal of device is detected.
1394     *
1395     * @param address a logical address of a device to be removed
1396     */
1397    @ServiceThreadOnly
1398    final void removeCecDevice(int address) {
1399        assertRunOnServiceThread();
1400        HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
1401
1402        mCecMessageCache.flushMessagesFrom(address);
1403        invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
1404    }
1405
1406    @ServiceThreadOnly
1407    void handleRemoveActiveRoutingPath(int path) {
1408        assertRunOnServiceThread();
1409        // Seq #23
1410        if (isTailOfActivePath(path, getActivePath())) {
1411            int newPath = mService.portIdToPath(getActivePortId());
1412            startRoutingControl(getActivePath(), newPath, true, null);
1413        }
1414    }
1415
1416    /**
1417     * Launch routing control process.
1418     *
1419     * @param routingForBootup true if routing control is initiated due to One Touch Play
1420     *        or TV power on
1421     */
1422    @ServiceThreadOnly
1423    void launchRoutingControl(boolean routingForBootup) {
1424        assertRunOnServiceThread();
1425        // Seq #24
1426        if (getActivePortId() != Constants.INVALID_PORT_ID) {
1427            if (!routingForBootup && !isProhibitMode()) {
1428                int newPath = mService.portIdToPath(getActivePortId());
1429                setActivePath(newPath);
1430                startRoutingControl(getActivePath(), newPath, routingForBootup, null);
1431            }
1432        } else {
1433            int activePath = mService.getPhysicalAddress();
1434            setActivePath(activePath);
1435            if (!routingForBootup
1436                    && !mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) {
1437                mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(mAddress,
1438                        activePath));
1439            }
1440        }
1441    }
1442
1443    /**
1444     * Returns the {@link HdmiDeviceInfo} instance whose physical address matches
1445     * the given routing path. CEC devices use routing path for its physical address to
1446     * describe the hierarchy of the devices in the network.
1447     *
1448     * @param path routing path or physical address
1449     * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null
1450     */
1451    @ServiceThreadOnly
1452    final HdmiDeviceInfo getDeviceInfoByPath(int path) {
1453        assertRunOnServiceThread();
1454        for (HdmiDeviceInfo info : getDeviceInfoList(false)) {
1455            if (info.getPhysicalAddress() == path) {
1456                return info;
1457            }
1458        }
1459        return null;
1460    }
1461
1462    /**
1463     * Whether a device of the specified physical address and logical address exists
1464     * in a device info list. However, both are minimal condition and it could
1465     * be different device from the original one.
1466     *
1467     * @param logicalAddress logical address of a device to be searched
1468     * @param physicalAddress physical address of a device to be searched
1469     * @return true if exist; otherwise false
1470     */
1471    @ServiceThreadOnly
1472    private boolean isInDeviceList(int logicalAddress, int physicalAddress) {
1473        assertRunOnServiceThread();
1474        HdmiDeviceInfo device = getCecDeviceInfo(logicalAddress);
1475        if (device == null) {
1476            return false;
1477        }
1478        return device.getPhysicalAddress() == physicalAddress;
1479    }
1480
1481    @Override
1482    @ServiceThreadOnly
1483    void onHotplug(int portId, boolean connected) {
1484        assertRunOnServiceThread();
1485
1486        if (!connected) {
1487            removeCecSwitches(portId);
1488        }
1489        // Tv device will have permanent HotplugDetectionAction.
1490        List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class);
1491        if (!hotplugActions.isEmpty()) {
1492            // Note that hotplug action is single action running on a machine.
1493            // "pollAllDevicesNow" cleans up timer and start poll action immediately.
1494            // It covers seq #40, #43.
1495            hotplugActions.get(0).pollAllDevicesNow();
1496        }
1497    }
1498
1499    private void removeCecSwitches(int portId) {
1500        Iterator<Integer> it = mCecSwitches.iterator();
1501        while (!it.hasNext()) {
1502            int path = it.next();
1503            if (pathToPortId(path) == portId) {
1504                it.remove();
1505            }
1506        }
1507    }
1508
1509    @ServiceThreadOnly
1510    void setAutoDeviceOff(boolean enabled) {
1511        assertRunOnServiceThread();
1512        mAutoDeviceOff = enabled;
1513    }
1514
1515    @ServiceThreadOnly
1516    void setAutoWakeup(boolean enabled) {
1517        assertRunOnServiceThread();
1518        mAutoWakeup = enabled;
1519    }
1520
1521    @ServiceThreadOnly
1522    boolean getAutoWakeup() {
1523        assertRunOnServiceThread();
1524        return mAutoWakeup;
1525    }
1526
1527    @Override
1528    @ServiceThreadOnly
1529    protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
1530        super.disableDevice(initiatedByCec, callback);
1531        assertRunOnServiceThread();
1532        mService.unregisterTvInputCallback(mTvInputCallback);
1533        // Remove any repeated working actions.
1534        // HotplugDetectionAction will be reinstated during the wake up process.
1535        // HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
1536        //     LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery().
1537        removeAction(DeviceDiscoveryAction.class);
1538        removeAction(HotplugDetectionAction.class);
1539        removeAction(PowerStatusMonitorAction.class);
1540        // Remove recording actions.
1541        removeAction(OneTouchRecordAction.class);
1542        removeAction(TimerRecordingAction.class);
1543
1544        disableSystemAudioIfExist();
1545        disableArcIfExist();
1546        clearDeviceInfoList();
1547        checkIfPendingActionsCleared();
1548    }
1549
1550    @ServiceThreadOnly
1551    private void disableSystemAudioIfExist() {
1552        assertRunOnServiceThread();
1553        if (getAvrDeviceInfo() == null) {
1554            return;
1555        }
1556
1557        // Seq #31.
1558        removeAction(SystemAudioActionFromAvr.class);
1559        removeAction(SystemAudioActionFromTv.class);
1560        removeAction(SystemAudioAutoInitiationAction.class);
1561        removeAction(SystemAudioStatusAction.class);
1562        removeAction(VolumeControlAction.class);
1563
1564        // Turn off the mode but do not write it the settings, so that the next time TV powers on
1565        // the system audio mode setting can be restored automatically.
1566        setSystemAudioMode(false, false);
1567    }
1568
1569    @ServiceThreadOnly
1570    private void disableArcIfExist() {
1571        assertRunOnServiceThread();
1572        HdmiDeviceInfo avr = getAvrDeviceInfo();
1573        if (avr == null) {
1574            return;
1575        }
1576
1577        // Seq #44.
1578        removeAction(RequestArcInitiationAction.class);
1579        if (!hasAction(RequestArcTerminationAction.class) && isArcEstabilished()) {
1580            addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress()));
1581        }
1582    }
1583
1584    @Override
1585    @ServiceThreadOnly
1586    protected void onStandby(boolean initiatedByCec) {
1587        assertRunOnServiceThread();
1588        // Seq #11
1589        if (!mService.isControlEnabled()) {
1590            return;
1591        }
1592        if (!initiatedByCec && mAutoDeviceOff) {
1593            mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(
1594                    mAddress, Constants.ADDR_BROADCAST));
1595        }
1596    }
1597
1598    boolean isProhibitMode() {
1599        return mService.isProhibitMode();
1600    }
1601
1602    boolean isPowerStandbyOrTransient() {
1603        return mService.isPowerStandbyOrTransient();
1604    }
1605
1606    @ServiceThreadOnly
1607    void displayOsd(int messageId) {
1608        assertRunOnServiceThread();
1609        mService.displayOsd(messageId);
1610    }
1611
1612    @ServiceThreadOnly
1613    void displayOsd(int messageId, int extra) {
1614        assertRunOnServiceThread();
1615        mService.displayOsd(messageId, extra);
1616    }
1617
1618    // Seq #54 and #55
1619    @ServiceThreadOnly
1620    int startOneTouchRecord(int recorderAddress, byte[] recordSource) {
1621        assertRunOnServiceThread();
1622        if (!mService.isControlEnabled()) {
1623            Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1624            announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
1625            return Constants.ABORT_NOT_IN_CORRECT_MODE;
1626        }
1627
1628        if (!checkRecorder(recorderAddress)) {
1629            Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1630            announceOneTouchRecordResult(recorderAddress,
1631                    ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
1632            return Constants.ABORT_NOT_IN_CORRECT_MODE;
1633        }
1634
1635        if (!checkRecordSource(recordSource)) {
1636            Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1637            announceOneTouchRecordResult(recorderAddress,
1638                    ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN);
1639            return Constants.ABORT_CANNOT_PROVIDE_SOURCE;
1640        }
1641
1642        addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource));
1643        Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:"
1644                + Arrays.toString(recordSource));
1645        return Constants.ABORT_NO_ERROR;
1646    }
1647
1648    @ServiceThreadOnly
1649    void stopOneTouchRecord(int recorderAddress) {
1650        assertRunOnServiceThread();
1651        if (!mService.isControlEnabled()) {
1652            Slog.w(TAG, "Can not stop one touch record. CEC control is disabled.");
1653            announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
1654            return;
1655        }
1656
1657        if (!checkRecorder(recorderAddress)) {
1658            Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1659            announceOneTouchRecordResult(recorderAddress,
1660                    ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
1661            return;
1662        }
1663
1664        // Remove one touch record action so that other one touch record can be started.
1665        removeAction(OneTouchRecordAction.class);
1666        mService.sendCecCommand(HdmiCecMessageBuilder.buildRecordOff(mAddress, recorderAddress));
1667        Slog.i(TAG, "Stop [One Touch Record]-Target:" + recorderAddress);
1668    }
1669
1670    private boolean checkRecorder(int recorderAddress) {
1671        HdmiDeviceInfo device = getCecDeviceInfo(recorderAddress);
1672        return (device != null)
1673                && (HdmiUtils.getTypeFromAddress(recorderAddress)
1674                        == HdmiDeviceInfo.DEVICE_RECORDER);
1675    }
1676
1677    private boolean checkRecordSource(byte[] recordSource) {
1678        return (recordSource != null) && HdmiRecordSources.checkRecordSource(recordSource);
1679    }
1680
1681    @ServiceThreadOnly
1682    void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
1683        assertRunOnServiceThread();
1684        if (!mService.isControlEnabled()) {
1685            Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1686            announceTimerRecordingResult(recorderAddress,
1687                    TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED);
1688            return;
1689        }
1690
1691        if (!checkRecorder(recorderAddress)) {
1692            Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1693            announceTimerRecordingResult(recorderAddress,
1694                    TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION);
1695            return;
1696        }
1697
1698        if (!checkTimerRecordingSource(sourceType, recordSource)) {
1699            Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1700            announceTimerRecordingResult(
1701                    recorderAddress,
1702                    TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE);
1703            return;
1704        }
1705
1706        addAndStartAction(
1707                new TimerRecordingAction(this, recorderAddress, sourceType, recordSource));
1708        Slog.i(TAG, "Start [Timer Recording]-Target:" + recorderAddress + ", SourceType:"
1709                + sourceType + ", RecordSource:" + Arrays.toString(recordSource));
1710    }
1711
1712    private boolean checkTimerRecordingSource(int sourceType, byte[] recordSource) {
1713        return (recordSource != null)
1714                && HdmiTimerRecordSources.checkTimerRecordSource(sourceType, recordSource);
1715    }
1716
1717    @ServiceThreadOnly
1718    void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
1719        assertRunOnServiceThread();
1720        if (!mService.isControlEnabled()) {
1721            Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1722            announceClearTimerRecordingResult(recorderAddress, CLEAR_TIMER_STATUS_CEC_DISABLE);
1723            return;
1724        }
1725
1726        if (!checkRecorder(recorderAddress)) {
1727            Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1728            announceClearTimerRecordingResult(recorderAddress,
1729                    CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION);
1730            return;
1731        }
1732
1733        if (!checkTimerRecordingSource(sourceType, recordSource)) {
1734            Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1735            announceClearTimerRecordingResult(recorderAddress,
1736                    CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1737            return;
1738        }
1739
1740        sendClearTimerMessage(recorderAddress, sourceType, recordSource);
1741    }
1742
1743    private void sendClearTimerMessage(final int recorderAddress, int sourceType,
1744            byte[] recordSource) {
1745        HdmiCecMessage message = null;
1746        switch (sourceType) {
1747            case TIMER_RECORDING_TYPE_DIGITAL:
1748                message = HdmiCecMessageBuilder.buildClearDigitalTimer(mAddress, recorderAddress,
1749                        recordSource);
1750                break;
1751            case TIMER_RECORDING_TYPE_ANALOGUE:
1752                message = HdmiCecMessageBuilder.buildClearAnalogueTimer(mAddress, recorderAddress,
1753                        recordSource);
1754                break;
1755            case TIMER_RECORDING_TYPE_EXTERNAL:
1756                message = HdmiCecMessageBuilder.buildClearExternalTimer(mAddress, recorderAddress,
1757                        recordSource);
1758                break;
1759            default:
1760                Slog.w(TAG, "Invalid source type:" + recorderAddress);
1761                announceClearTimerRecordingResult(recorderAddress,
1762                        CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1763                return;
1764
1765        }
1766        mService.sendCecCommand(message, new SendMessageCallback() {
1767            @Override
1768            public void onSendCompleted(int error) {
1769                if (error != Constants.SEND_RESULT_SUCCESS) {
1770                    announceClearTimerRecordingResult(recorderAddress,
1771                            CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1772                }
1773            }
1774        });
1775    }
1776
1777    void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
1778        HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
1779        if (info == null) {
1780            Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress);
1781            return;
1782        }
1783
1784        if (info.getDevicePowerStatus() == newPowerStatus) {
1785            return;
1786        }
1787
1788        HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus);
1789        // addDeviceInfo replaces old device info with new one if exists.
1790        addDeviceInfo(newInfo);
1791
1792        invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
1793    }
1794
1795    @Override
1796    protected boolean handleMenuStatus(HdmiCecMessage message) {
1797        // Do nothing and just return true not to prevent from responding <Feature Abort>.
1798        return true;
1799    }
1800
1801    @Override
1802    protected void sendStandby(int deviceId) {
1803        HdmiDeviceInfo targetDevice = mDeviceInfos.get(deviceId);
1804        if (targetDevice == null) {
1805            return;
1806        }
1807        int targetAddress = targetDevice.getLogicalAddress();
1808        mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
1809    }
1810
1811    @ServiceThreadOnly
1812    void processAllDelayedMessages() {
1813        assertRunOnServiceThread();
1814        mDelayedMessageBuffer.processAllMessages();
1815    }
1816
1817    @ServiceThreadOnly
1818    void processDelayedMessages(int address) {
1819        assertRunOnServiceThread();
1820        mDelayedMessageBuffer.processMessagesForDevice(address);
1821    }
1822
1823    @ServiceThreadOnly
1824    void processDelayedActiveSource(int address) {
1825        assertRunOnServiceThread();
1826        mDelayedMessageBuffer.processActiveSource(address);
1827    }
1828
1829    @Override
1830    protected void dump(final IndentingPrintWriter pw) {
1831        super.dump(pw);
1832        pw.println("mArcEstablished: " + mArcEstablished);
1833        pw.println("mArcFeatureEnabled: " + mArcFeatureEnabled);
1834        pw.println("mSystemAudioActivated: " + mSystemAudioActivated);
1835        pw.println("mSystemAudioMute: " + mSystemAudioMute);
1836        pw.println("mAutoDeviceOff: " + mAutoDeviceOff);
1837        pw.println("mAutoWakeup: " + mAutoWakeup);
1838        pw.println("mSkipRoutingControl: " + mSkipRoutingControl);
1839        pw.println("mPrevPortId: " + mPrevPortId);
1840        pw.println("CEC devices:");
1841        pw.increaseIndent();
1842        for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
1843            pw.println(info);
1844        }
1845        pw.decreaseIndent();
1846    }
1847}
1848