HdmiCecLocalDevice.java revision 8fa36b110be29d92a9aba070fa4666eefb14b584
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_ACTIVE_SOURCE:
129                return handleActiveSource(message);
130            case HdmiCec.MESSAGE_INACTIVE_SOURCE:
131                return handleInactiveSource(message);
132            case HdmiCec.MESSAGE_REQUEST_ACTIVE_SOURCE:
133                return handleRequestActiveSource(message);
134            case HdmiCec.MESSAGE_GET_MENU_LANGUAGE:
135                return handleGetMenuLanguage(message);
136            case HdmiCec.MESSAGE_GIVE_PHYSICAL_ADDRESS:
137                return handleGivePhysicalAddress();
138            case HdmiCec.MESSAGE_GIVE_OSD_NAME:
139                return handleGiveOsdName(message);
140            case HdmiCec.MESSAGE_GIVE_DEVICE_VENDOR_ID:
141                return handleGiveDeviceVendorId();
142            case HdmiCec.MESSAGE_GET_CEC_VERSION:
143                return handleGetCecVersion(message);
144            case HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS:
145                return handleReportPhysicalAddress(message);
146            case HdmiCec.MESSAGE_INITIATE_ARC:
147                return handleInitiateArc(message);
148            case HdmiCec.MESSAGE_TERMINATE_ARC:
149                return handleTerminateArc(message);
150            case HdmiCec.MESSAGE_SET_SYSTEM_AUDIO_MODE:
151                return handleSetSystemAudioMode(message);
152            case HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_STATUS:
153                return handleSystemAudioModeStatus(message);
154            case HdmiCec.MESSAGE_REPORT_AUDIO_STATUS:
155                return handleReportAudioStatus(message);
156            default:
157                return false;
158        }
159    }
160
161    @ServiceThreadOnly
162    private boolean dispatchMessageToAction(HdmiCecMessage message) {
163        assertRunOnServiceThread();
164        for (FeatureAction action : mActions) {
165            if (action.processCommand(message)) {
166                return true;
167            }
168        }
169        return false;
170    }
171
172    @ServiceThreadOnly
173    protected boolean handleGivePhysicalAddress() {
174        assertRunOnServiceThread();
175
176        int physicalAddress = mService.getPhysicalAddress();
177        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
178                mAddress, physicalAddress, mDeviceType);
179        mService.sendCecCommand(cecMessage);
180        return true;
181    }
182
183    @ServiceThreadOnly
184    protected boolean handleGiveDeviceVendorId() {
185        assertRunOnServiceThread();
186        int vendorId = mService.getVendorId();
187        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
188                mAddress, vendorId);
189        mService.sendCecCommand(cecMessage);
190        return true;
191    }
192
193    @ServiceThreadOnly
194    protected boolean handleGetCecVersion(HdmiCecMessage message) {
195        assertRunOnServiceThread();
196        int version = mService.getCecVersion();
197        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(),
198                message.getSource(), version);
199        mService.sendCecCommand(cecMessage);
200        return true;
201    }
202
203    @ServiceThreadOnly
204    protected boolean handleActiveSource(HdmiCecMessage message) {
205        return false;
206    }
207
208    @ServiceThreadOnly
209    protected boolean handleInactiveSource(HdmiCecMessage message) {
210        return false;
211    }
212
213    @ServiceThreadOnly
214    protected boolean handleRequestActiveSource(HdmiCecMessage message) {
215        return false;
216    }
217
218    @ServiceThreadOnly
219    protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
220        assertRunOnServiceThread();
221        Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString());
222        mService.sendCecCommand(
223                HdmiCecMessageBuilder.buildFeatureAbortCommand(mAddress,
224                        message.getSource(), HdmiCec.MESSAGE_GET_MENU_LANGUAGE,
225                        HdmiConstants.ABORT_UNRECOGNIZED_MODE));
226        return true;
227    }
228
229    @ServiceThreadOnly
230    protected boolean handleGiveOsdName(HdmiCecMessage message) {
231        assertRunOnServiceThread();
232        // Note that since this method is called after logical address allocation is done,
233        // mDeviceInfo should not be null.
234        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand(
235                mAddress, message.getSource(), mDeviceInfo.getDisplayName());
236        if (cecMessage != null) {
237            mService.sendCecCommand(cecMessage);
238        } else {
239            Slog.w(TAG, "Failed to build <Get Osd Name>:" + mDeviceInfo.getDisplayName());
240        }
241        return true;
242    }
243
244    protected boolean handleVendorSpecificCommand(HdmiCecMessage message) {
245        return false;
246    }
247
248    protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
249        return false;
250    }
251
252    protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
253        return false;
254    }
255
256    protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
257        return false;
258    }
259
260    protected boolean handleTerminateArc(HdmiCecMessage message) {
261        return false;
262    }
263
264    protected boolean handleInitiateArc(HdmiCecMessage message) {
265        return false;
266    }
267
268    protected boolean handleReportAudioStatus(HdmiCecMessage message) {
269        return false;
270    }
271
272    @ServiceThreadOnly
273    final void handleAddressAllocated(int logicalAddress) {
274        assertRunOnServiceThread();
275        mAddress = mPreferredAddress = logicalAddress;
276        onAddressAllocated(logicalAddress);
277    }
278
279    @ServiceThreadOnly
280    HdmiCecDeviceInfo getDeviceInfo() {
281        assertRunOnServiceThread();
282        return mDeviceInfo;
283    }
284
285    @ServiceThreadOnly
286    void setDeviceInfo(HdmiCecDeviceInfo info) {
287        assertRunOnServiceThread();
288        mDeviceInfo = info;
289    }
290
291    // Returns true if the logical address is same as the argument.
292    @ServiceThreadOnly
293    boolean isAddressOf(int addr) {
294        assertRunOnServiceThread();
295        return addr == mAddress;
296    }
297
298    // Resets the logical address to unregistered(15), meaning the logical device is invalid.
299    @ServiceThreadOnly
300    void clearAddress() {
301        assertRunOnServiceThread();
302        mAddress = HdmiCec.ADDR_UNREGISTERED;
303    }
304
305    @ServiceThreadOnly
306    void setPreferredAddress(int addr) {
307        assertRunOnServiceThread();
308        mPreferredAddress = addr;
309    }
310
311    @ServiceThreadOnly
312    int getPreferredAddress() {
313        assertRunOnServiceThread();
314        return mPreferredAddress;
315    }
316
317    @ServiceThreadOnly
318    void addAndStartAction(final FeatureAction action) {
319        assertRunOnServiceThread();
320        mActions.add(action);
321        action.start();
322    }
323
324    // See if we have an action of a given type in progress.
325    @ServiceThreadOnly
326    <T extends FeatureAction> boolean hasAction(final Class<T> clazz) {
327        assertRunOnServiceThread();
328        for (FeatureAction action : mActions) {
329            if (action.getClass().equals(clazz)) {
330                return true;
331            }
332        }
333        return false;
334    }
335
336    // Returns all actions matched with given class type.
337    @ServiceThreadOnly
338    <T extends FeatureAction> List<T> getActions(final Class<T> clazz) {
339        assertRunOnServiceThread();
340        ArrayList<T> actions = new ArrayList<>();
341        for (FeatureAction action : mActions) {
342            if (action.getClass().equals(clazz)) {
343                actions.add((T) action);
344            }
345        }
346        return actions;
347    }
348
349    /**
350     * Remove the given {@link FeatureAction} object from the action queue.
351     *
352     * @param action {@link FeatureAction} to remove
353     */
354    @ServiceThreadOnly
355    void removeAction(final FeatureAction action) {
356        assertRunOnServiceThread();
357        mActions.remove(action);
358    }
359
360    // Remove all actions matched with the given Class type.
361    @ServiceThreadOnly
362    <T extends FeatureAction> void removeAction(final Class<T> clazz) {
363        assertRunOnServiceThread();
364        removeActionExcept(clazz, null);
365    }
366
367    // Remove all actions matched with the given Class type besides |exception|.
368    @ServiceThreadOnly
369    <T extends FeatureAction> void removeActionExcept(final Class<T> clazz,
370            final FeatureAction exception) {
371        assertRunOnServiceThread();
372        Iterator<FeatureAction> iter = mActions.iterator();
373        while (iter.hasNext()) {
374            FeatureAction action = iter.next();
375            if (action != exception && action.getClass().equals(clazz)) {
376                action.clear();
377                mActions.remove(action);
378            }
379        }
380    }
381
382    protected void assertRunOnServiceThread() {
383        if (Looper.myLooper() != mService.getServiceLooper()) {
384            throw new IllegalStateException("Should run on service thread.");
385        }
386    }
387
388    /**
389     * Called when a hot-plug event issued.
390     *
391     * @param portId id of port where a hot-plug event happened
392     * @param connected whether to connected or not on the event
393     */
394    void onHotplug(int portId, boolean connected) {
395    }
396
397    final HdmiControlService getService() {
398        return mService;
399    }
400
401    @ServiceThreadOnly
402    final boolean isConnectedToArcPort(int path) {
403        assertRunOnServiceThread();
404        return mService.isConnectedToArcPort(path);
405    }
406
407    int getActiveSource() {
408        synchronized (mLock) {
409            return mActiveSource;
410        }
411    }
412
413    void setActiveSource(int source) {
414        synchronized (mLock) {
415            mActiveSource = source;
416        }
417    }
418
419    int getActivePath() {
420        synchronized (mLock) {
421            return mActiveRoutingPath;
422        }
423    }
424
425    void setActivePath(int path) {
426        synchronized (mLock) {
427            mActiveRoutingPath = path;
428        }
429    }
430
431    /**
432     * Returns the ID of the active HDMI port. The active port is the one that has the active
433     * routing path connected to it directly or indirectly under the device hierarchy.
434     */
435    int getActivePortId() {
436        synchronized (mLock) {
437            return mService.pathToPortId(mActiveRoutingPath);
438        }
439    }
440
441    /**
442     * Update the active port.
443     *
444     * @param portId the new active port id
445     */
446    void setActivePortId(int portId) {
447        synchronized (mLock) {
448            // We update active routing path instead, since we get the active port id from
449            // the active routing path.
450            mActiveRoutingPath = mService.portIdToPath(portId);
451        }
452    }
453
454    void updateActiveDevice(int logicalAddress, int physicalAddress) {
455        synchronized (mLock) {
456            mActiveSource = logicalAddress;
457            mActiveRoutingPath = physicalAddress;
458        }
459    }
460
461    void setInputChangeEnabled(boolean enabled) {
462        synchronized (mLock) {
463            mInputChangeEnabled = enabled;
464        }
465    }
466
467    boolean isInPresetInstallationMode() {
468        // TODO: Change this to check the right flag.
469        synchronized (mLock) {
470            return !mInputChangeEnabled;
471        }
472    }
473
474    boolean isHdmiControlEnabled() {
475        synchronized (mLock) {
476            return !mInputChangeEnabled;
477        }
478    }
479
480    /**
481     * Whether the given path is located in the tail of current active path.
482     *
483     * @param path to be tested
484     * @return true if the given path is located in the tail of current active path; otherwise,
485     *         false
486     */
487    // TODO: move this to local device tv.
488    boolean isTailOfActivePath(int path) {
489        synchronized (mLock) {
490            // If active routing path is internal source, return false.
491            if (mActiveRoutingPath == 0) {
492                return false;
493            }
494            for (int i = 12; i >= 0; i -= 4) {
495                int curActivePath = (mActiveRoutingPath >> i) & 0xF;
496                if (curActivePath == 0) {
497                    return true;
498                } else {
499                    int curPath = (path >> i) & 0xF;
500                    if (curPath != curActivePath) {
501                        return false;
502                    }
503                }
504            }
505            return false;
506        }
507    }
508
509    @ServiceThreadOnly
510    HdmiCecMessageCache getCecMessageCache() {
511        assertRunOnServiceThread();
512        return mCecMessageCache;
513    }
514
515    @ServiceThreadOnly
516    int pathToPortId(int newPath) {
517        assertRunOnServiceThread();
518        return mService.pathToPortId(newPath);
519    }
520}
521