HdmiCecLocalDevice.java revision a5b7414970c85217e88015e78ecbc5ba093dead3
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.HdmiCec;
20import android.hardware.hdmi.HdmiCecDeviceInfo;
21import android.hardware.hdmi.HdmiCecMessage;
22import android.os.Looper;
23import android.util.Slog;
24
25import com.android.internal.annotations.GuardedBy;
26import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
27
28import java.util.ArrayList;
29import java.util.Iterator;
30import java.util.LinkedList;
31import java.util.List;
32
33/**
34 * Class that models a logical CEC device hosted in this system. Handles initialization,
35 * CEC commands that call for actions customized per device type.
36 */
37abstract class HdmiCecLocalDevice {
38    private static final String TAG = "HdmiCecLocalDevice";
39
40    protected final HdmiControlService mService;
41    protected final int mDeviceType;
42    protected int mAddress;
43    protected int mPreferredAddress;
44    protected HdmiCecDeviceInfo mDeviceInfo;
45
46    // Logical address of the active source.
47    @GuardedBy("mLock")
48    private int mActiveSource;
49
50    // Active routing path. Physical address of the active source but not all the time, such as
51    // when the new active source does not claim itself to be one. Note that we don't keep
52    // the active port id (or active input) since it can be gotten by {@link #pathToPortId(int)}.
53    @GuardedBy("mLock")
54    private int mActiveRoutingPath;
55
56    // Set to true while the service is in normal mode. While set to false, no input change is
57    // allowed. Used for situations where input change can confuse users such as channel auto-scan,
58    // system upgrade, etc., a.k.a. "prohibit mode".
59    @GuardedBy("mLock")
60    private boolean mInputChangeEnabled;
61
62    protected final HdmiCecMessageCache mCecMessageCache = new HdmiCecMessageCache();
63    protected final Object mLock;
64
65    // A collection of FeatureAction.
66    // Note that access to this collection should happen in service thread.
67    private final LinkedList<FeatureAction> mActions = new LinkedList<>();
68
69    protected HdmiCecLocalDevice(HdmiControlService service, int deviceType) {
70        mService = service;
71        mDeviceType = deviceType;
72        mAddress = HdmiCec.ADDR_UNREGISTERED;
73        mLock = service.getServiceLock();
74
75        // TODO: Get control flag from persistent storage
76        mInputChangeEnabled = true;
77    }
78
79    // Factory method that returns HdmiCecLocalDevice of corresponding type.
80    static HdmiCecLocalDevice create(HdmiControlService service, int deviceType) {
81        switch (deviceType) {
82        case HdmiCec.DEVICE_TV:
83            return new HdmiCecLocalDeviceTv(service);
84        case HdmiCec.DEVICE_PLAYBACK:
85            return new HdmiCecLocalDevicePlayback(service);
86        default:
87            return null;
88        }
89    }
90
91    @ServiceThreadOnly
92    void init() {
93        assertRunOnServiceThread();
94        mPreferredAddress = HdmiCec.ADDR_UNREGISTERED;
95        // TODO: load preferred address from permanent storage.
96    }
97
98    /**
99     * Called once a logical address of the local device is allocated.
100     */
101    protected abstract void onAddressAllocated(int logicalAddress);
102
103    /**
104     * Dispatch incoming message.
105     *
106     * @param message incoming message
107     * @return true if consumed a message; otherwise, return false.
108     */
109    @ServiceThreadOnly
110    final boolean dispatchMessage(HdmiCecMessage message) {
111        assertRunOnServiceThread();
112        int dest = message.getDestination();
113        if (dest != mAddress && dest != HdmiCec.ADDR_BROADCAST) {
114            return false;
115        }
116        // Cache incoming message. Note that it caches only white-listed one.
117        mCecMessageCache.cacheMessage(message);
118        return onMessage(message);
119    }
120
121    @ServiceThreadOnly
122    protected final boolean onMessage(HdmiCecMessage message) {
123        assertRunOnServiceThread();
124        if (dispatchMessageToAction(message)) {
125            return true;
126        }
127        switch (message.getOpcode()) {
128            case HdmiCec.MESSAGE_GET_MENU_LANGUAGE:
129                return handleGetMenuLanguage(message);
130            case HdmiCec.MESSAGE_GIVE_PHYSICAL_ADDRESS:
131                return handleGivePhysicalAddress();
132            case HdmiCec.MESSAGE_GIVE_OSD_NAME:
133                return handleGiveOsdName(message);
134            case HdmiCec.MESSAGE_GIVE_DEVICE_VENDOR_ID:
135                return handleGiveDeviceVendorId();
136            case HdmiCec.MESSAGE_GET_CEC_VERSION:
137                return handleGetCecVersion(message);
138            case HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS:
139                return handleReportPhysicalAddress(message);
140            case HdmiCec.MESSAGE_INITIATE_ARC:
141                return handleInitiateArc(message);
142            case HdmiCec.MESSAGE_TERMINATE_ARC:
143                return handleTerminateArc(message);
144            case HdmiCec.MESSAGE_SET_SYSTEM_AUDIO_MODE:
145                return handleSetSystemAudioMode(message);
146            case HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_STATUS:
147                return handleSystemAudioModeStatus(message);
148            default:
149                return false;
150        }
151    }
152
153    @ServiceThreadOnly
154    private boolean dispatchMessageToAction(HdmiCecMessage message) {
155        assertRunOnServiceThread();
156        for (FeatureAction action : mActions) {
157            if (action.processCommand(message)) {
158                return true;
159            }
160        }
161        return false;
162    }
163
164    @ServiceThreadOnly
165    protected boolean handleGivePhysicalAddress() {
166        assertRunOnServiceThread();
167
168        int physicalAddress = mService.getPhysicalAddress();
169        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
170                mAddress, physicalAddress, mDeviceType);
171        mService.sendCecCommand(cecMessage);
172        return true;
173    }
174
175    @ServiceThreadOnly
176    protected boolean handleGiveDeviceVendorId() {
177        assertRunOnServiceThread();
178        int vendorId = mService.getVendorId();
179        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
180                mAddress, vendorId);
181        mService.sendCecCommand(cecMessage);
182        return true;
183    }
184
185    @ServiceThreadOnly
186    protected boolean handleGetCecVersion(HdmiCecMessage message) {
187        assertRunOnServiceThread();
188        int version = mService.getCecVersion();
189        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(),
190                message.getSource(), version);
191        mService.sendCecCommand(cecMessage);
192        return true;
193    }
194
195    @ServiceThreadOnly
196    protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
197        assertRunOnServiceThread();
198        Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString());
199        mService.sendCecCommand(
200                HdmiCecMessageBuilder.buildFeatureAbortCommand(mAddress,
201                        message.getSource(), HdmiCec.MESSAGE_GET_MENU_LANGUAGE,
202                        HdmiConstants.ABORT_UNRECOGNIZED_MODE));
203        return true;
204    }
205
206    @ServiceThreadOnly
207    protected boolean handleGiveOsdName(HdmiCecMessage message) {
208        assertRunOnServiceThread();
209        // Note that since this method is called after logical address allocation is done,
210        // mDeviceInfo should not be null.
211        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand(
212                mAddress, message.getSource(), mDeviceInfo.getDisplayName());
213        if (cecMessage != null) {
214            mService.sendCecCommand(cecMessage);
215        } else {
216            Slog.w(TAG, "Failed to build <Get Osd Name>:" + mDeviceInfo.getDisplayName());
217        }
218        return true;
219    }
220
221    protected boolean handleVendorSpecificCommand(HdmiCecMessage message) {
222        return false;
223    }
224
225    protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
226        return false;
227    }
228
229    protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
230        return false;
231    }
232
233    protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
234        return false;
235    }
236
237    protected boolean handleTerminateArc(HdmiCecMessage message) {
238        return false;
239    }
240
241    protected boolean handleInitiateArc(HdmiCecMessage message) {
242        return false;
243    }
244
245    @ServiceThreadOnly
246    final void handleAddressAllocated(int logicalAddress) {
247        assertRunOnServiceThread();
248        mAddress = mPreferredAddress = logicalAddress;
249        onAddressAllocated(logicalAddress);
250    }
251
252    @ServiceThreadOnly
253    HdmiCecDeviceInfo getDeviceInfo() {
254        assertRunOnServiceThread();
255        return mDeviceInfo;
256    }
257
258    @ServiceThreadOnly
259    void setDeviceInfo(HdmiCecDeviceInfo info) {
260        assertRunOnServiceThread();
261        mDeviceInfo = info;
262    }
263
264    // Returns true if the logical address is same as the argument.
265    @ServiceThreadOnly
266    boolean isAddressOf(int addr) {
267        assertRunOnServiceThread();
268        return addr == mAddress;
269    }
270
271    // Resets the logical address to unregistered(15), meaning the logical device is invalid.
272    @ServiceThreadOnly
273    void clearAddress() {
274        assertRunOnServiceThread();
275        mAddress = HdmiCec.ADDR_UNREGISTERED;
276    }
277
278    @ServiceThreadOnly
279    void setPreferredAddress(int addr) {
280        assertRunOnServiceThread();
281        mPreferredAddress = addr;
282    }
283
284    @ServiceThreadOnly
285    int getPreferredAddress() {
286        assertRunOnServiceThread();
287        return mPreferredAddress;
288    }
289
290    @ServiceThreadOnly
291    void addAndStartAction(final FeatureAction action) {
292        assertRunOnServiceThread();
293        mActions.add(action);
294        action.start();
295    }
296
297    // See if we have an action of a given type in progress.
298    @ServiceThreadOnly
299    <T extends FeatureAction> boolean hasAction(final Class<T> clazz) {
300        assertRunOnServiceThread();
301        for (FeatureAction action : mActions) {
302            if (action.getClass().equals(clazz)) {
303                return true;
304            }
305        }
306        return false;
307    }
308
309    // Returns all actions matched with given class type.
310    @ServiceThreadOnly
311    <T extends FeatureAction> List<T> getActions(final Class<T> clazz) {
312        assertRunOnServiceThread();
313        ArrayList<T> actions = new ArrayList<>();
314        for (FeatureAction action : mActions) {
315            if (action.getClass().equals(clazz)) {
316                actions.add((T) action);
317            }
318        }
319        return actions;
320    }
321
322    /**
323     * Remove the given {@link FeatureAction} object from the action queue.
324     *
325     * @param action {@link FeatureAction} to remove
326     */
327    @ServiceThreadOnly
328    void removeAction(final FeatureAction action) {
329        assertRunOnServiceThread();
330        mActions.remove(action);
331    }
332
333    // Remove all actions matched with the given Class type.
334    @ServiceThreadOnly
335    <T extends FeatureAction> void removeAction(final Class<T> clazz) {
336        assertRunOnServiceThread();
337        removeActionExcept(clazz, null);
338    }
339
340    // Remove all actions matched with the given Class type besides |exception|.
341    @ServiceThreadOnly
342    <T extends FeatureAction> void removeActionExcept(final Class<T> clazz,
343            final FeatureAction exception) {
344        assertRunOnServiceThread();
345        Iterator<FeatureAction> iter = mActions.iterator();
346        while (iter.hasNext()) {
347            FeatureAction action = iter.next();
348            if (action != exception && action.getClass().equals(clazz)) {
349                action.clear();
350                mActions.remove(action);
351            }
352        }
353    }
354
355    protected void assertRunOnServiceThread() {
356        if (Looper.myLooper() != mService.getServiceLooper()) {
357            throw new IllegalStateException("Should run on service thread.");
358        }
359    }
360
361    /**
362     * Called when a hot-plug event issued.
363     *
364     * @param portId id of port where a hot-plug event happened
365     * @param connected whether to connected or not on the event
366     */
367    void onHotplug(int portId, boolean connected) {
368    }
369
370    final HdmiControlService getService() {
371        return mService;
372    }
373
374    @ServiceThreadOnly
375    final boolean isConnectedToArcPort(int path) {
376        assertRunOnServiceThread();
377        return mService.isConnectedToArcPort(path);
378    }
379
380    int getActiveSource() {
381        synchronized (mLock) {
382            return mActiveSource;
383        }
384    }
385
386    /**
387     * Returns the active routing path.
388     */
389    int getActivePath() {
390        synchronized (mLock) {
391            return mActiveRoutingPath;
392        }
393    }
394
395    /**
396     * Returns the ID of the active HDMI port. The active port is the one that has the active
397     * routing path connected to it directly or indirectly under the device hierarchy.
398     */
399    int getActivePortId() {
400        synchronized (mLock) {
401            return mService.pathToPortId(mActiveRoutingPath);
402        }
403    }
404
405    /**
406     * Update the active port.
407     *
408     * @param portId the new active port id
409     */
410    void setActivePortId(int portId) {
411        synchronized (mLock) {
412            // We update active routing path instead, since we get the active port id from
413            // the active routing path.
414            mActiveRoutingPath = mService.portIdToPath(portId);
415        }
416    }
417
418    void updateActiveDevice(int logicalAddress, int physicalAddress) {
419        synchronized (mLock) {
420            mActiveSource = logicalAddress;
421            mActiveRoutingPath = physicalAddress;
422        }
423    }
424
425    void setInputChangeEnabled(boolean enabled) {
426        synchronized (mLock) {
427            mInputChangeEnabled = enabled;
428        }
429    }
430
431    boolean isInPresetInstallationMode() {
432        synchronized (mLock) {
433            return !mInputChangeEnabled;
434        }
435    }
436
437    /**
438     * Whether the given path is located in the tail of current active path.
439     *
440     * @param path to be tested
441     * @return true if the given path is located in the tail of current active path; otherwise,
442     *         false
443     */
444    // TODO: move this to local device tv.
445    boolean isTailOfActivePath(int path) {
446        synchronized (mLock) {
447            // If active routing path is internal source, return false.
448            if (mActiveRoutingPath == 0) {
449                return false;
450            }
451            for (int i = 12; i >= 0; i -= 4) {
452                int curActivePath = (mActiveRoutingPath >> i) & 0xF;
453                if (curActivePath == 0) {
454                    return true;
455                } else {
456                    int curPath = (path >> i) & 0xF;
457                    if (curPath != curActivePath) {
458                        return false;
459                    }
460                }
461            }
462            return false;
463        }
464    }
465
466    @ServiceThreadOnly
467    HdmiCecMessageCache getCecMessageCache() {
468        assertRunOnServiceThread();
469        return mCecMessageCache;
470    }
471
472    @ServiceThreadOnly
473    int pathToPortId(int newPath) {
474        assertRunOnServiceThread();
475        return mService.pathToPortId(newPath);
476    }
477}
478