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