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