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