1 /*
2  * Copyright (C) 2018 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 
17 package com.android.server.hdmi;
18 
19 import android.hardware.hdmi.HdmiControlManager;
20 import android.hardware.hdmi.HdmiPortInfo;
21 import android.hardware.hdmi.IHdmiControlCallback;
22 import android.sysprop.HdmiProperties;
23 import android.util.Slog;
24 
25 import com.android.internal.annotations.GuardedBy;
26 import com.android.internal.annotations.VisibleForTesting;
27 import com.android.server.hdmi.Constants.LocalActivePort;
28 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
29 
30 import java.util.List;
31 
32 /**
33  * Represent a logical source device residing in Android system.
34  */
35 abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice {
36 
37     private static final String TAG = "HdmiCecLocalDeviceSource";
38 
39     // Indicate if current device is Active Source or not
40     @VisibleForTesting
41     protected boolean mIsActiveSource = false;
42 
43     // Device has cec switch functionality or not.
44     // Default is false.
45     protected boolean mIsSwitchDevice = HdmiProperties.is_switch().orElse(false);
46 
47     // Routing port number used for Routing Control.
48     // This records the default routing port or the previous valid routing port.
49     // Default is HOME input.
50     // Note that we don't save active path here because for source device,
51     // new Active Source physical address might not match the active path
52     @GuardedBy("mLock")
53     @LocalActivePort
54     private int mRoutingPort = Constants.CEC_SWITCH_HOME;
55 
56     // This records the current input of the device.
57     // When device is switched to ARC input, mRoutingPort does not record it
58     // since it's not an HDMI port used for Routing Control.
59     // mLocalActivePort will record whichever input we switch to to keep tracking on
60     // the current input status of the device.
61     // This can help prevent duplicate switching and provide status information.
62     @GuardedBy("mLock")
63     @LocalActivePort
64     protected int mLocalActivePort = Constants.CEC_SWITCH_HOME;
65 
66     // Whether the Routing Coutrol feature is enabled or not. False by default.
67     @GuardedBy("mLock")
68     protected boolean mRoutingControlFeatureEnabled;
69 
HdmiCecLocalDeviceSource(HdmiControlService service, int deviceType)70     protected HdmiCecLocalDeviceSource(HdmiControlService service, int deviceType) {
71         super(service, deviceType);
72     }
73 
74     @Override
75     @ServiceThreadOnly
onHotplug(int portId, boolean connected)76     void onHotplug(int portId, boolean connected) {
77         assertRunOnServiceThread();
78         if (mService.getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
79             mCecMessageCache.flushAll();
80         }
81         // We'll not clear mIsActiveSource on the hotplug event to pass CETC 11.2.2-2 ~ 3.
82         if (connected) {
83             mService.wakeUp();
84         }
85     }
86 
87     @Override
88     @ServiceThreadOnly
sendStandby(int deviceId)89     protected void sendStandby(int deviceId) {
90         assertRunOnServiceThread();
91 
92         // Send standby to TV only for now
93         int targetAddress = Constants.ADDR_TV;
94         mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
95     }
96 
97     @ServiceThreadOnly
oneTouchPlay(IHdmiControlCallback callback)98     void oneTouchPlay(IHdmiControlCallback callback) {
99         assertRunOnServiceThread();
100         List<OneTouchPlayAction> actions = getActions(OneTouchPlayAction.class);
101         if (!actions.isEmpty()) {
102             Slog.i(TAG, "oneTouchPlay already in progress");
103             actions.get(0).addCallback(callback);
104             return;
105         }
106         OneTouchPlayAction action = OneTouchPlayAction.create(this, Constants.ADDR_TV,
107                 callback);
108         if (action == null) {
109             Slog.w(TAG, "Cannot initiate oneTouchPlay");
110             invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
111             return;
112         }
113         addAndStartAction(action);
114     }
115 
116     @ServiceThreadOnly
handleActiveSource(HdmiCecMessage message)117     protected boolean handleActiveSource(HdmiCecMessage message) {
118         assertRunOnServiceThread();
119         int logicalAddress = message.getSource();
120         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
121         ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
122         if (!getActiveSource().equals(activeSource)) {
123             setActiveSource(activeSource);
124         }
125         setIsActiveSource(physicalAddress == mService.getPhysicalAddress());
126         updateDevicePowerStatus(logicalAddress, HdmiControlManager.POWER_STATUS_ON);
127         if (isRoutingControlFeatureEnabled()) {
128             switchInputOnReceivingNewActivePath(physicalAddress);
129         }
130         return true;
131     }
132 
133     @Override
134     @ServiceThreadOnly
handleRequestActiveSource(HdmiCecMessage message)135     protected boolean handleRequestActiveSource(HdmiCecMessage message) {
136         assertRunOnServiceThread();
137         maySendActiveSource(message.getSource());
138         return true;
139     }
140 
141     @Override
142     @ServiceThreadOnly
handleSetStreamPath(HdmiCecMessage message)143     protected boolean handleSetStreamPath(HdmiCecMessage message) {
144         assertRunOnServiceThread();
145         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
146         // If current device is the target path, set to Active Source.
147         // If the path is under the current device, should switch
148         if (physicalAddress == mService.getPhysicalAddress() && mService.isPlaybackDevice()) {
149             setAndBroadcastActiveSource(message, physicalAddress);
150         }
151         switchInputOnReceivingNewActivePath(physicalAddress);
152         return true;
153     }
154 
155     @Override
156     @ServiceThreadOnly
handleRoutingChange(HdmiCecMessage message)157     protected boolean handleRoutingChange(HdmiCecMessage message) {
158         assertRunOnServiceThread();
159         if (!isRoutingControlFeatureEnabled()) {
160             mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
161             return true;
162         }
163         int newPath = HdmiUtils.twoBytesToInt(message.getParams(), 2);
164         // if the current device is a pure playback device
165         if (!mIsSwitchDevice
166                 && newPath == mService.getPhysicalAddress()
167                 && mService.isPlaybackDevice()) {
168             setAndBroadcastActiveSource(message, newPath);
169         }
170         handleRoutingChangeAndInformation(newPath, message);
171         return true;
172     }
173 
174     @Override
175     @ServiceThreadOnly
handleRoutingInformation(HdmiCecMessage message)176     protected boolean handleRoutingInformation(HdmiCecMessage message) {
177         assertRunOnServiceThread();
178         if (!isRoutingControlFeatureEnabled()) {
179             mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
180             return true;
181         }
182         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
183         // if the current device is a pure playback device
184         if (!mIsSwitchDevice
185                 && physicalAddress == mService.getPhysicalAddress()
186                 && mService.isPlaybackDevice()) {
187             setAndBroadcastActiveSource(message, physicalAddress);
188         }
189         handleRoutingChangeAndInformation(physicalAddress, message);
190         return true;
191     }
192 
193     // Method to switch Input with the new Active Path.
194     // All the devices with Switch functionality should implement this.
switchInputOnReceivingNewActivePath(int physicalAddress)195     protected void switchInputOnReceivingNewActivePath(int physicalAddress) {
196         // do nothing
197     }
198 
199     // Source device with Switch functionality should implement this method.
200     // TODO(): decide which type will handle the routing when multi device type is supported
handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message)201     protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) {
202         // do nothing
203     }
204 
205     // Update the power status of the devices connected to the current device.
206     // This only works if the current device is a switch and keeps tracking the device info
207     // of the device connected to it.
updateDevicePowerStatus(int logicalAddress, int newPowerStatus)208     protected void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
209         // do nothing
210     }
211 
212     // Active source claiming needs to be handled in Service
213     // since service can decide who will be the active source when the device supports
214     // multiple device types in this method.
215     // This method should only be called when the device can be the active source.
setAndBroadcastActiveSource(HdmiCecMessage message, int physicalAddress)216     protected void setAndBroadcastActiveSource(HdmiCecMessage message, int physicalAddress) {
217         mService.setAndBroadcastActiveSource(
218                 physicalAddress, getDeviceInfo().getDeviceType(), message.getSource());
219     }
220 
221     @ServiceThreadOnly
setIsActiveSource(boolean on)222     void setIsActiveSource(boolean on) {
223         assertRunOnServiceThread();
224         mIsActiveSource = on;
225     }
226 
wakeUpIfActiveSource()227     protected void wakeUpIfActiveSource() {
228         if (!mIsActiveSource) {
229             return;
230         }
231         // Wake up the device
232         mService.wakeUp();
233         return;
234     }
235 
maySendActiveSource(int dest)236     protected void maySendActiveSource(int dest) {
237         if (mIsActiveSource) {
238             mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
239                     mAddress, mService.getPhysicalAddress()));
240         }
241     }
242 
243     /**
244      * Set {@link #mRoutingPort} to a specific {@link LocalActivePort} to record the current active
245      * CEC Routing Control related port.
246      *
247      * @param portId The portId of the new routing port.
248      */
249     @VisibleForTesting
setRoutingPort(@ocalActivePort int portId)250     protected void setRoutingPort(@LocalActivePort int portId) {
251         synchronized (mLock) {
252             mRoutingPort = portId;
253         }
254     }
255 
256     /**
257      * Get {@link #mRoutingPort}. This is useful when the device needs to route to the last valid
258      * routing port.
259      */
260     @LocalActivePort
getRoutingPort()261     protected int getRoutingPort() {
262         synchronized (mLock) {
263             return mRoutingPort;
264         }
265     }
266 
267     /**
268      * Get {@link #mLocalActivePort}. This is useful when device needs to know the current active
269      * port.
270      */
271     @LocalActivePort
getLocalActivePort()272     protected int getLocalActivePort() {
273         synchronized (mLock) {
274             return mLocalActivePort;
275         }
276     }
277 
278     /**
279      * Set {@link #mLocalActivePort} to a specific {@link LocalActivePort} to record the current
280      * active port.
281      *
282      * <p>It does not have to be a Routing Control related port. For example it can be
283      * set to {@link Constants#CEC_SWITCH_ARC} but this port is System Audio related.
284      *
285      * @param activePort The portId of the new active port.
286      */
setLocalActivePort(@ocalActivePort int activePort)287     protected void setLocalActivePort(@LocalActivePort int activePort) {
288         synchronized (mLock) {
289             mLocalActivePort = activePort;
290         }
291     }
292 
isRoutingControlFeatureEnabled()293     boolean isRoutingControlFeatureEnabled() {
294         synchronized (mLock) {
295             return mRoutingControlFeatureEnabled;
296         }
297     }
298 
299     // Check if the device is trying to switch to the same input that is active right now.
300     // This can help avoid redundant port switching.
isSwitchingToTheSameInput(@ocalActivePort int activePort)301     protected boolean isSwitchingToTheSameInput(@LocalActivePort int activePort) {
302         return activePort == getLocalActivePort();
303     }
304 }
305