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        boolean mute = HdmiUtils.isAudioStatusMute(message);
702        int volume = HdmiUtils.getAudioStatusVolume(message);
703        setAudioStatus(mute, volume);
704        return true;
705    }
706
707    @Override
708    @ServiceThreadOnly
709    protected boolean handleTextViewOn(HdmiCecMessage message) {
710        assertRunOnServiceThread();
711
712        // Note that <Text View On> (and <Image View On>) command won't be handled here in
713        // most cases. A dedicated microcontroller should be in charge while Android system
714        // is in sleep mode, and the command need not be passed up to this service.
715        // The only situation where the command reaches this handler is that sleep mode is
716        // implemented in such a way that Android system is not really put to standby mode
717        // but only the display is set to blank. Then the command leads to the effect of
718        // turning on the display by the invocation of PowerManager.wakeUp().
719        if (mService.isPowerStandbyOrTransient() && mAutoWakeup) {
720            mService.wakeUp();
721        }
722        return true;
723    }
724
725    @Override
726    @ServiceThreadOnly
727    protected boolean handleImageViewOn(HdmiCecMessage message) {
728        assertRunOnServiceThread();
729        // Currently, it's the same as <Text View On>.
730        return handleTextViewOn(message);
731    }
732
733    @Override
734    @ServiceThreadOnly
735    protected boolean handleSetOsdName(HdmiCecMessage message) {
736        int source = message.getSource();
737        HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source);
738        // If the device is not in device list, ignore it.
739        if (deviceInfo == null) {
740            Slog.e(TAG, "No source device info for <Set Osd Name>." + message);
741            return true;
742        }
743        String osdName = null;
744        try {
745            osdName = new String(message.getParams(), "US-ASCII");
746        } catch (UnsupportedEncodingException e) {
747            Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e);
748            return true;
749        }
750
751        if (deviceInfo.getDisplayName().equals(osdName)) {
752            Slog.i(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
753            return true;
754        }
755
756        addCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
757                deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(),
758                deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName));
759        return true;
760    }
761
762    @ServiceThreadOnly
763    private void launchDeviceDiscovery() {
764        assertRunOnServiceThread();
765        clearDeviceInfoList();
766        DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
767                new DeviceDiscoveryCallback() {
768                    @Override
769                    public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
770                        for (HdmiDeviceInfo info : deviceInfos) {
771                            addCecDevice(info);
772                        }
773
774                        // Since we removed all devices when it's start and
775                        // device discovery action does not poll local devices,
776                        // we should put device info of local device manually here
777                        for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
778                            addCecDevice(device.getDeviceInfo());
779                        }
780
781                        mSelectRequestBuffer.process();
782                        resetSelectRequestBuffer();
783
784                        addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
785                        addAndStartAction(new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this));
786
787                        HdmiDeviceInfo avr = getAvrDeviceInfo();
788                        if (avr != null) {
789                            onNewAvrAdded(avr);
790                        } else {
791                            setSystemAudioMode(false);
792                        }
793                    }
794                });
795        addAndStartAction(action);
796    }
797
798    @ServiceThreadOnly
799    void onNewAvrAdded(HdmiDeviceInfo avr) {
800        assertRunOnServiceThread();
801        addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
802        if (isConnected(avr.getPortId()) && isArcFeatureEnabled(avr.getPortId())
803                && !hasAction(SetArcTransmissionStateAction.class)) {
804            startArcAction(true);
805        }
806    }
807
808    // Clear all device info.
809    @ServiceThreadOnly
810    private void clearDeviceInfoList() {
811        assertRunOnServiceThread();
812        for (HdmiDeviceInfo info : mSafeExternalInputs) {
813            invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
814        }
815        mDeviceInfos.clear();
816        updateSafeDeviceInfoList();
817    }
818
819    @ServiceThreadOnly
820    // Seq #32
821    void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {
822        assertRunOnServiceThread();
823        if (!mService.isControlEnabled() || hasAction(DeviceDiscoveryAction.class)) {
824            setSystemAudioMode(false);
825            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
826            return;
827        }
828        HdmiDeviceInfo avr = getAvrDeviceInfo();
829        if (avr == null) {
830            setSystemAudioMode(false);
831            invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
832            return;
833        }
834
835        addAndStartAction(
836                new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback));
837    }
838
839    // # Seq 25
840    void setSystemAudioMode(boolean on) {
841        if (!isSystemAudioControlFeatureEnabled() && on) {
842            HdmiLogger.debug("Cannot turn on system audio mode "
843                    + "because the System Audio Control feature is disabled.");
844            return;
845        }
846        HdmiLogger.debug("System Audio Mode change[old:%b new:%b]", mSystemAudioActivated, on);
847        updateAudioManagerForSystemAudio(on);
848        synchronized (mLock) {
849            if (mSystemAudioActivated != on) {
850                mSystemAudioActivated = on;
851                mService.announceSystemAudioModeChange(on);
852            }
853            startArcAction(on);
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        if (!isSystemAudioActivated()) {
1007            return;
1008        }
1009        synchronized (mLock) {
1010            mSystemAudioMute = mute;
1011            mSystemAudioVolume = volume;
1012            int maxVolume = mService.getAudioManager().getStreamMaxVolume(
1013                    AudioManager.STREAM_MUSIC);
1014            mService.setAudioStatus(mute,
1015                    VolumeControlAction.scaleToCustomVolume(volume, maxVolume));
1016            displayOsd(HdmiControlManager.OSD_MESSAGE_AVR_VOLUME_CHANGED,
1017                    mute ? HdmiControlManager.AVR_VOLUME_MUTED : volume);
1018        }
1019    }
1020
1021    @ServiceThreadOnly
1022    void changeVolume(int curVolume, int delta, int maxVolume) {
1023        assertRunOnServiceThread();
1024        if (getAvrDeviceInfo() == null) {
1025            // On initialization process, getAvrDeviceInfo() may return null and cause exception
1026            return;
1027        }
1028        if (delta == 0 || !isSystemAudioActivated()) {
1029            return;
1030        }
1031
1032        int targetVolume = curVolume + delta;
1033        int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume);
1034        synchronized (mLock) {
1035            // If new volume is the same as current system audio volume, just ignore it.
1036            // Note that UNKNOWN_VOLUME is not in range of cec volume scale.
1037            if (cecVolume == mSystemAudioVolume) {
1038                // Update tv volume with system volume value.
1039                mService.setAudioStatus(false,
1040                        VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume));
1041                return;
1042            }
1043        }
1044
1045        List<VolumeControlAction> actions = getActions(VolumeControlAction.class);
1046        if (actions.isEmpty()) {
1047            addAndStartAction(new VolumeControlAction(this,
1048                    getAvrDeviceInfo().getLogicalAddress(), delta > 0));
1049        } else {
1050            actions.get(0).handleVolumeChange(delta > 0);
1051        }
1052    }
1053
1054    @ServiceThreadOnly
1055    void changeMute(boolean mute) {
1056        assertRunOnServiceThread();
1057        if (getAvrDeviceInfo() == null) {
1058            // On initialization process, getAvrDeviceInfo() may return null and cause exception
1059            return;
1060        }
1061        HdmiLogger.debug("[A]:Change mute:%b", mute);
1062        synchronized (mLock) {
1063            if (mSystemAudioMute == mute) {
1064                HdmiLogger.debug("No need to change mute.");
1065                return;
1066            }
1067        }
1068        if (!isSystemAudioActivated()) {
1069            HdmiLogger.debug("[A]:System audio is not activated.");
1070            return;
1071        }
1072
1073        // Remove existing volume action.
1074        removeAction(VolumeControlAction.class);
1075        sendUserControlPressedAndReleased(getAvrDeviceInfo().getLogicalAddress(),
1076                HdmiCecKeycode.getMuteKey(mute));
1077    }
1078
1079    @Override
1080    @ServiceThreadOnly
1081    protected boolean handleInitiateArc(HdmiCecMessage message) {
1082        assertRunOnServiceThread();
1083
1084        if (!canStartArcUpdateAction(message.getSource(), true)) {
1085            if (getAvrDeviceInfo() == null) {
1086                // AVR may not have been discovered yet. Delay the message processing.
1087                mDelayedMessageBuffer.add(message);
1088                return true;
1089            }
1090            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
1091            if (!isConnectedToArcPort(message.getSource())) {
1092                displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
1093            }
1094            return true;
1095        }
1096
1097        // In case where <Initiate Arc> is started by <Request ARC Initiation>
1098        // need to clean up RequestArcInitiationAction.
1099        removeAction(RequestArcInitiationAction.class);
1100        SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
1101                message.getSource(), true);
1102        addAndStartAction(action);
1103        return true;
1104    }
1105
1106    private boolean canStartArcUpdateAction(int avrAddress, boolean enabled) {
1107        HdmiDeviceInfo avr = getAvrDeviceInfo();
1108        if (avr != null
1109                && (avrAddress == avr.getLogicalAddress())
1110                && isConnectedToArcPort(avr.getPhysicalAddress())
1111                && isDirectConnectAddress(avr.getPhysicalAddress())) {
1112            if (enabled) {
1113                return isConnected(avr.getPortId()) && isArcFeatureEnabled(avr.getPortId());
1114            } else {
1115                return true;
1116            }
1117        } else {
1118            return false;
1119        }
1120    }
1121
1122    @Override
1123    @ServiceThreadOnly
1124    protected boolean handleTerminateArc(HdmiCecMessage message) {
1125        assertRunOnServiceThread();
1126        if (mService .isPowerStandbyOrTransient()) {
1127            setArcStatus(false);
1128            return true;
1129        }
1130        // Do not check ARC configuration since the AVR might have been already removed.
1131        // Clean up RequestArcTerminationAction in case <Terminate Arc> was started by
1132        // <Request ARC Termination>.
1133        removeAction(RequestArcTerminationAction.class);
1134        SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
1135                message.getSource(), false);
1136        addAndStartAction(action);
1137        return true;
1138    }
1139
1140    @Override
1141    @ServiceThreadOnly
1142    protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
1143        assertRunOnServiceThread();
1144        boolean systemAudioStatus = HdmiUtils.parseCommandParamSystemAudioStatus(message);
1145        if (!isMessageForSystemAudio(message)) {
1146            if (getAvrDeviceInfo() == null) {
1147                // AVR may not have been discovered yet. Delay the message processing.
1148                mDelayedMessageBuffer.add(message);
1149            } else {
1150                HdmiLogger.warning("Invalid <Set System Audio Mode> message:" + message);
1151                mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
1152            }
1153            return true;
1154        } else if (systemAudioStatus && !isSystemAudioControlFeatureEnabled()) {
1155            HdmiLogger.debug("Ignoring <Set System Audio Mode> message "
1156                    + "because the System Audio Control feature is disabled: %s", message);
1157            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
1158            return true;
1159        }
1160        removeAction(SystemAudioAutoInitiationAction.class);
1161        SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this,
1162                message.getSource(), systemAudioStatus, null);
1163        addAndStartAction(action);
1164        return true;
1165    }
1166
1167    @Override
1168    @ServiceThreadOnly
1169    protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
1170        assertRunOnServiceThread();
1171        if (!isMessageForSystemAudio(message)) {
1172            HdmiLogger.warning("Invalid <System Audio Mode Status> message:" + message);
1173            // Ignore this message.
1174            return true;
1175        }
1176        setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message));
1177        return true;
1178    }
1179
1180    // Seq #53
1181    @Override
1182    @ServiceThreadOnly
1183    protected boolean handleRecordTvScreen(HdmiCecMessage message) {
1184        List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class);
1185        if (!actions.isEmpty()) {
1186            // Assumes only one OneTouchRecordAction.
1187            OneTouchRecordAction action = actions.get(0);
1188            if (action.getRecorderAddress() != message.getSource()) {
1189                announceOneTouchRecordResult(
1190                        message.getSource(),
1191                        HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS);
1192            }
1193            return super.handleRecordTvScreen(message);
1194        }
1195
1196        int recorderAddress = message.getSource();
1197        byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress);
1198        int reason = startOneTouchRecord(recorderAddress, recordSource);
1199        if (reason != Constants.ABORT_NO_ERROR) {
1200            mService.maySendFeatureAbortCommand(message, reason);
1201        }
1202        return true;
1203    }
1204
1205    @Override
1206    protected boolean handleTimerClearedStatus(HdmiCecMessage message) {
1207        byte[] params = message.getParams();
1208        int timerClearedStatusData = params[0] & 0xFF;
1209        announceTimerRecordingResult(message.getSource(), timerClearedStatusData);
1210        return true;
1211    }
1212
1213    void announceOneTouchRecordResult(int recorderAddress, int result) {
1214        mService.invokeOneTouchRecordResult(recorderAddress, result);
1215    }
1216
1217    void announceTimerRecordingResult(int recorderAddress, int result) {
1218        mService.invokeTimerRecordingResult(recorderAddress, result);
1219    }
1220
1221    void announceClearTimerRecordingResult(int recorderAddress, int result) {
1222        mService.invokeClearTimerRecordingResult(recorderAddress, result);
1223    }
1224
1225    private boolean isMessageForSystemAudio(HdmiCecMessage message) {
1226        return mService.isControlEnabled()
1227                && message.getSource() == Constants.ADDR_AUDIO_SYSTEM
1228                && (message.getDestination() == Constants.ADDR_TV
1229                        || message.getDestination() == Constants.ADDR_BROADCAST)
1230                && getAvrDeviceInfo() != null;
1231    }
1232
1233    /**
1234     * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same
1235     * logical address as new device info's.
1236     *
1237     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
1238     *
1239     * @param deviceInfo a new {@link HdmiDeviceInfo} to be added.
1240     * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo}
1241     *         that has the same logical address as new one has.
1242     */
1243    @ServiceThreadOnly
1244    private HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) {
1245        assertRunOnServiceThread();
1246        HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress());
1247        if (oldDeviceInfo != null) {
1248            removeDeviceInfo(deviceInfo.getId());
1249        }
1250        mDeviceInfos.append(deviceInfo.getId(), deviceInfo);
1251        updateSafeDeviceInfoList();
1252        return oldDeviceInfo;
1253    }
1254
1255    /**
1256     * Remove a device info corresponding to the given {@code logicalAddress}.
1257     * It returns removed {@link HdmiDeviceInfo} if exists.
1258     *
1259     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
1260     *
1261     * @param id id of device to be removed
1262     * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null}
1263     */
1264    @ServiceThreadOnly
1265    private HdmiDeviceInfo removeDeviceInfo(int id) {
1266        assertRunOnServiceThread();
1267        HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id);
1268        if (deviceInfo != null) {
1269            mDeviceInfos.remove(id);
1270        }
1271        updateSafeDeviceInfoList();
1272        return deviceInfo;
1273    }
1274
1275    /**
1276     * Return a list of all {@link HdmiDeviceInfo}.
1277     *
1278     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
1279     * This is not thread-safe. For thread safety, call {@link #getSafeExternalInputsLocked} which
1280     * does not include local device.
1281     */
1282    @ServiceThreadOnly
1283    List<HdmiDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) {
1284        assertRunOnServiceThread();
1285        if (includeLocalDevice) {
1286            return HdmiUtils.sparseArrayToList(mDeviceInfos);
1287        } else {
1288            ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
1289            for (int i = 0; i < mDeviceInfos.size(); ++i) {
1290                HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
1291                if (!isLocalDeviceAddress(info.getLogicalAddress())) {
1292                    infoList.add(info);
1293                }
1294            }
1295            return infoList;
1296        }
1297    }
1298
1299    /**
1300     * Return external input devices.
1301     */
1302    @GuardedBy("mLock")
1303    List<HdmiDeviceInfo> getSafeExternalInputsLocked() {
1304        return mSafeExternalInputs;
1305    }
1306
1307    @ServiceThreadOnly
1308    private void updateSafeDeviceInfoList() {
1309        assertRunOnServiceThread();
1310        List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
1311        List<HdmiDeviceInfo> externalInputs = getInputDevices();
1312        synchronized (mLock) {
1313            mSafeAllDeviceInfos = copiedDevices;
1314            mSafeExternalInputs = externalInputs;
1315        }
1316    }
1317
1318    /**
1319     * Return a list of external cec input (source) devices.
1320     *
1321     * <p>Note that this effectively excludes non-source devices like system audio,
1322     * secondary TV.
1323     */
1324    private List<HdmiDeviceInfo> getInputDevices() {
1325        ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
1326        for (int i = 0; i < mDeviceInfos.size(); ++i) {
1327            HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
1328            if (isLocalDeviceAddress(info.getLogicalAddress())) {
1329                continue;
1330            }
1331            if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) {
1332                infoList.add(info);
1333            }
1334        }
1335        return infoList;
1336    }
1337
1338    // Check if we are hiding CEC devices connected to a legacy (non-CEC) switch.
1339    // Returns true if the policy is set to true, and the device to check does not have
1340    // a parent CEC device (which should be the CEC-enabled switch) in the list.
1341    private boolean hideDevicesBehindLegacySwitch(HdmiDeviceInfo info) {
1342        return HdmiConfig.HIDE_DEVICES_BEHIND_LEGACY_SWITCH
1343                && !isConnectedToCecSwitch(info.getPhysicalAddress(), mCecSwitches);
1344    }
1345
1346    private static boolean isConnectedToCecSwitch(int path, Collection<Integer> switches) {
1347        for (int switchPath : switches) {
1348            if (isParentPath(switchPath, path)) {
1349                return true;
1350            }
1351        }
1352        return false;
1353    }
1354
1355    private static boolean isParentPath(int parentPath, int childPath) {
1356        // (A000, AB00) (AB00, ABC0), (ABC0, ABCD)
1357        // If child's last non-zero nibble is removed, the result equals to the parent.
1358        for (int i = 0; i <= 12; i += 4) {
1359            int nibble = (childPath >> i) & 0xF;
1360            if (nibble != 0) {
1361                int parentNibble = (parentPath >> i) & 0xF;
1362                return parentNibble == 0 && (childPath >> i+4) == (parentPath >> i+4);
1363            }
1364        }
1365        return false;
1366    }
1367
1368    private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) {
1369        if (!hideDevicesBehindLegacySwitch(info)) {
1370            mService.invokeDeviceEventListeners(info, status);
1371        }
1372    }
1373
1374    private boolean isLocalDeviceAddress(int address) {
1375        return mLocalDeviceAddresses.contains(address);
1376    }
1377
1378    @ServiceThreadOnly
1379    HdmiDeviceInfo getAvrDeviceInfo() {
1380        assertRunOnServiceThread();
1381        return getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
1382    }
1383
1384    /**
1385     * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}.
1386     *
1387     * This is not thread-safe. For thread safety, call {@link #getSafeCecDeviceInfo(int)}.
1388     *
1389     * @param logicalAddress logical address of the device to be retrieved
1390     * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
1391     *         Returns null if no logical address matched
1392     */
1393    @ServiceThreadOnly
1394    HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) {
1395        assertRunOnServiceThread();
1396        return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress));
1397    }
1398
1399    boolean hasSystemAudioDevice() {
1400        return getSafeAvrDeviceInfo() != null;
1401    }
1402
1403    HdmiDeviceInfo getSafeAvrDeviceInfo() {
1404        return getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
1405    }
1406
1407    /**
1408     * Thread safe version of {@link #getCecDeviceInfo(int)}.
1409     *
1410     * @param logicalAddress logical address to be retrieved
1411     * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
1412     *         Returns null if no logical address matched
1413     */
1414    HdmiDeviceInfo getSafeCecDeviceInfo(int logicalAddress) {
1415        synchronized (mLock) {
1416            for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
1417                if (info.isCecDevice() && info.getLogicalAddress() == logicalAddress) {
1418                    return info;
1419                }
1420            }
1421            return null;
1422        }
1423    }
1424
1425    @GuardedBy("mLock")
1426    List<HdmiDeviceInfo> getSafeCecDevicesLocked() {
1427        ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
1428        for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
1429            if (isLocalDeviceAddress(info.getLogicalAddress())) {
1430                continue;
1431            }
1432            infoList.add(info);
1433        }
1434        return infoList;
1435    }
1436
1437    /**
1438     * Called when a device is newly added or a new device is detected or
1439     * existing device is updated.
1440     *
1441     * @param info device info of a new device.
1442     */
1443    @ServiceThreadOnly
1444    final void addCecDevice(HdmiDeviceInfo info) {
1445        assertRunOnServiceThread();
1446        HdmiDeviceInfo old = addDeviceInfo(info);
1447        if (info.getLogicalAddress() == mAddress) {
1448            // The addition of TV device itself should not be notified.
1449            return;
1450        }
1451        if (old == null) {
1452            invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
1453        } else if (!old.equals(info)) {
1454            invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
1455            invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
1456        }
1457    }
1458
1459    /**
1460     * Called when a device is removed or removal of device is detected.
1461     *
1462     * @param address a logical address of a device to be removed
1463     */
1464    @ServiceThreadOnly
1465    final void removeCecDevice(int address) {
1466        assertRunOnServiceThread();
1467        HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
1468
1469        mCecMessageCache.flushMessagesFrom(address);
1470        invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
1471    }
1472
1473    @ServiceThreadOnly
1474    void handleRemoveActiveRoutingPath(int path) {
1475        assertRunOnServiceThread();
1476        // Seq #23
1477        if (isTailOfActivePath(path, getActivePath())) {
1478            int newPath = mService.portIdToPath(getActivePortId());
1479            startRoutingControl(getActivePath(), newPath, true, null);
1480        }
1481    }
1482
1483    /**
1484     * Launch routing control process.
1485     *
1486     * @param routingForBootup true if routing control is initiated due to One Touch Play
1487     *        or TV power on
1488     */
1489    @ServiceThreadOnly
1490    void launchRoutingControl(boolean routingForBootup) {
1491        assertRunOnServiceThread();
1492        // Seq #24
1493        if (getActivePortId() != Constants.INVALID_PORT_ID) {
1494            if (!routingForBootup && !isProhibitMode()) {
1495                int newPath = mService.portIdToPath(getActivePortId());
1496                setActivePath(newPath);
1497                startRoutingControl(getActivePath(), newPath, routingForBootup, null);
1498            }
1499        } else {
1500            int activePath = mService.getPhysicalAddress();
1501            setActivePath(activePath);
1502            if (!routingForBootup
1503                    && !mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) {
1504                mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(mAddress,
1505                        activePath));
1506            }
1507        }
1508    }
1509
1510    /**
1511     * Returns the {@link HdmiDeviceInfo} instance whose physical address matches
1512     * the given routing path. CEC devices use routing path for its physical address to
1513     * describe the hierarchy of the devices in the network.
1514     *
1515     * @param path routing path or physical address
1516     * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null
1517     */
1518    @ServiceThreadOnly
1519    final HdmiDeviceInfo getDeviceInfoByPath(int path) {
1520        assertRunOnServiceThread();
1521        for (HdmiDeviceInfo info : getDeviceInfoList(false)) {
1522            if (info.getPhysicalAddress() == path) {
1523                return info;
1524            }
1525        }
1526        return null;
1527    }
1528
1529    /**
1530     * Returns the {@link HdmiDeviceInfo} instance whose physical address matches
1531     * the given routing path. This is the version accessible safely from threads
1532     * other than service thread.
1533     *
1534     * @param path routing path or physical address
1535     * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null
1536     */
1537    HdmiDeviceInfo getSafeDeviceInfoByPath(int path) {
1538        synchronized (mLock) {
1539            for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
1540                if (info.getPhysicalAddress() == path) {
1541                    return info;
1542                }
1543            }
1544            return null;
1545        }
1546    }
1547
1548    /**
1549     * Whether a device of the specified physical address and logical address exists
1550     * in a device info list. However, both are minimal condition and it could
1551     * be different device from the original one.
1552     *
1553     * @param logicalAddress logical address of a device to be searched
1554     * @param physicalAddress physical address of a device to be searched
1555     * @return true if exist; otherwise false
1556     */
1557    @ServiceThreadOnly
1558    boolean isInDeviceList(int logicalAddress, int physicalAddress) {
1559        assertRunOnServiceThread();
1560        HdmiDeviceInfo device = getCecDeviceInfo(logicalAddress);
1561        if (device == null) {
1562            return false;
1563        }
1564        return device.getPhysicalAddress() == physicalAddress;
1565    }
1566
1567    @Override
1568    @ServiceThreadOnly
1569    void onHotplug(int portId, boolean connected) {
1570        assertRunOnServiceThread();
1571
1572        if (!connected) {
1573            removeCecSwitches(portId);
1574        }
1575        // Tv device will have permanent HotplugDetectionAction.
1576        List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class);
1577        if (!hotplugActions.isEmpty()) {
1578            // Note that hotplug action is single action running on a machine.
1579            // "pollAllDevicesNow" cleans up timer and start poll action immediately.
1580            // It covers seq #40, #43.
1581            hotplugActions.get(0).pollAllDevicesNow();
1582        }
1583    }
1584
1585    private void removeCecSwitches(int portId) {
1586        Iterator<Integer> it = mCecSwitches.iterator();
1587        while (!it.hasNext()) {
1588            int path = it.next();
1589            if (pathToPortId(path) == portId) {
1590                it.remove();
1591            }
1592        }
1593    }
1594
1595    @Override
1596    @ServiceThreadOnly
1597    void setAutoDeviceOff(boolean enabled) {
1598        assertRunOnServiceThread();
1599        mAutoDeviceOff = enabled;
1600    }
1601
1602    @ServiceThreadOnly
1603    void setAutoWakeup(boolean enabled) {
1604        assertRunOnServiceThread();
1605        mAutoWakeup = enabled;
1606    }
1607
1608    @ServiceThreadOnly
1609    boolean getAutoWakeup() {
1610        assertRunOnServiceThread();
1611        return mAutoWakeup;
1612    }
1613
1614    @Override
1615    @ServiceThreadOnly
1616    protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
1617        assertRunOnServiceThread();
1618        mService.unregisterTvInputCallback(mTvInputCallback);
1619        // Remove any repeated working actions.
1620        // HotplugDetectionAction will be reinstated during the wake up process.
1621        // HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
1622        //     LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery().
1623        removeAction(DeviceDiscoveryAction.class);
1624        removeAction(HotplugDetectionAction.class);
1625        removeAction(PowerStatusMonitorAction.class);
1626        // Remove recording actions.
1627        removeAction(OneTouchRecordAction.class);
1628        removeAction(TimerRecordingAction.class);
1629
1630        disableSystemAudioIfExist();
1631        disableArcIfExist();
1632
1633        super.disableDevice(initiatedByCec, callback);
1634        clearDeviceInfoList();
1635        getActiveSource().invalidate();
1636        setActivePath(Constants.INVALID_PHYSICAL_ADDRESS);
1637        checkIfPendingActionsCleared();
1638    }
1639
1640    @ServiceThreadOnly
1641    private void disableSystemAudioIfExist() {
1642        assertRunOnServiceThread();
1643        if (getAvrDeviceInfo() == null) {
1644            return;
1645        }
1646
1647        // Seq #31.
1648        removeAction(SystemAudioActionFromAvr.class);
1649        removeAction(SystemAudioActionFromTv.class);
1650        removeAction(SystemAudioAutoInitiationAction.class);
1651        removeAction(SystemAudioStatusAction.class);
1652        removeAction(VolumeControlAction.class);
1653    }
1654
1655    @ServiceThreadOnly
1656    private void disableArcIfExist() {
1657        assertRunOnServiceThread();
1658        HdmiDeviceInfo avr = getAvrDeviceInfo();
1659        if (avr == null) {
1660            return;
1661        }
1662
1663        // Seq #44.
1664        removeAction(RequestArcInitiationAction.class);
1665        if (!hasAction(RequestArcTerminationAction.class) && isArcEstablished()) {
1666            addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress()));
1667        }
1668    }
1669
1670    @Override
1671    @ServiceThreadOnly
1672    protected void onStandby(boolean initiatedByCec, int standbyAction) {
1673        assertRunOnServiceThread();
1674        // Seq #11
1675        if (!mService.isControlEnabled()) {
1676            return;
1677        }
1678        if (!initiatedByCec && mAutoDeviceOff) {
1679            mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(
1680                    mAddress, Constants.ADDR_BROADCAST));
1681        }
1682    }
1683
1684    boolean isProhibitMode() {
1685        return mService.isProhibitMode();
1686    }
1687
1688    boolean isPowerStandbyOrTransient() {
1689        return mService.isPowerStandbyOrTransient();
1690    }
1691
1692    @ServiceThreadOnly
1693    void displayOsd(int messageId) {
1694        assertRunOnServiceThread();
1695        mService.displayOsd(messageId);
1696    }
1697
1698    @ServiceThreadOnly
1699    void displayOsd(int messageId, int extra) {
1700        assertRunOnServiceThread();
1701        mService.displayOsd(messageId, extra);
1702    }
1703
1704    // Seq #54 and #55
1705    @ServiceThreadOnly
1706    int startOneTouchRecord(int recorderAddress, byte[] recordSource) {
1707        assertRunOnServiceThread();
1708        if (!mService.isControlEnabled()) {
1709            Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1710            announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
1711            return Constants.ABORT_NOT_IN_CORRECT_MODE;
1712        }
1713
1714        if (!checkRecorder(recorderAddress)) {
1715            Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1716            announceOneTouchRecordResult(recorderAddress,
1717                    ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
1718            return Constants.ABORT_NOT_IN_CORRECT_MODE;
1719        }
1720
1721        if (!checkRecordSource(recordSource)) {
1722            Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1723            announceOneTouchRecordResult(recorderAddress,
1724                    ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN);
1725            return Constants.ABORT_CANNOT_PROVIDE_SOURCE;
1726        }
1727
1728        addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource));
1729        Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:"
1730                + Arrays.toString(recordSource));
1731        return Constants.ABORT_NO_ERROR;
1732    }
1733
1734    @ServiceThreadOnly
1735    void stopOneTouchRecord(int recorderAddress) {
1736        assertRunOnServiceThread();
1737        if (!mService.isControlEnabled()) {
1738            Slog.w(TAG, "Can not stop one touch record. CEC control is disabled.");
1739            announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
1740            return;
1741        }
1742
1743        if (!checkRecorder(recorderAddress)) {
1744            Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1745            announceOneTouchRecordResult(recorderAddress,
1746                    ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
1747            return;
1748        }
1749
1750        // Remove one touch record action so that other one touch record can be started.
1751        removeAction(OneTouchRecordAction.class);
1752        mService.sendCecCommand(HdmiCecMessageBuilder.buildRecordOff(mAddress, recorderAddress));
1753        Slog.i(TAG, "Stop [One Touch Record]-Target:" + recorderAddress);
1754    }
1755
1756    private boolean checkRecorder(int recorderAddress) {
1757        HdmiDeviceInfo device = getCecDeviceInfo(recorderAddress);
1758        return (device != null)
1759                && (HdmiUtils.getTypeFromAddress(recorderAddress)
1760                        == HdmiDeviceInfo.DEVICE_RECORDER);
1761    }
1762
1763    private boolean checkRecordSource(byte[] recordSource) {
1764        return (recordSource != null) && HdmiRecordSources.checkRecordSource(recordSource);
1765    }
1766
1767    @ServiceThreadOnly
1768    void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
1769        assertRunOnServiceThread();
1770        if (!mService.isControlEnabled()) {
1771            Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1772            announceTimerRecordingResult(recorderAddress,
1773                    TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED);
1774            return;
1775        }
1776
1777        if (!checkRecorder(recorderAddress)) {
1778            Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1779            announceTimerRecordingResult(recorderAddress,
1780                    TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION);
1781            return;
1782        }
1783
1784        if (!checkTimerRecordingSource(sourceType, recordSource)) {
1785            Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1786            announceTimerRecordingResult(
1787                    recorderAddress,
1788                    TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE);
1789            return;
1790        }
1791
1792        addAndStartAction(
1793                new TimerRecordingAction(this, recorderAddress, sourceType, recordSource));
1794        Slog.i(TAG, "Start [Timer Recording]-Target:" + recorderAddress + ", SourceType:"
1795                + sourceType + ", RecordSource:" + Arrays.toString(recordSource));
1796    }
1797
1798    private boolean checkTimerRecordingSource(int sourceType, byte[] recordSource) {
1799        return (recordSource != null)
1800                && HdmiTimerRecordSources.checkTimerRecordSource(sourceType, recordSource);
1801    }
1802
1803    @ServiceThreadOnly
1804    void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
1805        assertRunOnServiceThread();
1806        if (!mService.isControlEnabled()) {
1807            Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1808            announceClearTimerRecordingResult(recorderAddress, CLEAR_TIMER_STATUS_CEC_DISABLE);
1809            return;
1810        }
1811
1812        if (!checkRecorder(recorderAddress)) {
1813            Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1814            announceClearTimerRecordingResult(recorderAddress,
1815                    CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION);
1816            return;
1817        }
1818
1819        if (!checkTimerRecordingSource(sourceType, recordSource)) {
1820            Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1821            announceClearTimerRecordingResult(recorderAddress,
1822                    CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1823            return;
1824        }
1825
1826        sendClearTimerMessage(recorderAddress, sourceType, recordSource);
1827    }
1828
1829    private void sendClearTimerMessage(final int recorderAddress, int sourceType,
1830            byte[] recordSource) {
1831        HdmiCecMessage message = null;
1832        switch (sourceType) {
1833            case TIMER_RECORDING_TYPE_DIGITAL:
1834                message = HdmiCecMessageBuilder.buildClearDigitalTimer(mAddress, recorderAddress,
1835                        recordSource);
1836                break;
1837            case TIMER_RECORDING_TYPE_ANALOGUE:
1838                message = HdmiCecMessageBuilder.buildClearAnalogueTimer(mAddress, recorderAddress,
1839                        recordSource);
1840                break;
1841            case TIMER_RECORDING_TYPE_EXTERNAL:
1842                message = HdmiCecMessageBuilder.buildClearExternalTimer(mAddress, recorderAddress,
1843                        recordSource);
1844                break;
1845            default:
1846                Slog.w(TAG, "Invalid source type:" + recorderAddress);
1847                announceClearTimerRecordingResult(recorderAddress,
1848                        CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1849                return;
1850
1851        }
1852        mService.sendCecCommand(message, new SendMessageCallback() {
1853            @Override
1854            public void onSendCompleted(int error) {
1855                if (error != SendMessageResult.SUCCESS) {
1856                    announceClearTimerRecordingResult(recorderAddress,
1857                            CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1858                }
1859            }
1860        });
1861    }
1862
1863    void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
1864        HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
1865        if (info == null) {
1866            Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress);
1867            return;
1868        }
1869
1870        if (info.getDevicePowerStatus() == newPowerStatus) {
1871            return;
1872        }
1873
1874        HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus);
1875        // addDeviceInfo replaces old device info with new one if exists.
1876        addDeviceInfo(newInfo);
1877
1878        invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
1879    }
1880
1881    @Override
1882    protected boolean handleMenuStatus(HdmiCecMessage message) {
1883        // Do nothing and just return true not to prevent from responding <Feature Abort>.
1884        return true;
1885    }
1886
1887    @Override
1888    protected void sendStandby(int deviceId) {
1889        HdmiDeviceInfo targetDevice = mDeviceInfos.get(deviceId);
1890        if (targetDevice == null) {
1891            return;
1892        }
1893        int targetAddress = targetDevice.getLogicalAddress();
1894        mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
1895    }
1896
1897    @ServiceThreadOnly
1898    void processAllDelayedMessages() {
1899        assertRunOnServiceThread();
1900        mDelayedMessageBuffer.processAllMessages();
1901    }
1902
1903    @ServiceThreadOnly
1904    void processDelayedMessages(int address) {
1905        assertRunOnServiceThread();
1906        mDelayedMessageBuffer.processMessagesForDevice(address);
1907    }
1908
1909    @ServiceThreadOnly
1910    void processDelayedActiveSource(int address) {
1911        assertRunOnServiceThread();
1912        mDelayedMessageBuffer.processActiveSource(address);
1913    }
1914
1915    @Override
1916    protected void dump(final IndentingPrintWriter pw) {
1917        super.dump(pw);
1918        pw.println("mArcEstablished: " + mArcEstablished);
1919        pw.println("mArcFeatureEnabled: " + mArcFeatureEnabled);
1920        pw.println("mSystemAudioActivated: " + mSystemAudioActivated);
1921        pw.println("mSystemAudioMute: " + mSystemAudioMute);
1922        pw.println("mSystemAudioControlFeatureEnabled: " + mSystemAudioControlFeatureEnabled);
1923        pw.println("mAutoDeviceOff: " + mAutoDeviceOff);
1924        pw.println("mAutoWakeup: " + mAutoWakeup);
1925        pw.println("mSkipRoutingControl: " + mSkipRoutingControl);
1926        pw.println("mPrevPortId: " + mPrevPortId);
1927        pw.println("CEC devices:");
1928        pw.increaseIndent();
1929        for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
1930            pw.println(info);
1931        }
1932        pw.decreaseIndent();
1933    }
1934}
1935