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