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