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