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