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