1 /* 2 * Copyright (C) 2014 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.HdmiDeviceInfo; 21 import android.hardware.hdmi.IHdmiControlCallback; 22 import android.os.PowerManager; 23 import android.os.PowerManager.WakeLock; 24 import android.os.RemoteException; 25 import android.os.SystemProperties; 26 import android.util.Slog; 27 28 import com.android.internal.util.IndentingPrintWriter; 29 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; 30 31 /** 32 * Represent a logical device of type Playback residing in Android system. 33 */ 34 final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { 35 private static final String TAG = "HdmiCecLocalDevicePlayback"; 36 37 private boolean mIsActiveSource = false; 38 39 // Used to keep the device awake while it is the active source. For devices that 40 // cannot wake up via CEC commands, this address the inconvenience of having to 41 // turn them on. 42 // Lazily initialized - should call getWakeLock() to get the instance. 43 private WakeLock mWakeLock; 44 45 HdmiCecLocalDevicePlayback(HdmiControlService service) { 46 super(service, HdmiDeviceInfo.DEVICE_PLAYBACK); 47 } 48 49 @Override 50 @ServiceThreadOnly 51 protected void onAddressAllocated(int logicalAddress, int reason) { 52 assertRunOnServiceThread(); 53 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( 54 mAddress, mService.getPhysicalAddress(), mDeviceType)); 55 mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand( 56 mAddress, mService.getVendorId())); 57 startQueuedActions(); 58 } 59 60 @Override 61 @ServiceThreadOnly 62 protected int getPreferredAddress() { 63 assertRunOnServiceThread(); 64 return SystemProperties.getInt(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK, 65 Constants.ADDR_UNREGISTERED); 66 } 67 68 @Override 69 @ServiceThreadOnly 70 protected void setPreferredAddress(int addr) { 71 assertRunOnServiceThread(); 72 SystemProperties.set(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK, 73 String.valueOf(addr)); 74 } 75 76 @ServiceThreadOnly 77 void oneTouchPlay(IHdmiControlCallback callback) { 78 assertRunOnServiceThread(); 79 if (hasAction(OneTouchPlayAction.class)) { 80 Slog.w(TAG, "oneTouchPlay already in progress"); 81 invokeCallback(callback, HdmiControlManager.RESULT_ALREADY_IN_PROGRESS); 82 return; 83 } 84 85 // TODO: Consider the case of multiple TV sets. For now we always direct the command 86 // to the primary one. 87 OneTouchPlayAction action = OneTouchPlayAction.create(this, Constants.ADDR_TV, 88 callback); 89 if (action == null) { 90 Slog.w(TAG, "Cannot initiate oneTouchPlay"); 91 invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION); 92 return; 93 } 94 addAndStartAction(action); 95 } 96 97 @ServiceThreadOnly 98 void queryDisplayStatus(IHdmiControlCallback callback) { 99 assertRunOnServiceThread(); 100 if (hasAction(DevicePowerStatusAction.class)) { 101 Slog.w(TAG, "queryDisplayStatus already in progress"); 102 invokeCallback(callback, HdmiControlManager.RESULT_ALREADY_IN_PROGRESS); 103 return; 104 } 105 DevicePowerStatusAction action = DevicePowerStatusAction.create(this, 106 Constants.ADDR_TV, callback); 107 if (action == null) { 108 Slog.w(TAG, "Cannot initiate queryDisplayStatus"); 109 invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION); 110 return; 111 } 112 addAndStartAction(action); 113 } 114 115 @ServiceThreadOnly 116 private void invokeCallback(IHdmiControlCallback callback, int result) { 117 assertRunOnServiceThread(); 118 try { 119 callback.onComplete(result); 120 } catch (RemoteException e) { 121 Slog.e(TAG, "Invoking callback failed:" + e); 122 } 123 } 124 125 @Override 126 @ServiceThreadOnly 127 void onHotplug(int portId, boolean connected) { 128 assertRunOnServiceThread(); 129 mCecMessageCache.flushAll(); 130 // We'll not clear mIsActiveSource on the hotplug event to pass CETC 11.2.2-2 ~ 3. 131 if (connected && mService.isPowerStandbyOrTransient()) { 132 mService.wakeUp(); 133 } 134 if (!connected) { 135 getWakeLock().release(); 136 } 137 } 138 139 @ServiceThreadOnly 140 void setActiveSource(boolean on) { 141 assertRunOnServiceThread(); 142 mIsActiveSource = on; 143 if (on) { 144 getWakeLock().acquire(); 145 HdmiLogger.debug("active source: %b. Wake lock acquired", mIsActiveSource); 146 } else { 147 getWakeLock().release(); 148 HdmiLogger.debug("Wake lock released"); 149 } 150 } 151 152 @ServiceThreadOnly 153 private WakeLock getWakeLock() { 154 assertRunOnServiceThread(); 155 if (mWakeLock == null) { 156 mWakeLock = mService.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); 157 mWakeLock.setReferenceCounted(false); 158 } 159 return mWakeLock; 160 } 161 162 @Override 163 protected boolean canGoToStandby() { 164 return !getWakeLock().isHeld(); 165 } 166 167 @Override 168 @ServiceThreadOnly 169 protected boolean handleActiveSource(HdmiCecMessage message) { 170 assertRunOnServiceThread(); 171 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); 172 mayResetActiveSource(physicalAddress); 173 return true; // Broadcast message. 174 } 175 176 private void mayResetActiveSource(int physicalAddress) { 177 if (physicalAddress != mService.getPhysicalAddress()) { 178 setActiveSource(false); 179 } 180 } 181 182 @ServiceThreadOnly 183 protected boolean handleUserControlPressed(HdmiCecMessage message) { 184 assertRunOnServiceThread(); 185 wakeUpIfActiveSource(); 186 return super.handleUserControlPressed(message); 187 } 188 189 @Override 190 @ServiceThreadOnly 191 protected boolean handleSetStreamPath(HdmiCecMessage message) { 192 assertRunOnServiceThread(); 193 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); 194 maySetActiveSource(physicalAddress); 195 maySendActiveSource(message.getSource()); 196 wakeUpIfActiveSource(); 197 return true; // Broadcast message. 198 } 199 200 // Samsung model we tested sends <Routing Change> and <Request Active Source> 201 // in a row, and then changes the input to the internal source if there is no 202 // <Active Source> in response. To handle this, we'll set ActiveSource aggressively. 203 @Override 204 @ServiceThreadOnly 205 protected boolean handleRoutingChange(HdmiCecMessage message) { 206 assertRunOnServiceThread(); 207 int newPath = HdmiUtils.twoBytesToInt(message.getParams(), 2); 208 maySetActiveSource(newPath); 209 return true; // Broadcast message. 210 } 211 212 @Override 213 @ServiceThreadOnly 214 protected boolean handleRoutingInformation(HdmiCecMessage message) { 215 assertRunOnServiceThread(); 216 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); 217 maySetActiveSource(physicalAddress); 218 return true; // Broadcast message. 219 } 220 221 private void maySetActiveSource(int physicalAddress) { 222 setActiveSource(physicalAddress == mService.getPhysicalAddress()); 223 } 224 225 private void wakeUpIfActiveSource() { 226 if (!mIsActiveSource) { 227 return; 228 } 229 // Wake up the device if the power is in standby mode, or its screen is off - 230 // which can happen if the device is holding a partial lock. 231 if (mService.isPowerStandbyOrTransient() || !mService.getPowerManager().isScreenOn()) { 232 mService.wakeUp(); 233 } 234 } 235 236 private void maySendActiveSource(int dest) { 237 if (mIsActiveSource) { 238 mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource( 239 mAddress, mService.getPhysicalAddress())); 240 // Always reports menu-status active to receive RCP. 241 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportMenuStatus( 242 mAddress, dest, Constants.MENU_STATE_ACTIVATED)); 243 } 244 } 245 246 @Override 247 @ServiceThreadOnly 248 protected boolean handleRequestActiveSource(HdmiCecMessage message) { 249 assertRunOnServiceThread(); 250 maySendActiveSource(message.getSource()); 251 return true; // Broadcast message. 252 } 253 254 @Override 255 @ServiceThreadOnly 256 protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) { 257 super.disableDevice(initiatedByCec, callback); 258 259 assertRunOnServiceThread(); 260 if (!initiatedByCec && mIsActiveSource) { 261 mService.sendCecCommand(HdmiCecMessageBuilder.buildInactiveSource( 262 mAddress, mService.getPhysicalAddress())); 263 } 264 setActiveSource(false); 265 checkIfPendingActionsCleared(); 266 } 267 268 @Override 269 protected void dump(final IndentingPrintWriter pw) { 270 super.dump(pw); 271 pw.println("mIsActiveSource: " + mIsActiveSource); 272 } 273 } 274