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