HdmiCecLocalDevice.java revision 8f2ed357a23fac4a55da43d20138b438b4ac79a7
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.HdmiCecDeviceInfo;
20import android.os.Looper;
21import android.util.Slog;
22
23import com.android.internal.annotations.GuardedBy;
24import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
25
26import java.util.ArrayList;
27import java.util.Iterator;
28import java.util.LinkedList;
29import java.util.List;
30
31/**
32 * Class that models a logical CEC device hosted in this system. Handles initialization,
33 * CEC commands that call for actions customized per device type.
34 */
35abstract class HdmiCecLocalDevice {
36    private static final String TAG = "HdmiCecLocalDevice";
37
38    protected final HdmiControlService mService;
39    protected final int mDeviceType;
40    protected int mAddress;
41    protected int mPreferredAddress;
42    protected HdmiCecDeviceInfo mDeviceInfo;
43
44    // Logical address of the active source.
45    @GuardedBy("mLock")
46    private int mActiveSource;
47
48    // Active routing path. Physical address of the active source but not all the time, such as
49    // when the new active source does not claim itself to be one. Note that we don't keep
50    // the active port id (or active input) since it can be gotten by {@link #pathToPortId(int)}.
51    @GuardedBy("mLock")
52    private int mActiveRoutingPath;
53
54    protected final HdmiCecMessageCache mCecMessageCache = new HdmiCecMessageCache();
55    protected final Object mLock;
56
57    // A collection of FeatureAction.
58    // Note that access to this collection should happen in service thread.
59    private final LinkedList<FeatureAction> mActions = new LinkedList<>();
60
61    protected HdmiCecLocalDevice(HdmiControlService service, int deviceType) {
62        mService = service;
63        mDeviceType = deviceType;
64        mAddress = Constants.ADDR_UNREGISTERED;
65        mLock = service.getServiceLock();
66    }
67
68    // Factory method that returns HdmiCecLocalDevice of corresponding type.
69    static HdmiCecLocalDevice create(HdmiControlService service, int deviceType) {
70        switch (deviceType) {
71        case HdmiCecDeviceInfo.DEVICE_TV:
72            return new HdmiCecLocalDeviceTv(service);
73        case HdmiCecDeviceInfo.DEVICE_PLAYBACK:
74            return new HdmiCecLocalDevicePlayback(service);
75        default:
76            return null;
77        }
78    }
79
80    @ServiceThreadOnly
81    void init() {
82        assertRunOnServiceThread();
83        mPreferredAddress = Constants.ADDR_UNREGISTERED;
84        // TODO: load preferred address from permanent storage.
85    }
86
87    /**
88     * Called once a logical address of the local device is allocated.
89     */
90    protected abstract void onAddressAllocated(int logicalAddress);
91
92    /**
93     * Dispatch incoming message.
94     *
95     * @param message incoming message
96     * @return true if consumed a message; otherwise, return false.
97     */
98    @ServiceThreadOnly
99    final boolean dispatchMessage(HdmiCecMessage message) {
100        assertRunOnServiceThread();
101        int dest = message.getDestination();
102        if (dest != mAddress && dest != Constants.ADDR_BROADCAST) {
103            return false;
104        }
105        // Cache incoming message. Note that it caches only white-listed one.
106        mCecMessageCache.cacheMessage(message);
107        return onMessage(message);
108    }
109
110    @ServiceThreadOnly
111    protected final boolean onMessage(HdmiCecMessage message) {
112        assertRunOnServiceThread();
113        if (dispatchMessageToAction(message)) {
114            return true;
115        }
116        switch (message.getOpcode()) {
117            case Constants.MESSAGE_ACTIVE_SOURCE:
118                return handleActiveSource(message);
119            case Constants.MESSAGE_INACTIVE_SOURCE:
120                return handleInactiveSource(message);
121            case Constants.MESSAGE_REQUEST_ACTIVE_SOURCE:
122                return handleRequestActiveSource(message);
123            case Constants.MESSAGE_GET_MENU_LANGUAGE:
124                return handleGetMenuLanguage(message);
125            case Constants.MESSAGE_GIVE_PHYSICAL_ADDRESS:
126                return handleGivePhysicalAddress();
127            case Constants.MESSAGE_GIVE_OSD_NAME:
128                return handleGiveOsdName(message);
129            case Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID:
130                return handleGiveDeviceVendorId();
131            case Constants.MESSAGE_GET_CEC_VERSION:
132                return handleGetCecVersion(message);
133            case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS:
134                return handleReportPhysicalAddress(message);
135            case Constants.MESSAGE_ROUTING_CHANGE:
136                return handleRoutingChange(message);
137            case Constants.MESSAGE_INITIATE_ARC:
138                return handleInitiateArc(message);
139            case Constants.MESSAGE_TERMINATE_ARC:
140                return handleTerminateArc(message);
141            case Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE:
142                return handleSetSystemAudioMode(message);
143            case Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS:
144                return handleSystemAudioModeStatus(message);
145            case Constants.MESSAGE_REPORT_AUDIO_STATUS:
146                return handleReportAudioStatus(message);
147            case Constants.MESSAGE_STANDBY:
148                return handleStandby(message);
149            case Constants.MESSAGE_TEXT_VIEW_ON:
150                return handleTextViewOn(message);
151            case Constants.MESSAGE_IMAGE_VIEW_ON:
152                return handleImageViewOn(message);
153            case Constants.MESSAGE_USER_CONTROL_PRESSED:
154                return handleUserControlPressed(message);
155            case Constants.MESSAGE_SET_STREAM_PATH:
156                return handleSetStreamPath(message);
157            case Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS:
158                return handleGiveDevicePowerStatus(message);
159            case Constants.MESSAGE_VENDOR_COMMAND:
160                return handleVendorCommand(message);
161            case Constants.MESSAGE_VENDOR_COMMAND_WITH_ID:
162                return handleVendorCommandWithId(message);
163            case Constants.MESSAGE_SET_OSD_NAME:
164                return handleSetOsdName(message);
165            default:
166                return false;
167        }
168    }
169
170    @ServiceThreadOnly
171    private boolean dispatchMessageToAction(HdmiCecMessage message) {
172        assertRunOnServiceThread();
173        for (FeatureAction action : mActions) {
174            if (action.processCommand(message)) {
175                return true;
176            }
177        }
178        return false;
179    }
180
181    @ServiceThreadOnly
182    protected boolean handleGivePhysicalAddress() {
183        assertRunOnServiceThread();
184
185        int physicalAddress = mService.getPhysicalAddress();
186        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
187                mAddress, physicalAddress, mDeviceType);
188        mService.sendCecCommand(cecMessage);
189        return true;
190    }
191
192    @ServiceThreadOnly
193    protected boolean handleGiveDeviceVendorId() {
194        assertRunOnServiceThread();
195        int vendorId = mService.getVendorId();
196        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
197                mAddress, vendorId);
198        mService.sendCecCommand(cecMessage);
199        return true;
200    }
201
202    @ServiceThreadOnly
203    protected boolean handleGetCecVersion(HdmiCecMessage message) {
204        assertRunOnServiceThread();
205        int version = mService.getCecVersion();
206        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(),
207                message.getSource(), version);
208        mService.sendCecCommand(cecMessage);
209        return true;
210    }
211
212    @ServiceThreadOnly
213    protected boolean handleActiveSource(HdmiCecMessage message) {
214        return false;
215    }
216
217    @ServiceThreadOnly
218    protected boolean handleInactiveSource(HdmiCecMessage message) {
219        return false;
220    }
221
222    @ServiceThreadOnly
223    protected boolean handleRequestActiveSource(HdmiCecMessage message) {
224        return false;
225    }
226
227    @ServiceThreadOnly
228    protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
229        assertRunOnServiceThread();
230        Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString());
231        mService.sendCecCommand(
232                HdmiCecMessageBuilder.buildFeatureAbortCommand(mAddress,
233                        message.getSource(), Constants.MESSAGE_GET_MENU_LANGUAGE,
234                        Constants.ABORT_UNRECOGNIZED_MODE));
235        return true;
236    }
237
238    @ServiceThreadOnly
239    protected boolean handleGiveOsdName(HdmiCecMessage message) {
240        assertRunOnServiceThread();
241        // Note that since this method is called after logical address allocation is done,
242        // mDeviceInfo should not be null.
243        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand(
244                mAddress, message.getSource(), mDeviceInfo.getDisplayName());
245        if (cecMessage != null) {
246            mService.sendCecCommand(cecMessage);
247        } else {
248            Slog.w(TAG, "Failed to build <Get Osd Name>:" + mDeviceInfo.getDisplayName());
249        }
250        return true;
251    }
252
253    protected boolean handleRoutingChange(HdmiCecMessage message) {
254        return false;
255    }
256
257    protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
258        return false;
259    }
260
261    protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
262        return false;
263    }
264
265    protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
266        return false;
267    }
268
269    protected boolean handleTerminateArc(HdmiCecMessage message) {
270        return false;
271    }
272
273    protected boolean handleInitiateArc(HdmiCecMessage message) {
274        return false;
275    }
276
277    protected boolean handleReportAudioStatus(HdmiCecMessage message) {
278        return false;
279    }
280
281    @ServiceThreadOnly
282    protected boolean handleStandby(HdmiCecMessage message) {
283        assertRunOnServiceThread();
284        // Seq #12
285        if (mService.isControlEnabled() && !mService.isProhibitMode()
286                && mService.isPowerOnOrTransient()) {
287            mService.standby();
288            return true;
289        }
290        return false;
291    }
292
293    @ServiceThreadOnly
294    protected boolean handleUserControlPressed(HdmiCecMessage message) {
295        assertRunOnServiceThread();
296        if (mService.isPowerOnOrTransient() && isPowerOffOrToggleCommand(message)) {
297            mService.standby();
298            return true;
299        } else if (mService.isPowerStandbyOrTransient() && isPowerOnOrToggleCommand(message)) {
300            mService.wakeUp();
301            return true;
302        }
303        return false;
304    }
305
306    private static boolean isPowerOnOrToggleCommand(HdmiCecMessage message) {
307        byte[] params = message.getParams();
308        return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED
309                && params.length == 1
310                && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER
311                        || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION
312                        || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
313    }
314
315    private static boolean isPowerOffOrToggleCommand(HdmiCecMessage message) {
316        byte[] params = message.getParams();
317        return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED
318                && params.length == 1
319                && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER
320                        || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_OFF_FUNCTION
321                        || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
322    }
323
324    protected boolean handleTextViewOn(HdmiCecMessage message) {
325        return false;
326    }
327
328    protected boolean handleImageViewOn(HdmiCecMessage message) {
329        return false;
330    }
331
332    protected boolean handleSetStreamPath(HdmiCecMessage message) {
333        return false;
334    }
335
336    protected boolean handleGiveDevicePowerStatus(HdmiCecMessage message) {
337        mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPowerStatus(
338                mAddress, message.getSource(), mService.getPowerStatus()));
339        return true;
340    }
341
342    protected boolean handleVendorCommand(HdmiCecMessage message) {
343        mService.invokeVendorCommandListeners(mDeviceType, message.getSource(),
344                message.getParams(), false);
345        return true;
346    }
347
348    protected boolean handleVendorCommandWithId(HdmiCecMessage message) {
349        byte[] params = message.getParams();
350        int vendorId = HdmiUtils.threeBytesToInt(params);
351        if (vendorId == mService.getVendorId()) {
352            mService.invokeVendorCommandListeners(mDeviceType, message.getSource(), params, true);
353        } else if (message.getDestination() != Constants.ADDR_BROADCAST &&
354                message.getSource() != Constants.ADDR_UNREGISTERED) {
355            Slog.v(TAG, "Wrong direct vendor command. Replying with <Feature Abort>");
356            mService.sendCecCommand(HdmiCecMessageBuilder.buildFeatureAbortCommand(mAddress,
357                    message.getSource(), Constants.MESSAGE_VENDOR_COMMAND_WITH_ID,
358                    Constants.ABORT_UNRECOGNIZED_MODE));
359        } else {
360            Slog.v(TAG, "Wrong broadcast vendor command. Ignoring");
361        }
362        return true;
363    }
364
365    protected boolean handleSetOsdName(HdmiCecMessage message) {
366        // The default behavior of <Set Osd Name> is doing nothing.
367        return true;
368    }
369
370    @ServiceThreadOnly
371    final void handleAddressAllocated(int logicalAddress) {
372        assertRunOnServiceThread();
373        mAddress = mPreferredAddress = logicalAddress;
374        onAddressAllocated(logicalAddress);
375    }
376
377    @ServiceThreadOnly
378    HdmiCecDeviceInfo getDeviceInfo() {
379        assertRunOnServiceThread();
380        return mDeviceInfo;
381    }
382
383    @ServiceThreadOnly
384    void setDeviceInfo(HdmiCecDeviceInfo info) {
385        assertRunOnServiceThread();
386        mDeviceInfo = info;
387    }
388
389    // Returns true if the logical address is same as the argument.
390    @ServiceThreadOnly
391    boolean isAddressOf(int addr) {
392        assertRunOnServiceThread();
393        return addr == mAddress;
394    }
395
396    // Resets the logical address to unregistered(15), meaning the logical device is invalid.
397    @ServiceThreadOnly
398    void clearAddress() {
399        assertRunOnServiceThread();
400        mAddress = Constants.ADDR_UNREGISTERED;
401    }
402
403    @ServiceThreadOnly
404    void setPreferredAddress(int addr) {
405        assertRunOnServiceThread();
406        mPreferredAddress = addr;
407    }
408
409    @ServiceThreadOnly
410    int getPreferredAddress() {
411        assertRunOnServiceThread();
412        return mPreferredAddress;
413    }
414
415    @ServiceThreadOnly
416    void addAndStartAction(final FeatureAction action) {
417        assertRunOnServiceThread();
418        if (mService.isPowerStandbyOrTransient()) {
419            Slog.w(TAG, "Skip the action during Standby: " + action);
420            return;
421        }
422        mActions.add(action);
423        action.start();
424    }
425
426    // See if we have an action of a given type in progress.
427    @ServiceThreadOnly
428    <T extends FeatureAction> boolean hasAction(final Class<T> clazz) {
429        assertRunOnServiceThread();
430        for (FeatureAction action : mActions) {
431            if (action.getClass().equals(clazz)) {
432                return true;
433            }
434        }
435        return false;
436    }
437
438    // Returns all actions matched with given class type.
439    @ServiceThreadOnly
440    <T extends FeatureAction> List<T> getActions(final Class<T> clazz) {
441        assertRunOnServiceThread();
442        ArrayList<T> actions = new ArrayList<>();
443        for (FeatureAction action : mActions) {
444            if (action.getClass().equals(clazz)) {
445                actions.add((T) action);
446            }
447        }
448        return actions;
449    }
450
451    /**
452     * Remove the given {@link FeatureAction} object from the action queue.
453     *
454     * @param action {@link FeatureAction} to remove
455     */
456    @ServiceThreadOnly
457    void removeAction(final FeatureAction action) {
458        assertRunOnServiceThread();
459        mActions.remove(action);
460        checkIfPendingActionsCleared();
461    }
462
463    // Remove all actions matched with the given Class type.
464    @ServiceThreadOnly
465    <T extends FeatureAction> void removeAction(final Class<T> clazz) {
466        assertRunOnServiceThread();
467        removeActionExcept(clazz, null);
468    }
469
470    // Remove all actions matched with the given Class type besides |exception|.
471    @ServiceThreadOnly
472    <T extends FeatureAction> void removeActionExcept(final Class<T> clazz,
473            final FeatureAction exception) {
474        assertRunOnServiceThread();
475        Iterator<FeatureAction> iter = mActions.iterator();
476        while (iter.hasNext()) {
477            FeatureAction action = iter.next();
478            if (action != exception && action.getClass().equals(clazz)) {
479                action.clear();
480                mActions.remove(action);
481            }
482        }
483        checkIfPendingActionsCleared();
484    }
485
486    protected void checkIfPendingActionsCleared() {
487        if (mActions.isEmpty()) {
488            mService.onPendingActionsCleared();
489        }
490    }
491    protected void assertRunOnServiceThread() {
492        if (Looper.myLooper() != mService.getServiceLooper()) {
493            throw new IllegalStateException("Should run on service thread.");
494        }
495    }
496
497    /**
498     * Called when a hot-plug event issued.
499     *
500     * @param portId id of port where a hot-plug event happened
501     * @param connected whether to connected or not on the event
502     */
503    void onHotplug(int portId, boolean connected) {
504    }
505
506    final HdmiControlService getService() {
507        return mService;
508    }
509
510    @ServiceThreadOnly
511    final boolean isConnectedToArcPort(int path) {
512        assertRunOnServiceThread();
513        return mService.isConnectedToArcPort(path);
514    }
515
516    int getActiveSource() {
517        synchronized (mLock) {
518            return mActiveSource;
519        }
520    }
521
522    void setActiveSource(int source) {
523        synchronized (mLock) {
524            mActiveSource = source;
525        }
526    }
527
528    int getActivePath() {
529        synchronized (mLock) {
530            return mActiveRoutingPath;
531        }
532    }
533
534    void setActivePath(int path) {
535        synchronized (mLock) {
536            mActiveRoutingPath = path;
537        }
538    }
539
540    /**
541     * Returns the ID of the active HDMI port. The active port is the one that has the active
542     * routing path connected to it directly or indirectly under the device hierarchy.
543     */
544    int getActivePortId() {
545        synchronized (mLock) {
546            return mService.pathToPortId(mActiveRoutingPath);
547        }
548    }
549
550    /**
551     * Update the active port.
552     *
553     * @param portId the new active port id
554     */
555    void setActivePortId(int portId) {
556        synchronized (mLock) {
557            // We update active routing path instead, since we get the active port id from
558            // the active routing path.
559            mActiveRoutingPath = mService.portIdToPath(portId);
560        }
561    }
562
563    void updateActiveDevice(int logicalAddress, int physicalAddress) {
564        synchronized (mLock) {
565            mActiveSource = logicalAddress;
566            mActiveRoutingPath = physicalAddress;
567        }
568    }
569
570    @ServiceThreadOnly
571    HdmiCecMessageCache getCecMessageCache() {
572        assertRunOnServiceThread();
573        return mCecMessageCache;
574    }
575
576    @ServiceThreadOnly
577    int pathToPortId(int newPath) {
578        assertRunOnServiceThread();
579        return mService.pathToPortId(newPath);
580    }
581
582    /**
583     * Called when the system started transition to standby mode.
584     *
585     * @param initiatedByCec true if this power sequence is initiated
586     *         by the reception the CEC messages like <StandBy>
587     */
588    protected void onTransitionToStandby(boolean initiatedByCec) {
589        // If there are no outstanding actions, we'll go to STANDBY state.
590        checkIfPendingActionsCleared();
591    }
592
593    /**
594     * Called when the system goes to standby mode.
595     *
596     * @param initiatedByCec true if this power sequence is initiated
597     *         by the reception the CEC messages like <StandBy>
598     */
599    protected void onStandBy(boolean initiatedByCec) {}
600}
601