HdmiCecLocalDevice.java revision 4d43d93743222311c6377d4904c19ccb93699d3b
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    protected final HdmiCecMessageCache mCecMessageCache = new HdmiCecMessageCache();
57    protected final Object mLock;
58
59    // A collection of FeatureAction.
60    // Note that access to this collection should happen in service thread.
61    private final LinkedList<FeatureAction> mActions = new LinkedList<>();
62
63    protected HdmiCecLocalDevice(HdmiControlService service, int deviceType) {
64        mService = service;
65        mDeviceType = deviceType;
66        mAddress = HdmiCec.ADDR_UNREGISTERED;
67        mLock = service.getServiceLock();
68    }
69
70    // Factory method that returns HdmiCecLocalDevice of corresponding type.
71    static HdmiCecLocalDevice create(HdmiControlService service, int deviceType) {
72        switch (deviceType) {
73        case HdmiCec.DEVICE_TV:
74            return new HdmiCecLocalDeviceTv(service);
75        case HdmiCec.DEVICE_PLAYBACK:
76            return new HdmiCecLocalDevicePlayback(service);
77        default:
78            return null;
79        }
80    }
81
82    @ServiceThreadOnly
83    void init() {
84        assertRunOnServiceThread();
85        mPreferredAddress = HdmiCec.ADDR_UNREGISTERED;
86        // TODO: load preferred address from permanent storage.
87    }
88
89    /**
90     * Called once a logical address of the local device is allocated.
91     */
92    protected abstract void onAddressAllocated(int logicalAddress);
93
94    /**
95     * Dispatch incoming message.
96     *
97     * @param message incoming message
98     * @return true if consumed a message; otherwise, return false.
99     */
100    @ServiceThreadOnly
101    final boolean dispatchMessage(HdmiCecMessage message) {
102        assertRunOnServiceThread();
103        int dest = message.getDestination();
104        if (dest != mAddress && dest != HdmiCec.ADDR_BROADCAST) {
105            return false;
106        }
107        // Cache incoming message. Note that it caches only white-listed one.
108        mCecMessageCache.cacheMessage(message);
109        return onMessage(message);
110    }
111
112    @ServiceThreadOnly
113    protected final boolean onMessage(HdmiCecMessage message) {
114        assertRunOnServiceThread();
115        if (dispatchMessageToAction(message)) {
116            return true;
117        }
118        switch (message.getOpcode()) {
119            case HdmiCec.MESSAGE_ACTIVE_SOURCE:
120                return handleActiveSource(message);
121            case HdmiCec.MESSAGE_INACTIVE_SOURCE:
122                return handleInactiveSource(message);
123            case HdmiCec.MESSAGE_REQUEST_ACTIVE_SOURCE:
124                return handleRequestActiveSource(message);
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_ROUTING_CHANGE:
138                return handleRoutingChange(message);
139            case HdmiCec.MESSAGE_INITIATE_ARC:
140                return handleInitiateArc(message);
141            case HdmiCec.MESSAGE_TERMINATE_ARC:
142                return handleTerminateArc(message);
143            case HdmiCec.MESSAGE_SET_SYSTEM_AUDIO_MODE:
144                return handleSetSystemAudioMode(message);
145            case HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_STATUS:
146                return handleSystemAudioModeStatus(message);
147            case HdmiCec.MESSAGE_REPORT_AUDIO_STATUS:
148                return handleReportAudioStatus(message);
149            case HdmiCec.MESSAGE_STANDBY:
150                return handleStandby(message);
151            case HdmiCec.MESSAGE_TEXT_VIEW_ON:
152                return handleTextViewOn(message);
153            case HdmiCec.MESSAGE_IMAGE_VIEW_ON:
154                return handleImageViewOn(message);
155            case HdmiCec.MESSAGE_USER_CONTROL_PRESSED:
156                return handleUserControlPressed(message);
157            case HdmiCec.MESSAGE_SET_STREAM_PATH:
158                return handleSetStreamPath(message);
159            case HdmiCec.MESSAGE_GIVE_DEVICE_POWER_STATUS:
160                return handleGiveDevicePowerStatus(message);
161            default:
162                return false;
163        }
164    }
165
166    @ServiceThreadOnly
167    private boolean dispatchMessageToAction(HdmiCecMessage message) {
168        assertRunOnServiceThread();
169        for (FeatureAction action : mActions) {
170            if (action.processCommand(message)) {
171                return true;
172            }
173        }
174        return false;
175    }
176
177    @ServiceThreadOnly
178    protected boolean handleGivePhysicalAddress() {
179        assertRunOnServiceThread();
180
181        int physicalAddress = mService.getPhysicalAddress();
182        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
183                mAddress, physicalAddress, mDeviceType);
184        mService.sendCecCommand(cecMessage);
185        return true;
186    }
187
188    @ServiceThreadOnly
189    protected boolean handleGiveDeviceVendorId() {
190        assertRunOnServiceThread();
191        int vendorId = mService.getVendorId();
192        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
193                mAddress, vendorId);
194        mService.sendCecCommand(cecMessage);
195        return true;
196    }
197
198    @ServiceThreadOnly
199    protected boolean handleGetCecVersion(HdmiCecMessage message) {
200        assertRunOnServiceThread();
201        int version = mService.getCecVersion();
202        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(),
203                message.getSource(), version);
204        mService.sendCecCommand(cecMessage);
205        return true;
206    }
207
208    @ServiceThreadOnly
209    protected boolean handleActiveSource(HdmiCecMessage message) {
210        return false;
211    }
212
213    @ServiceThreadOnly
214    protected boolean handleInactiveSource(HdmiCecMessage message) {
215        return false;
216    }
217
218    @ServiceThreadOnly
219    protected boolean handleRequestActiveSource(HdmiCecMessage message) {
220        return false;
221    }
222
223    @ServiceThreadOnly
224    protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
225        assertRunOnServiceThread();
226        Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString());
227        mService.sendCecCommand(
228                HdmiCecMessageBuilder.buildFeatureAbortCommand(mAddress,
229                        message.getSource(), HdmiCec.MESSAGE_GET_MENU_LANGUAGE,
230                        HdmiConstants.ABORT_UNRECOGNIZED_MODE));
231        return true;
232    }
233
234    @ServiceThreadOnly
235    protected boolean handleGiveOsdName(HdmiCecMessage message) {
236        assertRunOnServiceThread();
237        // Note that since this method is called after logical address allocation is done,
238        // mDeviceInfo should not be null.
239        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand(
240                mAddress, message.getSource(), mDeviceInfo.getDisplayName());
241        if (cecMessage != null) {
242            mService.sendCecCommand(cecMessage);
243        } else {
244            Slog.w(TAG, "Failed to build <Get Osd Name>:" + mDeviceInfo.getDisplayName());
245        }
246        return true;
247    }
248
249    protected boolean handleVendorSpecificCommand(HdmiCecMessage message) {
250        return false;
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        final int opCode = message.getOpcode();
297        final byte[] params = message.getParams();
298        if (mService.isPowerOnOrTransient() && isPowerOffOrToggleCommand(message)) {
299            mService.standby();
300            return true;
301        } else if (mService.isPowerStandbyOrTransient() && isPowerOnOrToggleCommand(message)) {
302            mService.wakeUp();
303            return true;
304        }
305        return false;
306    }
307
308    private static boolean isPowerOnOrToggleCommand(HdmiCecMessage message) {
309        byte[] params = message.getParams();
310        return message.getOpcode() == HdmiCec.MESSAGE_USER_CONTROL_PRESSED && params.length == 1
311                && (params[0] == HdmiConstants.UI_COMMAND_POWER
312                        || params[0] == HdmiConstants.UI_COMMAND_POWER_ON_FUNCTION
313                        || params[0] == HdmiConstants.UI_COMMAND_POWER_TOGGLE_FUNCTION);
314    }
315
316    private static boolean isPowerOffOrToggleCommand(HdmiCecMessage message) {
317        byte[] params = message.getParams();
318        return message.getOpcode() == HdmiCec.MESSAGE_USER_CONTROL_PRESSED && params.length == 1
319                && (params[0] == HdmiConstants.UI_COMMAND_POWER
320                        || params[0] == HdmiConstants.UI_COMMAND_POWER_OFF_FUNCTION
321                        || params[0] == HdmiConstants.UI_COMMAND_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    @ServiceThreadOnly
343    final void handleAddressAllocated(int logicalAddress) {
344        assertRunOnServiceThread();
345        mAddress = mPreferredAddress = logicalAddress;
346        onAddressAllocated(logicalAddress);
347    }
348
349    @ServiceThreadOnly
350    HdmiCecDeviceInfo getDeviceInfo() {
351        assertRunOnServiceThread();
352        return mDeviceInfo;
353    }
354
355    @ServiceThreadOnly
356    void setDeviceInfo(HdmiCecDeviceInfo info) {
357        assertRunOnServiceThread();
358        mDeviceInfo = info;
359    }
360
361    // Returns true if the logical address is same as the argument.
362    @ServiceThreadOnly
363    boolean isAddressOf(int addr) {
364        assertRunOnServiceThread();
365        return addr == mAddress;
366    }
367
368    // Resets the logical address to unregistered(15), meaning the logical device is invalid.
369    @ServiceThreadOnly
370    void clearAddress() {
371        assertRunOnServiceThread();
372        mAddress = HdmiCec.ADDR_UNREGISTERED;
373    }
374
375    @ServiceThreadOnly
376    void setPreferredAddress(int addr) {
377        assertRunOnServiceThread();
378        mPreferredAddress = addr;
379    }
380
381    @ServiceThreadOnly
382    int getPreferredAddress() {
383        assertRunOnServiceThread();
384        return mPreferredAddress;
385    }
386
387    @ServiceThreadOnly
388    void addAndStartAction(final FeatureAction action) {
389        assertRunOnServiceThread();
390        if (mService.isPowerStandbyOrTransient()) {
391            Slog.w(TAG, "Skip the action during Standby: " + action);
392            return;
393        }
394        mActions.add(action);
395        action.start();
396    }
397
398    // See if we have an action of a given type in progress.
399    @ServiceThreadOnly
400    <T extends FeatureAction> boolean hasAction(final Class<T> clazz) {
401        assertRunOnServiceThread();
402        for (FeatureAction action : mActions) {
403            if (action.getClass().equals(clazz)) {
404                return true;
405            }
406        }
407        return false;
408    }
409
410    // Returns all actions matched with given class type.
411    @ServiceThreadOnly
412    <T extends FeatureAction> List<T> getActions(final Class<T> clazz) {
413        assertRunOnServiceThread();
414        ArrayList<T> actions = new ArrayList<>();
415        for (FeatureAction action : mActions) {
416            if (action.getClass().equals(clazz)) {
417                actions.add((T) action);
418            }
419        }
420        return actions;
421    }
422
423    /**
424     * Remove the given {@link FeatureAction} object from the action queue.
425     *
426     * @param action {@link FeatureAction} to remove
427     */
428    @ServiceThreadOnly
429    void removeAction(final FeatureAction action) {
430        assertRunOnServiceThread();
431        mActions.remove(action);
432        checkIfPendingActionsCleared();
433    }
434
435    // Remove all actions matched with the given Class type.
436    @ServiceThreadOnly
437    <T extends FeatureAction> void removeAction(final Class<T> clazz) {
438        assertRunOnServiceThread();
439        removeActionExcept(clazz, null);
440    }
441
442    // Remove all actions matched with the given Class type besides |exception|.
443    @ServiceThreadOnly
444    <T extends FeatureAction> void removeActionExcept(final Class<T> clazz,
445            final FeatureAction exception) {
446        assertRunOnServiceThread();
447        Iterator<FeatureAction> iter = mActions.iterator();
448        while (iter.hasNext()) {
449            FeatureAction action = iter.next();
450            if (action != exception && action.getClass().equals(clazz)) {
451                action.clear();
452                mActions.remove(action);
453            }
454        }
455        checkIfPendingActionsCleared();
456    }
457
458    protected void checkIfPendingActionsCleared() {
459        if (mActions.isEmpty()) {
460            mService.onPendingActionsCleared();
461        }
462    }
463    protected void assertRunOnServiceThread() {
464        if (Looper.myLooper() != mService.getServiceLooper()) {
465            throw new IllegalStateException("Should run on service thread.");
466        }
467    }
468
469    /**
470     * Called when a hot-plug event issued.
471     *
472     * @param portId id of port where a hot-plug event happened
473     * @param connected whether to connected or not on the event
474     */
475    void onHotplug(int portId, boolean connected) {
476    }
477
478    final HdmiControlService getService() {
479        return mService;
480    }
481
482    @ServiceThreadOnly
483    final boolean isConnectedToArcPort(int path) {
484        assertRunOnServiceThread();
485        return mService.isConnectedToArcPort(path);
486    }
487
488    int getActiveSource() {
489        synchronized (mLock) {
490            return mActiveSource;
491        }
492    }
493
494    void setActiveSource(int source) {
495        synchronized (mLock) {
496            mActiveSource = source;
497        }
498    }
499
500    int getActivePath() {
501        synchronized (mLock) {
502            return mActiveRoutingPath;
503        }
504    }
505
506    void setActivePath(int path) {
507        synchronized (mLock) {
508            mActiveRoutingPath = path;
509        }
510    }
511
512    /**
513     * Returns the ID of the active HDMI port. The active port is the one that has the active
514     * routing path connected to it directly or indirectly under the device hierarchy.
515     */
516    int getActivePortId() {
517        synchronized (mLock) {
518            return mService.pathToPortId(mActiveRoutingPath);
519        }
520    }
521
522    /**
523     * Update the active port.
524     *
525     * @param portId the new active port id
526     */
527    void setActivePortId(int portId) {
528        synchronized (mLock) {
529            // We update active routing path instead, since we get the active port id from
530            // the active routing path.
531            mActiveRoutingPath = mService.portIdToPath(portId);
532        }
533    }
534
535    void updateActiveDevice(int logicalAddress, int physicalAddress) {
536        synchronized (mLock) {
537            mActiveSource = logicalAddress;
538            mActiveRoutingPath = physicalAddress;
539        }
540    }
541
542    @ServiceThreadOnly
543    HdmiCecMessageCache getCecMessageCache() {
544        assertRunOnServiceThread();
545        return mCecMessageCache;
546    }
547
548    @ServiceThreadOnly
549    int pathToPortId(int newPath) {
550        assertRunOnServiceThread();
551        return mService.pathToPortId(newPath);
552    }
553
554    /**
555     * Called when the system started transition to standby mode.
556     *
557     * @param initiatedByCec true if this power sequence is initiated
558     *         by the reception the CEC messages like <StandBy>
559     */
560    protected void onTransitionToStandby(boolean initiatedByCec) {
561        // If there are no outstanding actions, we'll go to STANDBY state.
562        checkIfPendingActionsCleared();
563    }
564
565    /**
566     * Called when the system goes to standby mode.
567     *
568     * @param initiatedByCec true if this power sequence is initiated
569     *         by the reception the CEC messages like <StandBy>
570     */
571    protected void onStandBy(boolean initiatedByCec) {}
572}
573