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