HdmiCecLocalDeviceTv.java revision c7eba0f1db8928ca779933a564a06989e22a8532
1006c792d99859341201d19f2b86886cc6ee525b4Mathias Agopian/*
2412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * Copyright (C) 2014 The Android Open Source Project
3412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich *
4412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * Licensed under the Apache License, Version 2.0 (the "License");
5412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * you may not use this file except in compliance with the License.
6412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * You may obtain a copy of the License at
7412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich *
8412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich *      http://www.apache.org/licenses/LICENSE-2.0
9412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich *
10412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * Unless required by applicable law or agreed to in writing, software
11412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * distributed under the License is distributed on an "AS IS" BASIS,
12412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * See the License for the specific language governing permissions and
14412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * limitations under the License.
15412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich */
16412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
17412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichpackage com.android.server.hdmi;
18412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
19412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport android.content.Intent;
20412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport android.hardware.hdmi.HdmiCecDeviceInfo;
21412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport android.hardware.hdmi.HdmiControlManager;
22412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport android.hardware.hdmi.IHdmiControlCallback;
23412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport android.media.AudioSystem;
24412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport android.os.IBinder;
25412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport android.os.RemoteException;
26412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport android.os.UserHandle;
27412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport android.util.Slog;
28412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport android.util.SparseArray;
29412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
30412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport com.android.internal.annotations.GuardedBy;
31412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
32412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
33412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
34412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport java.util.ArrayList;
35412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport java.util.Collections;
36412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport java.util.List;
37412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport java.util.Locale;
38412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
39412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich/**
40412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * Represent a logical device of type TV residing in Android system.
41412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich */
42412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichfinal class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
43412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    private static final String TAG = "HdmiCecLocalDeviceTv";
44412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
45412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    // Whether ARC is available or not. "true" means that ARC is estabilished between TV and
46412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    // AVR as audio receiver.
47412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    @ServiceThreadOnly
48412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    private boolean mArcEstablished = false;
49412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
50412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    // Whether ARC feature is enabled or not.
51412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    private boolean mArcFeatureEnabled = false;
52412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
53412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    // Whether SystemAudioMode is "On" or not.
54412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    @GuardedBy("mLock")
55412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    private boolean mSystemAudioMode;
56412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
57412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    // The previous port id (input) before switching to the new one. This is remembered in order to
58412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    // be able to switch to it upon receiving <Inactive Source> from currently active source.
59412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    // This remains valid only when the active source was switched via one touch play operation
60412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    // (either by TV or source device). Manual port switching invalidates this value to
61412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    // Constants.PORT_INVALID, for which case <Inactive Source> does not do anything.
62412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    @GuardedBy("mLock")
63412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    private int mPrevPortId;
64412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
65412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    @GuardedBy("mLock")
66412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    private int mSystemAudioVolume = Constants.UNKNOWN_VOLUME;
67412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
68412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    @GuardedBy("mLock")
69412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    private boolean mSystemAudioMute = false;
70412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
71412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    // Copy of mDeviceInfos to guarantee thread-safety.
72412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    @GuardedBy("mLock")
73412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    private List<HdmiCecDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList();
74412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    // All external cec input(source) devices. Does not include system audio device.
75412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    @GuardedBy("mLock")
76412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    private List<HdmiCecDeviceInfo> mSafeExternalInputs = Collections.emptyList();
77412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
78412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    // Map-like container of all cec devices including local ones.
79412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    // A logical address of device is used as key of container.
80412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    // This is not thread-safe. For external purpose use mSafeDeviceInfos.
81412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    private final SparseArray<HdmiCecDeviceInfo> mDeviceInfos = new SparseArray<>();
82412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
83412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    // If true, TV going to standby mode puts other devices also to standby.
84412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    private boolean mAutoDeviceOff;
85412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
86412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    HdmiCecLocalDeviceTv(HdmiControlService service) {
87412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        super(service, HdmiCecDeviceInfo.DEVICE_TV);
88412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        mPrevPortId = Constants.INVALID_PORT_ID;
89412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        // TODO: load system audio mode and set it to mSystemAudioMode.
90412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    }
91412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
92412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    @Override
93412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    @ServiceThreadOnly
94412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    protected void onAddressAllocated(int logicalAddress) {
95412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        assertRunOnServiceThread();
96412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        // TODO: vendor-specific initialization here.
97412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
98412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
99412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich                mAddress, mService.getPhysicalAddress(), mDeviceType));
100412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
101412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich                mAddress, mService.getVendorId()));
102412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        launchRoutingControl(true);
103412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        launchDeviceDiscovery();
104412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    }
105412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
106412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    /**
107412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich     * Performs the action 'device select', or 'one touch play' initiated by TV.
108412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich     *
109412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich     * @param targetAddress logical address of the device to select
110412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich     * @param callback callback object to report the result with
111412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich     */
112412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    @ServiceThreadOnly
113412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    void deviceSelect(int targetAddress, IHdmiControlCallback callback) {
114412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        assertRunOnServiceThread();
115412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        if (targetAddress == Constants.ADDR_INTERNAL) {
116412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            handleSelectInternalSource(callback);
117412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            return;
118412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        }
119412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        HdmiCecDeviceInfo targetDevice = getDeviceInfo(targetAddress);
120412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        if (targetDevice == null) {
121412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
122412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            return;
123412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        }
124412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        removeAction(DeviceSelectAction.class);
125412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        addAndStartAction(new DeviceSelectAction(this, targetDevice, callback));
126412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    }
127412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
128412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    @ServiceThreadOnly
129412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    private void handleSelectInternalSource(IHdmiControlCallback callback) {
130412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        assertRunOnServiceThread();
131412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        // Seq #18
132412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        if (mService.isControlEnabled() && getActiveSource() != mAddress) {
133412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            updateActiveSource(mAddress, mService.getPhysicalAddress());
134412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            // TODO: Check if this comes from <Text/Image View On> - if true, do nothing.
135412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
136412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich                    mAddress, mService.getPhysicalAddress());
137412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            mService.sendCecCommand(activeSource);
138412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        }
139412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    }
140412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
141412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    @ServiceThreadOnly
142412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    void updateActiveSource(int activeSource, int activePath) {
143412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        assertRunOnServiceThread();
144412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        // Seq #14
145412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        if (activeSource == getActiveSource() && activePath == getActivePath()) {
146412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            return;
147412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        }
148412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        setActiveSource(activeSource);
149412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        setActivePath(activePath);
150412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        if (getDeviceInfo(activeSource) != null && activeSource != mAddress) {
151412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            if (mService.pathToPortId(activePath) == getActivePortId()) {
152412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich                setPrevPortId(getActivePortId());
153412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            }
154412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            // TODO: Show the OSD banner related to the new active source device.
155412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        } else {
156412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            // TODO: If displayed, remove the OSD banner related to the previous
157412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            //       active source device.
158412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        }
159412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    }
160412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
161412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    /**
162412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich     * Returns the previous port id kept to handle input switching on <Inactive Source>.
163412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich     */
164412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    int getPrevPortId() {
165412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        synchronized (mLock) {
166412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            return mPrevPortId;
167412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        }
168412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    }
169412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
170412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    /**
171412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich     * Sets the previous port id. INVALID_PORT_ID invalidates it, hence no actions will be
172412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich     * taken for <Inactive Source>.
173412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich     */
174412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    void setPrevPortId(int portId) {
175412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        synchronized (mLock) {
176412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            mPrevPortId = portId;
177412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        }
178412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    }
179412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
180412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    @ServiceThreadOnly
181412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    void updateActivePortId(int portId) {
182412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        assertRunOnServiceThread();
183412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        // Seq #15
184412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        if (portId == getActivePortId()) {
185412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            return;
186412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        }
187412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        setPrevPortId(portId);
188412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        // TODO: Actually switch the physical port here. Handle PAP/PIP as well.
189412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        //       Show OSD port change banner
190412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        mService.invokeInputChangeListener(getActiveSource());
191412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    }
192412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
193412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    @ServiceThreadOnly
194412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
195412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        assertRunOnServiceThread();
196412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        // Seq #20
197412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        if (!mService.isControlEnabled() || portId == getActivePortId()) {
198412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
199412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            return;
200412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        }
201412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        // TODO: Make sure this call does not stem from <Active Source> message reception.
202412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
203412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        setActivePortId(portId);
204412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        // TODO: Return immediately if the operation is triggered by <Text/Image View On>
205412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        //       and this is the first notification about the active input after power-on.
206412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        // TODO: Handle invalid port id / active input which should be treated as an
207412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        //       internal tuner.
208412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
209412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        removeAction(RoutingControlAction.class);
210412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
211412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        int oldPath = mService.portIdToPath(mService.portIdToPath(getActivePortId()));
212412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        int newPath = mService.portIdToPath(portId);
213412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        HdmiCecMessage routingChange =
214412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich                HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath);
215412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        mService.sendCecCommand(routingChange);
216412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        addAndStartAction(new RoutingControlAction(this, newPath, false, callback));
217412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    }
218412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
219412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    int getPowerStatus() {
220412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        return mService.getPowerStatus();
221412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    }
222412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
223412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    /**
224412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich     * Sends key to a target CEC device.
225412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich     *
226412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich     * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}.
227412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich     * @param isPressed true if this is keypress event
228412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich     */
229412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    @ServiceThreadOnly
230412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    void sendKeyEvent(int keyCode, boolean isPressed) {
231412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        assertRunOnServiceThread();
232412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        List<SendKeyAction> action = getActions(SendKeyAction.class);
233412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        if (!action.isEmpty()) {
234412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            action.get(0).processKeyEvent(keyCode, isPressed);
235412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        } else {
236412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            if (isPressed) {
237412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich                addAndStartAction(new SendKeyAction(this, getActiveSource(), keyCode));
238412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            } else {
239412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich                Slog.w(TAG, "Discard key release event");
240412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            }
241412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        }
242412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    }
243412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
244412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    private static void invokeCallback(IHdmiControlCallback callback, int result) {
245412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        if (callback == null) {
246412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            return;
247412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        }
248412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        try {
249412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            callback.onComplete(result);
250412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        } catch (RemoteException e) {
251412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            Slog.e(TAG, "Invoking callback failed:" + e);
252412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        }
253412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    }
254412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
255412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    @Override
256412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    @ServiceThreadOnly
257412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    protected boolean handleActiveSource(HdmiCecMessage message) {
258412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        assertRunOnServiceThread();
259412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        int address = message.getSource();
260412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        int path = HdmiUtils.twoBytesToInt(message.getParams());
261412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        if (getDeviceInfo(address) == null) {
262412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            handleNewDeviceAtTheTailOfActivePath(address, path);
263412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        } else {
264412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich            ActiveSourceHandler.create(this, null).process(address, path);
2652fc72d1c0294fb759380604ea906ce67ebeb5c5fJesse Hall        }
266412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        return true;
267412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    }
268412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich
269412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    @Override
270412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    @ServiceThreadOnly
271412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich    protected boolean handleInactiveSource(HdmiCecMessage message) {
272412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich        assertRunOnServiceThread();
273        // Seq #10
274
275        // Ignore <Inactive Source> from non-active source device.
276        if (getActiveSource() != message.getSource()) {
277            return true;
278        }
279        if (isProhibitMode()) {
280            return true;
281        }
282        int portId = getPrevPortId();
283        if (portId != Constants.INVALID_PORT_ID) {
284            // TODO: Do this only if TV is not showing multiview like PIP/PAP.
285
286            HdmiCecDeviceInfo inactiveSource = getDeviceInfo(message.getSource());
287            if (inactiveSource == null) {
288                return true;
289            }
290            if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) {
291                return true;
292            }
293            // TODO: Switch the TV freeze mode off
294
295            setActivePortId(portId);
296            doManualPortSwitching(portId, null);
297            setPrevPortId(Constants.INVALID_PORT_ID);
298        }
299        return true;
300    }
301
302    @Override
303    @ServiceThreadOnly
304    protected boolean handleRequestActiveSource(HdmiCecMessage message) {
305        assertRunOnServiceThread();
306        // Seq #19
307        if (mAddress == getActiveSource()) {
308            mService.sendCecCommand(
309                    HdmiCecMessageBuilder.buildActiveSource(mAddress, getActivePath()));
310        }
311        return true;
312    }
313
314    @Override
315    @ServiceThreadOnly
316    protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
317        assertRunOnServiceThread();
318        HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand(
319                mAddress, Locale.getDefault().getISO3Language());
320        // TODO: figure out how to handle failed to get language code.
321        if (command != null) {
322            mService.sendCecCommand(command);
323        } else {
324            Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString());
325        }
326        return true;
327    }
328
329    @Override
330    @ServiceThreadOnly
331    protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
332        assertRunOnServiceThread();
333        // Ignore if [Device Discovery Action] is going on.
334        if (hasAction(DeviceDiscoveryAction.class)) {
335            Slog.i(TAG, "Ignore unrecognizable <Report Physical Address> "
336                    + "because Device Discovery Action is on-going:" + message);
337            return true;
338        }
339
340        int path = HdmiUtils.twoBytesToInt(message.getParams());
341        int address = message.getSource();
342        if (!isInDeviceList(path, address)) {
343            handleNewDeviceAtTheTailOfActivePath(address, path);
344        }
345        addAndStartAction(new NewDeviceAction(this, address, path));
346        return true;
347    }
348
349    private void handleNewDeviceAtTheTailOfActivePath(int address, int path) {
350        // Seq #22
351        if (isTailOfActivePath(path, getActivePath())) {
352            removeAction(RoutingControlAction.class);
353            int newPath = mService.portIdToPath(getActivePortId());
354            mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange(
355                    mAddress, getActivePath(), newPath));
356            addAndStartAction(new RoutingControlAction(this, getActivePortId(), false, null));
357        }
358    }
359
360    /**
361     * Whether the given path is located in the tail of current active path.
362     *
363     * @param path to be tested
364     * @param activePath current active path
365     * @return true if the given path is located in the tail of current active path; otherwise,
366     *         false
367     */
368    static boolean isTailOfActivePath(int path, int activePath) {
369        // If active routing path is internal source, return false.
370        if (activePath == 0) {
371            return false;
372        }
373        for (int i = 12; i >= 0; i -= 4) {
374            int curActivePath = (activePath >> i) & 0xF;
375            if (curActivePath == 0) {
376                return true;
377            } else {
378                int curPath = (path >> i) & 0xF;
379                if (curPath != curActivePath) {
380                    return false;
381                }
382            }
383        }
384        return false;
385    }
386
387    @Override
388    @ServiceThreadOnly
389    protected boolean handleRoutingChange(HdmiCecMessage message) {
390        assertRunOnServiceThread();
391        // Seq #21
392        byte[] params = message.getParams();
393        if (params.length != 4) {
394            Slog.w(TAG, "Wrong parameter: " + message);
395            return true;
396        }
397        int currentPath = HdmiUtils.twoBytesToInt(params);
398        if (HdmiUtils.isAffectingActiveRoutingPath(getActivePath(), currentPath)) {
399            int newPath = HdmiUtils.twoBytesToInt(params, 2);
400            setActivePath(newPath);
401            removeAction(RoutingControlAction.class);
402            addAndStartAction(new RoutingControlAction(this, newPath, true, null));
403        }
404        return true;
405    }
406
407    @Override
408    @ServiceThreadOnly
409    protected boolean handleReportAudioStatus(HdmiCecMessage message) {
410        assertRunOnServiceThread();
411
412        byte params[] = message.getParams();
413        if (params.length < 1) {
414            Slog.w(TAG, "Invalide <Report Audio Status> message:" + message);
415            return true;
416        }
417        int mute = params[0] & 0x80;
418        int volume = params[0] & 0x7F;
419        setAudioStatus(mute == 0x80, volume);
420        return true;
421    }
422
423    @Override
424    @ServiceThreadOnly
425    protected boolean handleTextViewOn(HdmiCecMessage message) {
426        assertRunOnServiceThread();
427        if (mService.isPowerStandbyOrTransient()) {
428            mService.wakeUp();
429        }
430        // TODO: Connect to Hardware input manager to invoke TV App with the appropriate channel
431        //       that represents the source device.
432        return true;
433    }
434
435    @Override
436    @ServiceThreadOnly
437    protected boolean handleImageViewOn(HdmiCecMessage message) {
438        assertRunOnServiceThread();
439        // Currently, it's the same as <Text View On>.
440        return handleTextViewOn(message);
441    }
442
443    @ServiceThreadOnly
444    private void launchDeviceDiscovery() {
445        assertRunOnServiceThread();
446        clearDeviceInfoList();
447        DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
448                new DeviceDiscoveryCallback() {
449                    @Override
450                    public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) {
451                        for (HdmiCecDeviceInfo info : deviceInfos) {
452                            addCecDevice(info);
453                        }
454
455                        // Since we removed all devices when it's start and
456                        // device discovery action does not poll local devices,
457                        // we should put device info of local device manually here
458                        for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
459                            addCecDevice(device.getDeviceInfo());
460                        }
461
462                        addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
463
464                        // If there is AVR, initiate System Audio Auto initiation action,
465                        // which turns on and off system audio according to last system
466                        // audio setting.
467                        HdmiCecDeviceInfo avrInfo = getAvrDeviceInfo();
468                        if (avrInfo != null) {
469                            addAndStartAction(new SystemAudioAutoInitiationAction(
470                                    HdmiCecLocalDeviceTv.this, avrInfo.getLogicalAddress()));
471                            if (mArcEstablished) {
472                                startArcAction(true);
473                            }
474                        }
475                    }
476                });
477        addAndStartAction(action);
478    }
479
480    // Clear all device info.
481    @ServiceThreadOnly
482    private void clearDeviceInfoList() {
483        assertRunOnServiceThread();
484        mDeviceInfos.clear();
485        updateSafeDeviceInfoList();
486    }
487
488    @ServiceThreadOnly
489    void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {
490        assertRunOnServiceThread();
491        HdmiCecDeviceInfo avr = getAvrDeviceInfo();
492        if (avr == null) {
493            invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
494            return;
495        }
496
497        addAndStartAction(
498                new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback));
499    }
500
501    // # Seq 25
502    void setSystemAudioMode(boolean on) {
503        synchronized (mLock) {
504            if (on != mSystemAudioMode) {
505                mSystemAudioMode = on;
506                // TODO: Need to set the preference for SystemAudioMode.
507                mService.announceSystemAudioModeChange(on);
508            }
509        }
510    }
511
512    boolean getSystemAudioMode() {
513        synchronized (mLock) {
514            return mSystemAudioMode;
515        }
516    }
517
518    /**
519     * Change ARC status into the given {@code enabled} status.
520     *
521     * @return {@code true} if ARC was in "Enabled" status
522     */
523    @ServiceThreadOnly
524    boolean setArcStatus(boolean enabled) {
525        assertRunOnServiceThread();
526        boolean oldStatus = mArcEstablished;
527        // 1. Enable/disable ARC circuit.
528        mService.setAudioReturnChannel(enabled);
529        // 2. Notify arc status to audio service.
530        notifyArcStatusToAudioService(enabled);
531        // 3. Update arc status;
532        mArcEstablished = enabled;
533        return oldStatus;
534    }
535
536    private void notifyArcStatusToAudioService(boolean enabled) {
537        // Note that we don't set any name to ARC.
538        mService.getAudioManager().setWiredDeviceConnectionState(
539                AudioSystem.DEVICE_OUT_HDMI_ARC,
540                enabled ? 1 : 0, "");
541    }
542
543    /**
544     * Returns whether ARC is enabled or not.
545     */
546    @ServiceThreadOnly
547    boolean isArcEstabilished() {
548        assertRunOnServiceThread();
549        return mArcFeatureEnabled && mArcEstablished;
550    }
551
552    @ServiceThreadOnly
553    void changeArcFeatureEnabled(boolean enabled) {
554        assertRunOnServiceThread();
555
556        if (mArcFeatureEnabled != enabled) {
557            if (enabled) {
558                if (!mArcEstablished) {
559                    startArcAction(true);
560                }
561            } else {
562                if (mArcEstablished) {
563                    startArcAction(false);
564                }
565            }
566            mArcFeatureEnabled = enabled;
567        }
568    }
569
570    @ServiceThreadOnly
571    boolean isArcFeatureEnabled() {
572        assertRunOnServiceThread();
573        return mArcFeatureEnabled;
574    }
575
576    @ServiceThreadOnly
577    private void startArcAction(boolean enabled) {
578        assertRunOnServiceThread();
579        HdmiCecDeviceInfo info = getAvrDeviceInfo();
580        if (info == null) {
581            return;
582        }
583        if (!isConnectedToArcPort(info.getPhysicalAddress())) {
584            return;
585        }
586
587        // Terminate opposite action and start action if not exist.
588        if (enabled) {
589            removeAction(RequestArcTerminationAction.class);
590            if (!hasAction(RequestArcInitiationAction.class)) {
591                addAndStartAction(new RequestArcInitiationAction(this, info.getLogicalAddress()));
592            }
593        } else {
594            removeAction(RequestArcInitiationAction.class);
595            if (!hasAction(RequestArcTerminationAction.class)) {
596                addAndStartAction(new RequestArcTerminationAction(this, info.getLogicalAddress()));
597            }
598        }
599    }
600
601    void setAudioStatus(boolean mute, int volume) {
602        synchronized (mLock) {
603            mSystemAudioMute = mute;
604            mSystemAudioVolume = volume;
605            // TODO: pass volume to service (audio service) after scale it to local volume level.
606            mService.setAudioStatus(mute, volume);
607        }
608    }
609
610    @ServiceThreadOnly
611    void changeVolume(int curVolume, int delta, int maxVolume) {
612        assertRunOnServiceThread();
613        if (delta == 0 || !isSystemAudioOn()) {
614            return;
615        }
616
617        int targetVolume = curVolume + delta;
618        int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume);
619        synchronized (mLock) {
620            // If new volume is the same as current system audio volume, just ignore it.
621            // Note that UNKNOWN_VOLUME is not in range of cec volume scale.
622            if (cecVolume == mSystemAudioVolume) {
623                // Update tv volume with system volume value.
624                mService.setAudioStatus(false,
625                        VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume));
626                return;
627            }
628        }
629
630        // Remove existing volume action.
631        removeAction(VolumeControlAction.class);
632
633        HdmiCecDeviceInfo avr = getAvrDeviceInfo();
634        addAndStartAction(VolumeControlAction.ofVolumeChange(this, avr.getLogicalAddress(),
635                cecVolume, delta > 0));
636    }
637
638    @ServiceThreadOnly
639    void changeMute(boolean mute) {
640        assertRunOnServiceThread();
641        if (!isSystemAudioOn()) {
642            return;
643        }
644
645        // Remove existing volume action.
646        removeAction(VolumeControlAction.class);
647        HdmiCecDeviceInfo avr = getAvrDeviceInfo();
648        addAndStartAction(VolumeControlAction.ofMute(this, avr.getLogicalAddress(), mute));
649    }
650
651    private boolean isSystemAudioOn() {
652        if (getAvrDeviceInfo() == null) {
653            return false;
654        }
655
656        synchronized (mLock) {
657            return mSystemAudioMode;
658        }
659    }
660
661    @Override
662    @ServiceThreadOnly
663    protected boolean handleInitiateArc(HdmiCecMessage message) {
664        assertRunOnServiceThread();
665        // In case where <Initiate Arc> is started by <Request ARC Initiation>
666        // need to clean up RequestArcInitiationAction.
667        removeAction(RequestArcInitiationAction.class);
668        SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
669                message.getSource(), true);
670        addAndStartAction(action);
671        return true;
672    }
673
674    @Override
675    @ServiceThreadOnly
676    protected boolean handleTerminateArc(HdmiCecMessage message) {
677        assertRunOnServiceThread();
678        // In case where <Terminate Arc> is started by <Request ARC Termination>
679        // need to clean up RequestArcInitiationAction.
680        // TODO: check conditions of power status by calling is_connected api
681        // to be added soon.
682        removeAction(RequestArcTerminationAction.class);
683        SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
684                message.getSource(), false);
685        addAndStartAction(action);
686        return true;
687    }
688
689    @Override
690    @ServiceThreadOnly
691    protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
692        assertRunOnServiceThread();
693        if (!isMessageForSystemAudio(message)) {
694            return false;
695        }
696        SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this,
697                message.getSource(), HdmiUtils.parseCommandParamSystemAudioStatus(message), null);
698        addAndStartAction(action);
699        return true;
700    }
701
702    @Override
703    @ServiceThreadOnly
704    protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
705        assertRunOnServiceThread();
706        if (!isMessageForSystemAudio(message)) {
707            return false;
708        }
709        setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message));
710        return true;
711    }
712
713    private boolean isMessageForSystemAudio(HdmiCecMessage message) {
714        if (message.getSource() != Constants.ADDR_AUDIO_SYSTEM
715                || message.getDestination() != Constants.ADDR_TV
716                || getAvrDeviceInfo() == null) {
717            Slog.w(TAG, "Skip abnormal CecMessage: " + message);
718            return false;
719        }
720        return true;
721    }
722
723    /**
724     * Add a new {@link HdmiCecDeviceInfo}. It returns old device info which has the same
725     * logical address as new device info's.
726     *
727     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
728     *
729     * @param deviceInfo a new {@link HdmiCecDeviceInfo} to be added.
730     * @return {@code null} if it is new device. Otherwise, returns old {@HdmiCecDeviceInfo}
731     *         that has the same logical address as new one has.
732     */
733    @ServiceThreadOnly
734    HdmiCecDeviceInfo addDeviceInfo(HdmiCecDeviceInfo deviceInfo) {
735        assertRunOnServiceThread();
736        HdmiCecDeviceInfo oldDeviceInfo = getDeviceInfo(deviceInfo.getLogicalAddress());
737        if (oldDeviceInfo != null) {
738            removeDeviceInfo(deviceInfo.getLogicalAddress());
739        }
740        mDeviceInfos.append(deviceInfo.getLogicalAddress(), deviceInfo);
741        updateSafeDeviceInfoList();
742        return oldDeviceInfo;
743    }
744
745    /**
746     * Remove a device info corresponding to the given {@code logicalAddress}.
747     * It returns removed {@link HdmiCecDeviceInfo} if exists.
748     *
749     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
750     *
751     * @param logicalAddress logical address of device to be removed
752     * @return removed {@link HdmiCecDeviceInfo} it exists. Otherwise, returns {@code null}
753     */
754    @ServiceThreadOnly
755    HdmiCecDeviceInfo removeDeviceInfo(int logicalAddress) {
756        assertRunOnServiceThread();
757        HdmiCecDeviceInfo deviceInfo = mDeviceInfos.get(logicalAddress);
758        if (deviceInfo != null) {
759            mDeviceInfos.remove(logicalAddress);
760        }
761        updateSafeDeviceInfoList();
762        return deviceInfo;
763    }
764
765    /**
766     * Return a list of all {@link HdmiCecDeviceInfo}.
767     *
768     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
769     * This is not thread-safe. For thread safety, call {@link #getSafeDeviceInfoList(boolean)}.
770     */
771    @ServiceThreadOnly
772    List<HdmiCecDeviceInfo> getDeviceInfoList(boolean includelLocalDevice) {
773        assertRunOnServiceThread();
774        if (includelLocalDevice) {
775            return HdmiUtils.sparseArrayToList(mDeviceInfos);
776        } else {
777            ArrayList<HdmiCecDeviceInfo> infoList = new ArrayList<>();
778            for (int i = 0; i < mDeviceInfos.size(); ++i) {
779                HdmiCecDeviceInfo info = mDeviceInfos.valueAt(i);
780                if (!isLocalDeviceAddress(info.getLogicalAddress())) {
781                    infoList.add(info);
782                }
783            }
784            return infoList;
785        }
786    }
787
788    /**
789     * Return external input devices.
790     */
791    List<HdmiCecDeviceInfo> getSafeExternalInputs() {
792        synchronized (mLock) {
793            return mSafeExternalInputs;
794        }
795    }
796
797    @ServiceThreadOnly
798    private void updateSafeDeviceInfoList() {
799        assertRunOnServiceThread();
800        List<HdmiCecDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
801        List<HdmiCecDeviceInfo> externalInputs = getInputDevices();
802        synchronized (mLock) {
803            mSafeAllDeviceInfos = copiedDevices;
804            mSafeExternalInputs = externalInputs;
805        }
806    }
807
808    /**
809     * Return a list of external cec input (source) devices.
810     *
811     * <p>Note that this effectively excludes non-source devices like system audio,
812     * secondary TV.
813     */
814    private List<HdmiCecDeviceInfo> getInputDevices() {
815        ArrayList<HdmiCecDeviceInfo> infoList = new ArrayList<>();
816        for (int i = 0; i < mDeviceInfos.size(); ++i) {
817            HdmiCecDeviceInfo info = mDeviceInfos.valueAt(i);
818            if (isLocalDeviceAddress(i)) {
819                continue;
820            }
821            if (info.isSourceType()) {
822                infoList.add(info);
823            }
824        }
825        return infoList;
826    }
827
828    @ServiceThreadOnly
829    private boolean isLocalDeviceAddress(int address) {
830        assertRunOnServiceThread();
831        for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
832            if (device.isAddressOf(address)) {
833                return true;
834            }
835        }
836        return false;
837    }
838
839    @ServiceThreadOnly
840    HdmiCecDeviceInfo getAvrDeviceInfo() {
841        assertRunOnServiceThread();
842        return getDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
843    }
844
845    /**
846     * Return a {@link HdmiCecDeviceInfo} corresponding to the given {@code logicalAddress}.
847     *
848     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
849     * This is not thread-safe. For thread safety, call {@link #getSafeDeviceInfo(int)}.
850     *
851     * @param logicalAddress logical address to be retrieved
852     * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}.
853     *         Returns null if no logical address matched
854     */
855    @ServiceThreadOnly
856    HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) {
857        assertRunOnServiceThread();
858        return mDeviceInfos.get(logicalAddress);
859    }
860
861    boolean hasSystemAudioDevice() {
862        return getSafeAvrDeviceInfo() != null;
863    }
864
865    HdmiCecDeviceInfo getSafeAvrDeviceInfo() {
866        return getSafeDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
867    }
868
869    /**
870     * Thread safe version of {@link #getDeviceInfo(int)}.
871     *
872     * @param logicalAddress logical address to be retrieved
873     * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}.
874     *         Returns null if no logical address matched
875     */
876    HdmiCecDeviceInfo getSafeDeviceInfo(int logicalAddress) {
877        synchronized (mLock) {
878            return mSafeAllDeviceInfos.get(logicalAddress);
879        }
880    }
881
882    /**
883     * Called when a device is newly added or a new device is detected.
884     *
885     * @param info device info of a new device.
886     */
887    @ServiceThreadOnly
888    final void addCecDevice(HdmiCecDeviceInfo info) {
889        assertRunOnServiceThread();
890        addDeviceInfo(info);
891        if (info.getLogicalAddress() == mAddress) {
892            // The addition of TV device itself should not be notified.
893            return;
894        }
895        mService.invokeDeviceEventListeners(info, true);
896    }
897
898    /**
899     * Called when a device is removed or removal of device is detected.
900     *
901     * @param address a logical address of a device to be removed
902     */
903    @ServiceThreadOnly
904    final void removeCecDevice(int address) {
905        assertRunOnServiceThread();
906        HdmiCecDeviceInfo info = removeDeviceInfo(address);
907
908        mCecMessageCache.flushMessagesFrom(address);
909        mService.invokeDeviceEventListeners(info, false);
910    }
911
912    @ServiceThreadOnly
913    void handleRemoveActiveRoutingPath(int path) {
914        assertRunOnServiceThread();
915        // Seq #23
916        if (isTailOfActivePath(path, getActivePath())) {
917            removeAction(RoutingControlAction.class);
918            int newPath = mService.portIdToPath(getActivePortId());
919            mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange(
920                    mAddress, getActivePath(), newPath));
921            addAndStartAction(new RoutingControlAction(this, getActivePortId(), true, null));
922        }
923    }
924
925    /**
926     * Launch routing control process.
927     *
928     * @param routingForBootup true if routing control is initiated due to One Touch Play
929     *        or TV power on
930     */
931    @ServiceThreadOnly
932    void launchRoutingControl(boolean routingForBootup) {
933        assertRunOnServiceThread();
934        // Seq #24
935        if (getActivePortId() != Constants.INVALID_PORT_ID) {
936            if (!routingForBootup && !isProhibitMode()) {
937                removeAction(RoutingControlAction.class);
938                int newPath = mService.portIdToPath(getActivePortId());
939                setActivePath(newPath);
940                mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange(mAddress,
941                        getActivePath(), newPath));
942                addAndStartAction(new RoutingControlAction(this, getActivePortId(),
943                        routingForBootup, null));
944            }
945        } else {
946            int activePath = mService.getPhysicalAddress();
947            setActivePath(activePath);
948            if (!routingForBootup) {
949                mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(mAddress,
950                        activePath));
951            }
952        }
953    }
954
955    /**
956     * Returns the {@link HdmiCecDeviceInfo} instance whose physical address matches
957     * the given routing path. CEC devices use routing path for its physical address to
958     * describe the hierarchy of the devices in the network.
959     *
960     * @param path routing path or physical address
961     * @return {@link HdmiCecDeviceInfo} if the matched info is found; otherwise null
962     */
963    @ServiceThreadOnly
964    final HdmiCecDeviceInfo getDeviceInfoByPath(int path) {
965        assertRunOnServiceThread();
966        for (HdmiCecDeviceInfo info : getDeviceInfoList(false)) {
967            if (info.getPhysicalAddress() == path) {
968                return info;
969            }
970        }
971        return null;
972    }
973
974    /**
975     * Whether a device of the specified physical address and logical address exists
976     * in a device info list. However, both are minimal condition and it could
977     * be different device from the original one.
978     *
979     * @param logicalAddress logical address of a device to be searched
980     * @param physicalAddress physical address of a device to be searched
981     * @return true if exist; otherwise false
982     */
983    @ServiceThreadOnly
984    boolean isInDeviceList(int logicalAddress, int physicalAddress) {
985        assertRunOnServiceThread();
986        HdmiCecDeviceInfo device = getDeviceInfo(logicalAddress);
987        if (device == null) {
988            return false;
989        }
990        return device.getPhysicalAddress() == physicalAddress;
991    }
992
993    @Override
994    @ServiceThreadOnly
995    void onHotplug(int portId, boolean connected) {
996        assertRunOnServiceThread();
997
998        // Tv device will have permanent HotplugDetectionAction.
999        List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class);
1000        if (!hotplugActions.isEmpty()) {
1001            // Note that hotplug action is single action running on a machine.
1002            // "pollAllDevicesNow" cleans up timer and start poll action immediately.
1003            hotplugActions.get(0).pollAllDevicesNow();
1004        }
1005    }
1006
1007    @ServiceThreadOnly
1008    void setAutoDeviceOff(boolean enabled) {
1009        assertRunOnServiceThread();
1010        mAutoDeviceOff = enabled;
1011    }
1012
1013    @Override
1014    @ServiceThreadOnly
1015    protected void onTransitionToStandby(boolean initiatedByCec) {
1016        assertRunOnServiceThread();
1017        // Remove any repeated working actions.
1018        // HotplugDetectionAction will be reinstated during the wake up process.
1019        // HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
1020        //     LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery().
1021        removeAction(HotplugDetectionAction.class);
1022        checkIfPendingActionsCleared();
1023    }
1024
1025    @Override
1026    @ServiceThreadOnly
1027    protected void onStandBy(boolean initiatedByCec) {
1028        assertRunOnServiceThread();
1029        // Seq #11
1030        if (!mService.isControlEnabled()) {
1031            return;
1032        }
1033        if (!initiatedByCec) {
1034            mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(
1035                    mAddress, Constants.ADDR_BROADCAST));
1036        }
1037    }
1038
1039    @Override
1040    @ServiceThreadOnly
1041    protected boolean handleStandby(HdmiCecMessage message) {
1042        assertRunOnServiceThread();
1043        // Seq #12
1044        // Tv accepts directly addressed <Standby> only.
1045        if (message.getDestination() == mAddress) {
1046            super.handleStandby(message);
1047        }
1048        return false;
1049    }
1050
1051    boolean isProhibitMode() {
1052        return mService.isProhibitMode();
1053    }
1054
1055    boolean isPowerStandbyOrTransient() {
1056        return mService.isPowerStandbyOrTransient();
1057
1058    void displayOsd(int messageId) {
1059        Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
1060        intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
1061        mService.getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
1062                HdmiControlService.PERMISSION);
1063    }
1064}
1065