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