HdmiCecLocalDevice.java revision c0c20d0522d7756d80f011e7a54bf3b51c78df41
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            default:
160                return false;
161        }
162    }
163
164    @ServiceThreadOnly
165    private boolean dispatchMessageToAction(HdmiCecMessage message) {
166        assertRunOnServiceThread();
167        for (FeatureAction action : mActions) {
168            if (action.processCommand(message)) {
169                return true;
170            }
171        }
172        return false;
173    }
174
175    @ServiceThreadOnly
176    protected boolean handleGivePhysicalAddress() {
177        assertRunOnServiceThread();
178
179        int physicalAddress = mService.getPhysicalAddress();
180        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
181                mAddress, physicalAddress, mDeviceType);
182        mService.sendCecCommand(cecMessage);
183        return true;
184    }
185
186    @ServiceThreadOnly
187    protected boolean handleGiveDeviceVendorId() {
188        assertRunOnServiceThread();
189        int vendorId = mService.getVendorId();
190        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
191                mAddress, vendorId);
192        mService.sendCecCommand(cecMessage);
193        return true;
194    }
195
196    @ServiceThreadOnly
197    protected boolean handleGetCecVersion(HdmiCecMessage message) {
198        assertRunOnServiceThread();
199        int version = mService.getCecVersion();
200        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(),
201                message.getSource(), version);
202        mService.sendCecCommand(cecMessage);
203        return true;
204    }
205
206    @ServiceThreadOnly
207    protected boolean handleActiveSource(HdmiCecMessage message) {
208        return false;
209    }
210
211    @ServiceThreadOnly
212    protected boolean handleInactiveSource(HdmiCecMessage message) {
213        return false;
214    }
215
216    @ServiceThreadOnly
217    protected boolean handleRequestActiveSource(HdmiCecMessage message) {
218        return false;
219    }
220
221    @ServiceThreadOnly
222    protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
223        assertRunOnServiceThread();
224        Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString());
225        mService.sendCecCommand(
226                HdmiCecMessageBuilder.buildFeatureAbortCommand(mAddress,
227                        message.getSource(), Constants.MESSAGE_GET_MENU_LANGUAGE,
228                        Constants.ABORT_UNRECOGNIZED_MODE));
229        return true;
230    }
231
232    @ServiceThreadOnly
233    protected boolean handleGiveOsdName(HdmiCecMessage message) {
234        assertRunOnServiceThread();
235        // Note that since this method is called after logical address allocation is done,
236        // mDeviceInfo should not be null.
237        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand(
238                mAddress, message.getSource(), mDeviceInfo.getDisplayName());
239        if (cecMessage != null) {
240            mService.sendCecCommand(cecMessage);
241        } else {
242            Slog.w(TAG, "Failed to build <Get Osd Name>:" + mDeviceInfo.getDisplayName());
243        }
244        return true;
245    }
246
247    protected boolean handleVendorSpecificCommand(HdmiCecMessage message) {
248        return false;
249    }
250
251    protected boolean handleRoutingChange(HdmiCecMessage message) {
252        return false;
253    }
254
255    protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
256        return false;
257    }
258
259    protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
260        return false;
261    }
262
263    protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
264        return false;
265    }
266
267    protected boolean handleTerminateArc(HdmiCecMessage message) {
268        return false;
269    }
270
271    protected boolean handleInitiateArc(HdmiCecMessage message) {
272        return false;
273    }
274
275    protected boolean handleReportAudioStatus(HdmiCecMessage message) {
276        return false;
277    }
278
279    @ServiceThreadOnly
280    protected boolean handleStandby(HdmiCecMessage message) {
281        assertRunOnServiceThread();
282        // Seq #12
283        if (mService.isControlEnabled() && !mService.isProhibitMode()
284                && mService.isPowerOnOrTransient()) {
285            mService.standby();
286            return true;
287        }
288        return false;
289    }
290
291    @ServiceThreadOnly
292    protected boolean handleUserControlPressed(HdmiCecMessage message) {
293        assertRunOnServiceThread();
294        if (mService.isPowerOnOrTransient() && isPowerOffOrToggleCommand(message)) {
295            mService.standby();
296            return true;
297        } else if (mService.isPowerStandbyOrTransient() && isPowerOnOrToggleCommand(message)) {
298            mService.wakeUp();
299            return true;
300        }
301        return false;
302    }
303
304    private static boolean isPowerOnOrToggleCommand(HdmiCecMessage message) {
305        byte[] params = message.getParams();
306        return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED
307                && params.length == 1
308                && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER
309                        || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION
310                        || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
311    }
312
313    private static boolean isPowerOffOrToggleCommand(HdmiCecMessage message) {
314        byte[] params = message.getParams();
315        return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED
316                && params.length == 1
317                && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER
318                        || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_OFF_FUNCTION
319                        || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
320    }
321
322    protected boolean handleTextViewOn(HdmiCecMessage message) {
323        return false;
324    }
325
326    protected boolean handleImageViewOn(HdmiCecMessage message) {
327        return false;
328    }
329
330    protected boolean handleSetStreamPath(HdmiCecMessage message) {
331        return false;
332    }
333
334    protected boolean handleGiveDevicePowerStatus(HdmiCecMessage message) {
335        mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPowerStatus(
336                mAddress, message.getSource(), mService.getPowerStatus()));
337        return true;
338    }
339
340    @ServiceThreadOnly
341    final void handleAddressAllocated(int logicalAddress) {
342        assertRunOnServiceThread();
343        mAddress = mPreferredAddress = logicalAddress;
344        onAddressAllocated(logicalAddress);
345    }
346
347    @ServiceThreadOnly
348    HdmiCecDeviceInfo getDeviceInfo() {
349        assertRunOnServiceThread();
350        return mDeviceInfo;
351    }
352
353    @ServiceThreadOnly
354    void setDeviceInfo(HdmiCecDeviceInfo info) {
355        assertRunOnServiceThread();
356        mDeviceInfo = info;
357    }
358
359    // Returns true if the logical address is same as the argument.
360    @ServiceThreadOnly
361    boolean isAddressOf(int addr) {
362        assertRunOnServiceThread();
363        return addr == mAddress;
364    }
365
366    // Resets the logical address to unregistered(15), meaning the logical device is invalid.
367    @ServiceThreadOnly
368    void clearAddress() {
369        assertRunOnServiceThread();
370        mAddress = Constants.ADDR_UNREGISTERED;
371    }
372
373    @ServiceThreadOnly
374    void setPreferredAddress(int addr) {
375        assertRunOnServiceThread();
376        mPreferredAddress = addr;
377    }
378
379    @ServiceThreadOnly
380    int getPreferredAddress() {
381        assertRunOnServiceThread();
382        return mPreferredAddress;
383    }
384
385    @ServiceThreadOnly
386    void addAndStartAction(final FeatureAction action) {
387        assertRunOnServiceThread();
388        if (mService.isPowerStandbyOrTransient()) {
389            Slog.w(TAG, "Skip the action during Standby: " + action);
390            return;
391        }
392        mActions.add(action);
393        action.start();
394    }
395
396    // See if we have an action of a given type in progress.
397    @ServiceThreadOnly
398    <T extends FeatureAction> boolean hasAction(final Class<T> clazz) {
399        assertRunOnServiceThread();
400        for (FeatureAction action : mActions) {
401            if (action.getClass().equals(clazz)) {
402                return true;
403            }
404        }
405        return false;
406    }
407
408    // Returns all actions matched with given class type.
409    @ServiceThreadOnly
410    <T extends FeatureAction> List<T> getActions(final Class<T> clazz) {
411        assertRunOnServiceThread();
412        ArrayList<T> actions = new ArrayList<>();
413        for (FeatureAction action : mActions) {
414            if (action.getClass().equals(clazz)) {
415                actions.add((T) action);
416            }
417        }
418        return actions;
419    }
420
421    /**
422     * Remove the given {@link FeatureAction} object from the action queue.
423     *
424     * @param action {@link FeatureAction} to remove
425     */
426    @ServiceThreadOnly
427    void removeAction(final FeatureAction action) {
428        assertRunOnServiceThread();
429        mActions.remove(action);
430        checkIfPendingActionsCleared();
431    }
432
433    // Remove all actions matched with the given Class type.
434    @ServiceThreadOnly
435    <T extends FeatureAction> void removeAction(final Class<T> clazz) {
436        assertRunOnServiceThread();
437        removeActionExcept(clazz, null);
438    }
439
440    // Remove all actions matched with the given Class type besides |exception|.
441    @ServiceThreadOnly
442    <T extends FeatureAction> void removeActionExcept(final Class<T> clazz,
443            final FeatureAction exception) {
444        assertRunOnServiceThread();
445        Iterator<FeatureAction> iter = mActions.iterator();
446        while (iter.hasNext()) {
447            FeatureAction action = iter.next();
448            if (action != exception && action.getClass().equals(clazz)) {
449                action.clear();
450                mActions.remove(action);
451            }
452        }
453        checkIfPendingActionsCleared();
454    }
455
456    protected void checkIfPendingActionsCleared() {
457        if (mActions.isEmpty()) {
458            mService.onPendingActionsCleared();
459        }
460    }
461    protected void assertRunOnServiceThread() {
462        if (Looper.myLooper() != mService.getServiceLooper()) {
463            throw new IllegalStateException("Should run on service thread.");
464        }
465    }
466
467    /**
468     * Called when a hot-plug event issued.
469     *
470     * @param portId id of port where a hot-plug event happened
471     * @param connected whether to connected or not on the event
472     */
473    void onHotplug(int portId, boolean connected) {
474    }
475
476    final HdmiControlService getService() {
477        return mService;
478    }
479
480    @ServiceThreadOnly
481    final boolean isConnectedToArcPort(int path) {
482        assertRunOnServiceThread();
483        return mService.isConnectedToArcPort(path);
484    }
485
486    int getActiveSource() {
487        synchronized (mLock) {
488            return mActiveSource;
489        }
490    }
491
492    void setActiveSource(int source) {
493        synchronized (mLock) {
494            mActiveSource = source;
495        }
496    }
497
498    int getActivePath() {
499        synchronized (mLock) {
500            return mActiveRoutingPath;
501        }
502    }
503
504    void setActivePath(int path) {
505        synchronized (mLock) {
506            mActiveRoutingPath = path;
507        }
508    }
509
510    /**
511     * Returns the ID of the active HDMI port. The active port is the one that has the active
512     * routing path connected to it directly or indirectly under the device hierarchy.
513     */
514    int getActivePortId() {
515        synchronized (mLock) {
516            return mService.pathToPortId(mActiveRoutingPath);
517        }
518    }
519
520    /**
521     * Update the active port.
522     *
523     * @param portId the new active port id
524     */
525    void setActivePortId(int portId) {
526        synchronized (mLock) {
527            // We update active routing path instead, since we get the active port id from
528            // the active routing path.
529            mActiveRoutingPath = mService.portIdToPath(portId);
530        }
531    }
532
533    void updateActiveDevice(int logicalAddress, int physicalAddress) {
534        synchronized (mLock) {
535            mActiveSource = logicalAddress;
536            mActiveRoutingPath = physicalAddress;
537        }
538    }
539
540    @ServiceThreadOnly
541    HdmiCecMessageCache getCecMessageCache() {
542        assertRunOnServiceThread();
543        return mCecMessageCache;
544    }
545
546    @ServiceThreadOnly
547    int pathToPortId(int newPath) {
548        assertRunOnServiceThread();
549        return mService.pathToPortId(newPath);
550    }
551
552    /**
553     * Called when the system started transition to standby mode.
554     *
555     * @param initiatedByCec true if this power sequence is initiated
556     *         by the reception the CEC messages like <StandBy>
557     */
558    protected void onTransitionToStandby(boolean initiatedByCec) {
559        // If there are no outstanding actions, we'll go to STANDBY state.
560        checkIfPendingActionsCleared();
561    }
562
563    /**
564     * Called when the system goes to standby mode.
565     *
566     * @param initiatedByCec true if this power sequence is initiated
567     *         by the reception the CEC messages like <StandBy>
568     */
569    protected void onStandBy(boolean initiatedByCec) {}
570}
571