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