blob: ae008b4bfa7af51e95e168b6e71e0660515acf65 [file] [log] [blame]
Amy848a9f22018-08-27 17:21:26 -07001/*
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
17package com.android.server.hdmi;
18
Amy17ee20f2018-10-11 11:08:23 -070019import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH;
20
Amy848a9f22018-08-27 17:21:26 -070021import android.hardware.hdmi.HdmiControlManager;
Amy59176da2018-10-12 16:30:54 -070022import android.hardware.hdmi.HdmiPortInfo;
Amy848a9f22018-08-27 17:21:26 -070023import android.hardware.hdmi.IHdmiControlCallback;
Amy34037422018-09-06 13:21:08 -070024import android.os.SystemProperties;
Amy848a9f22018-08-27 17:21:26 -070025import android.util.Slog;
26
Amy225d55a2018-09-06 11:03:51 -070027import com.android.internal.annotations.GuardedBy;
28import com.android.internal.annotations.VisibleForTesting;
29import com.android.server.hdmi.Constants.LocalActivePort;
Amy848a9f22018-08-27 17:21:26 -070030import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
31
32import java.util.List;
33
34/**
35 * Represent a logical source device residing in Android system.
36 */
37abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice {
38
39 private static final String TAG = "HdmiCecLocalDeviceSource";
40
Amy9a59d9c2018-08-31 13:47:24 -070041 // Indicate if current device is Active Source or not
Amy123ec402018-09-25 10:56:31 -070042 @VisibleForTesting
43 protected boolean mIsActiveSource = false;
Amy848a9f22018-08-27 17:21:26 -070044
Amy34037422018-09-06 13:21:08 -070045 // Device has cec switch functionality or not.
46 // Default is false.
47 protected boolean mIsSwitchDevice = SystemProperties.getBoolean(
Amy17ee20f2018-10-11 11:08:23 -070048 PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false);
Amy34037422018-09-06 13:21:08 -070049
Amy03afe482018-09-18 16:57:45 -070050 // Routing port number used for Routing Control.
51 // This records the default routing port or the previous valid routing port.
Amy225d55a2018-09-06 11:03:51 -070052 // Default is HOME input.
53 // Note that we don't save active path here because for source device,
Amy03afe482018-09-18 16:57:45 -070054 // new Active Source physical address might not match the active path
Amy225d55a2018-09-06 11:03:51 -070055 @GuardedBy("mLock")
56 @LocalActivePort
Amy03afe482018-09-18 16:57:45 -070057 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;
Amy225d55a2018-09-06 11:03:51 -070068
Amy77e672c2018-10-31 15:55:40 -070069 // Whether the Routing Coutrol feature is enabled or not. False by default.
Amy0c2e29f2018-10-23 12:17:52 -070070 @GuardedBy("mLock")
71 protected boolean mRoutingControlFeatureEnabled;
72
Amy848a9f22018-08-27 17:21:26 -070073 protected HdmiCecLocalDeviceSource(HdmiControlService service, int deviceType) {
74 super(service, deviceType);
75 }
76
77 @Override
78 @ServiceThreadOnly
79 void onHotplug(int portId, boolean connected) {
80 assertRunOnServiceThread();
Amy59176da2018-10-12 16:30:54 -070081 if (mService.getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
82 mCecMessageCache.flushAll();
83 }
Amy848a9f22018-08-27 17:21:26 -070084 // We'll not clear mIsActiveSource on the hotplug event to pass CETC 11.2.2-2 ~ 3.
Amyae4ee342018-10-09 16:55:14 -070085 if (connected) {
Amy848a9f22018-08-27 17:21:26 -070086 mService.wakeUp();
87 }
88 }
89
Amy777abd72018-09-10 16:11:33 -070090 @Override
91 @ServiceThreadOnly
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
Amy848a9f22018-08-27 17:21:26 -0700100 @ServiceThreadOnly
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
Amy848a9f22018-08-27 17:21:26 -0700120 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);
Amy123ec402018-09-25 10:56:31 -0700125 if (!getActiveSource().equals(activeSource)) {
Amy848a9f22018-08-27 17:21:26 -0700126 setActiveSource(activeSource);
Amy848a9f22018-08-27 17:21:26 -0700127 }
Amy9a59d9c2018-08-31 13:47:24 -0700128 setIsActiveSource(physicalAddress == mService.getPhysicalAddress());
Amy1e4a8cc2018-10-15 10:18:14 -0700129 updateDevicePowerStatus(logicalAddress, HdmiControlManager.POWER_STATUS_ON);
Amy0c2e29f2018-10-23 12:17:52 -0700130 if (isRoutingControlFeatureEnabled()) {
131 switchInputOnReceivingNewActivePath(physicalAddress);
132 }
Amy848a9f22018-08-27 17:21:26 -0700133 return true;
134 }
135
136 @Override
137 @ServiceThreadOnly
138 protected boolean handleRequestActiveSource(HdmiCecMessage message) {
139 assertRunOnServiceThread();
Amy225d55a2018-09-06 11:03:51 -0700140 maySendActiveSource(message.getSource());
Amy848a9f22018-08-27 17:21:26 -0700141 return true;
142 }
143
Amy34037422018-09-06 13:21:08 -0700144 @Override
145 @ServiceThreadOnly
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
Amy79b54e92018-09-11 17:02:28 -0700158 @Override
159 @ServiceThreadOnly
160 protected boolean handleRoutingChange(HdmiCecMessage message) {
161 assertRunOnServiceThread();
Amy0c2e29f2018-10-23 12:17:52 -0700162 if (!isRoutingControlFeatureEnabled()) {
163 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
164 return true;
165 }
Amy79b54e92018-09-11 17:02:28 -0700166 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
179 protected boolean handleRoutingInformation(HdmiCecMessage message) {
180 assertRunOnServiceThread();
Amy0c2e29f2018-10-23 12:17:52 -0700181 if (!isRoutingControlFeatureEnabled()) {
182 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
183 return true;
184 }
Amy79b54e92018-09-11 17:02:28 -0700185 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
Amy34037422018-09-06 13:21:08 -0700196 // Method to switch Input with the new Active Path.
197 // All the devices with Switch functionality should implement this.
198 protected void switchInputOnReceivingNewActivePath(int physicalAddress) {
199 // do nothing
200 }
201
Amy79b54e92018-09-11 17:02:28 -0700202 // Source device with Switch functionality should implement this method.
203 // TODO(): decide which type will handle the routing when multi device type is supported
204 protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) {
205 // do nothing
206 }
207
Amy1e4a8cc2018-10-15 10:18:14 -0700208 // Update the power status of the devices connected to the current device.
209 // This only works if the current device is a switch and keeps tracking the device info
210 // of the device connected to it.
211 protected void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
212 // do nothing
213 }
214
Amy123ec402018-09-25 10:56:31 -0700215 // Active source claiming needs to be handled in Service
216 // since service can decide who will be the active source when the device supports
Amy34037422018-09-06 13:21:08 -0700217 // multiple device types in this method.
218 // This method should only be called when the device can be the active source.
219 protected void setAndBroadcastActiveSource(HdmiCecMessage message, int physicalAddress) {
Amy123ec402018-09-25 10:56:31 -0700220 mService.setAndBroadcastActiveSource(
Amyb9d7f432018-11-30 15:08:30 -0800221 physicalAddress, getDeviceInfo().getDeviceType(), message.getSource());
Amy34037422018-09-06 13:21:08 -0700222 }
223
Amy848a9f22018-08-27 17:21:26 -0700224 @ServiceThreadOnly
Amy9a59d9c2018-08-31 13:47:24 -0700225 void setIsActiveSource(boolean on) {
Amy848a9f22018-08-27 17:21:26 -0700226 assertRunOnServiceThread();
227 mIsActiveSource = on;
228 }
Amy225d55a2018-09-06 11:03:51 -0700229
Amy34037422018-09-06 13:21:08 -0700230 protected void wakeUpIfActiveSource() {
231 if (!mIsActiveSource) {
232 return;
233 }
Amyae4ee342018-10-09 16:55:14 -0700234 // Wake up the device
235 mService.wakeUp();
Amy34037422018-09-06 13:21:08 -0700236 return;
Amy225d55a2018-09-06 11:03:51 -0700237 }
238
Amy34037422018-09-06 13:21:08 -0700239 protected void maySendActiveSource(int dest) {
240 if (mIsActiveSource) {
241 mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
242 mAddress, mService.getPhysicalAddress()));
243 }
Amy225d55a2018-09-06 11:03:51 -0700244 }
245
Amy03afe482018-09-18 16:57:45 -0700246 /**
247 * Set {@link #mRoutingPort} to a specific {@link LocalActivePort} to record the current active
248 * CEC Routing Control related port.
249 *
250 * @param portId The portId of the new routing port.
251 */
Amy225d55a2018-09-06 11:03:51 -0700252 @VisibleForTesting
Amy03afe482018-09-18 16:57:45 -0700253 protected void setRoutingPort(@LocalActivePort int portId) {
Amy225d55a2018-09-06 11:03:51 -0700254 synchronized (mLock) {
Amy03afe482018-09-18 16:57:45 -0700255 mRoutingPort = portId;
Amy225d55a2018-09-06 11:03:51 -0700256 }
257 }
258
Amy03afe482018-09-18 16:57:45 -0700259 /**
260 * Get {@link #mRoutingPort}. This is useful when the device needs to route to the last valid
261 * routing port.
262 */
263 @LocalActivePort
264 protected int getRoutingPort() {
265 synchronized (mLock) {
266 return mRoutingPort;
267 }
268 }
269
270 /**
271 * Get {@link #mLocalActivePort}. This is useful when device needs to know the current active
272 * port.
273 */
Amy225d55a2018-09-06 11:03:51 -0700274 @LocalActivePort
275 protected int getLocalActivePort() {
276 synchronized (mLock) {
277 return mLocalActivePort;
278 }
279 }
Amy03afe482018-09-18 16:57:45 -0700280
281 /**
282 * Set {@link #mLocalActivePort} to a specific {@link LocalActivePort} to record the current
283 * active port.
284 *
285 * <p>It does not have to be a Routing Control related port. For example it can be
286 * set to {@link Constants#CEC_SWITCH_ARC} but this port is System Audio related.
287 *
288 * @param activePort The portId of the new active port.
289 */
290 protected void setLocalActivePort(@LocalActivePort int activePort) {
291 synchronized (mLock) {
292 mLocalActivePort = activePort;
293 }
294 }
295
Amy0c2e29f2018-10-23 12:17:52 -0700296 boolean isRoutingControlFeatureEnabled() {
297 synchronized (mLock) {
298 return mRoutingControlFeatureEnabled;
299 }
300 }
301
Amy03afe482018-09-18 16:57:45 -0700302 // Check if the device is trying to switch to the same input that is active right now.
303 // This can help avoid redundant port switching.
304 protected boolean isSwitchingToTheSameInput(@LocalActivePort int activePort) {
305 return activePort == getLocalActivePort();
306 }
Amy848a9f22018-08-27 17:21:26 -0700307}