2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.server.hdmi;
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;
28 import com.android.internal.util.IndentingPrintWriter;
29 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
32 * Represent a logical device of type Playback residing in Android system.
34 final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice {
35 private static final String TAG = "HdmiCecLocalDevicePlayback";
37 private boolean mIsActiveSource = false;
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
42 // Lazily initialized - should call getWakeLock() to get the instance.
43 private WakeLock mWakeLock;
45 HdmiCecLocalDevicePlayback(HdmiControlService service) {
46 super(service, HdmiDeviceInfo.DEVICE_PLAYBACK);
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()));
62 protected int getPreferredAddress() {
63 assertRunOnServiceThread();
64 return SystemProperties.getInt(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
65 Constants.ADDR_UNREGISTERED);
70 protected void setPreferredAddress(int addr) {
71 assertRunOnServiceThread();
72 SystemProperties.set(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
73 String.valueOf(addr));
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);
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,
90 Slog.w(TAG, "Cannot initiate oneTouchPlay");
91 invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
94 addAndStartAction(action);
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);
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);
112 addAndStartAction(action);
116 private void invokeCallback(IHdmiControlCallback callback, int result) {
117 assertRunOnServiceThread();
119 callback.onComplete(result);
120 } catch (RemoteException e) {
121 Slog.e(TAG, "Invoking callback failed:" + e);
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()) {
135 getWakeLock().release();
140 void setActiveSource(boolean on) {
141 assertRunOnServiceThread();
142 mIsActiveSource = on;
144 getWakeLock().acquire();
145 HdmiLogger.debug("active source: %b. Wake lock acquired", mIsActiveSource);
147 getWakeLock().release();
148 HdmiLogger.debug("Wake lock released");
153 private WakeLock getWakeLock() {
154 assertRunOnServiceThread();
155 if (mWakeLock == null) {
156 mWakeLock = mService.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
157 mWakeLock.setReferenceCounted(false);
163 protected boolean canGoToStandby() {
164 return !getWakeLock().isHeld();
169 protected boolean handleActiveSource(HdmiCecMessage message) {
170 assertRunOnServiceThread();
171 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
172 mayResetActiveSource(physicalAddress);
173 return true; // Broadcast message.
176 private void mayResetActiveSource(int physicalAddress) {
177 if (physicalAddress != mService.getPhysicalAddress()) {
178 setActiveSource(false);
184 protected boolean handleSetStreamPath(HdmiCecMessage message) {
185 assertRunOnServiceThread();
186 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
187 maySetActiveSource(physicalAddress);
188 maySendActiveSource(message.getSource());
189 wakeUpIfActiveSource();
190 return true; // Broadcast message.
193 // Samsung model we tested sends <Routing Change> and <Request Active Source>
194 // in a row, and then changes the input to the internal source if there is no
195 // <Active Source> in response. To handle this, we'll set ActiveSource aggressively.
198 protected boolean handleRoutingChange(HdmiCecMessage message) {
199 assertRunOnServiceThread();
200 int newPath = HdmiUtils.twoBytesToInt(message.getParams(), 2);
201 maySetActiveSource(newPath);
202 return true; // Broadcast message.
207 protected boolean handleRoutingInformation(HdmiCecMessage message) {
208 assertRunOnServiceThread();
209 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
210 maySetActiveSource(physicalAddress);
211 return true; // Broadcast message.
214 private void maySetActiveSource(int physicalAddress) {
215 setActiveSource(physicalAddress == mService.getPhysicalAddress());
218 private void wakeUpIfActiveSource() {
219 if (mIsActiveSource && mService.isPowerStandbyOrTransient()) {
224 private void maySendActiveSource(int dest) {
225 if (mIsActiveSource) {
226 mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
227 mAddress, mService.getPhysicalAddress()));
228 // Always reports menu-status active to receive RCP.
229 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportMenuStatus(
230 mAddress, dest, Constants.MENU_STATE_ACTIVATED));
236 protected boolean handleRequestActiveSource(HdmiCecMessage message) {
237 assertRunOnServiceThread();
238 maySendActiveSource(message.getSource());
239 return true; // Broadcast message.
244 protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
245 super.disableDevice(initiatedByCec, callback);
247 assertRunOnServiceThread();
248 if (!initiatedByCec && mIsActiveSource) {
249 mService.sendCecCommand(HdmiCecMessageBuilder.buildInactiveSource(
250 mAddress, mService.getPhysicalAddress()));
252 setActiveSource(false);
253 checkIfPendingActionsCleared();
257 protected void dump(final IndentingPrintWriter pw) {
259 pw.println("mIsActiveSource: " + mIsActiveSource);