HdmiCecLocalDevice.java revision e9f6ed3b11fb8ebae5e73db1e4736b86cae272d9
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.HdmiDeviceInfo;
20import android.hardware.input.InputManager;
21import android.os.Handler;
22import android.os.Looper;
23import android.os.Message;
24import android.os.SystemClock;
25import android.util.Slog;
26import android.view.InputDevice;
27import android.view.KeyCharacterMap;
28import android.view.KeyEvent;
29
30import com.android.internal.annotations.GuardedBy;
31import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
32
33import java.util.ArrayList;
34import java.util.Collections;
35import java.util.Iterator;
36import java.util.LinkedList;
37import java.util.List;
38
39/**
40 * Class that models a logical CEC device hosted in this system. Handles initialization,
41 * CEC commands that call for actions customized per device type.
42 */
43abstract class HdmiCecLocalDevice {
44    private static final String TAG = "HdmiCecLocalDevice";
45
46    private static final int MSG_DISABLE_DEVICE_TIMEOUT = 1;
47    private static final int MSG_USER_CONTROL_RELEASE_TIMEOUT = 2;
48    // Timeout in millisecond for device clean up (5s).
49    // Normal actions timeout is 2s but some of them would have several sequence of timeout.
50    private static final int DEVICE_CLEANUP_TIMEOUT = 5000;
51    // Within the timer, a received <User Control Pressed> will start "Press and Hold" behavior.
52    // When it expires, we can assume <User Control Release> is received.
53    private static final int FOLLOWER_SAFETY_TIMEOUT = 550;
54
55    protected final HdmiControlService mService;
56    protected final int mDeviceType;
57    protected int mAddress;
58    protected int mPreferredAddress;
59    protected HdmiDeviceInfo mDeviceInfo;
60    protected int mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE;
61    protected int mLastKeyRepeatCount = 0;
62
63    static class ActiveSource {
64        int logicalAddress;
65        int physicalAddress;
66
67        public ActiveSource() {
68            invalidate();
69        }
70        public ActiveSource(int logical, int physical) {
71            logicalAddress = logical;
72            physicalAddress = physical;
73        }
74        public static ActiveSource of(int logical, int physical) {
75            return new ActiveSource(logical, physical);
76        }
77        public boolean isValid() {
78            return HdmiUtils.isValidAddress(logicalAddress);
79        }
80        public void invalidate() {
81            logicalAddress = Constants.ADDR_INVALID;
82            physicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
83        }
84        public boolean equals(int logical, int physical) {
85            return logicalAddress == logical && physicalAddress == physical;
86        }
87        @Override
88        public boolean equals(Object obj) {
89            if (obj instanceof ActiveSource) {
90                ActiveSource that = (ActiveSource) obj;
91                return that.logicalAddress == logicalAddress &&
92                       that.physicalAddress == physicalAddress;
93            }
94            return false;
95        }
96        @Override
97        public int hashCode() {
98            return logicalAddress * 29 + physicalAddress;
99        }
100    }
101    // Logical address of the active source.
102    @GuardedBy("mLock")
103    protected final ActiveSource mActiveSource = new ActiveSource();
104
105    // Active routing path. Physical address of the active source but not all the time, such as
106    // when the new active source does not claim itself to be one. Note that we don't keep
107    // the active port id (or active input) since it can be gotten by {@link #pathToPortId(int)}.
108    @GuardedBy("mLock")
109    private int mActiveRoutingPath;
110
111    protected final HdmiCecMessageCache mCecMessageCache = new HdmiCecMessageCache();
112    protected final Object mLock;
113
114    // A collection of FeatureAction.
115    // Note that access to this collection should happen in service thread.
116    private final LinkedList<HdmiCecFeatureAction> mActions = new LinkedList<>();
117
118    private final Handler mHandler = new Handler () {
119        @Override
120        public void handleMessage(Message msg) {
121            switch (msg.what) {
122                case MSG_DISABLE_DEVICE_TIMEOUT:
123                    handleDisableDeviceTimeout();
124                    break;
125                case MSG_USER_CONTROL_RELEASE_TIMEOUT:
126                    handleUserControlReleased();
127                    break;
128            }
129        }
130    };
131
132    /**
133     * A callback interface to get notified when all pending action is cleared.
134     * It can be called when timeout happened.
135     */
136    interface PendingActionClearedCallback {
137        void onCleared(HdmiCecLocalDevice device);
138    }
139
140    protected PendingActionClearedCallback mPendingActionClearedCallback;
141
142    protected HdmiCecLocalDevice(HdmiControlService service, int deviceType) {
143        mService = service;
144        mDeviceType = deviceType;
145        mAddress = Constants.ADDR_UNREGISTERED;
146        mLock = service.getServiceLock();
147    }
148
149    // Factory method that returns HdmiCecLocalDevice of corresponding type.
150    static HdmiCecLocalDevice create(HdmiControlService service, int deviceType) {
151        switch (deviceType) {
152        case HdmiDeviceInfo.DEVICE_TV:
153            return new HdmiCecLocalDeviceTv(service);
154        case HdmiDeviceInfo.DEVICE_PLAYBACK:
155            return new HdmiCecLocalDevicePlayback(service);
156        default:
157            return null;
158        }
159    }
160
161    @ServiceThreadOnly
162    void init() {
163        assertRunOnServiceThread();
164        mPreferredAddress = getPreferredAddress();
165    }
166
167    /**
168     * Called once a logical address of the local device is allocated.
169     */
170    protected abstract void onAddressAllocated(int logicalAddress, int reason);
171
172    /**
173     * Get the preferred logical address from system properties.
174     */
175    protected abstract int getPreferredAddress();
176
177    /**
178     * Set the preferred logical address to system properties.
179     */
180    protected abstract void setPreferredAddress(int addr);
181
182    /**
183     * Dispatch incoming message.
184     *
185     * @param message incoming message
186     * @return true if consumed a message; otherwise, return false.
187     */
188    @ServiceThreadOnly
189    boolean dispatchMessage(HdmiCecMessage message) {
190        assertRunOnServiceThread();
191        int dest = message.getDestination();
192        if (dest != mAddress && dest != Constants.ADDR_BROADCAST) {
193            return false;
194        }
195        // Cache incoming message. Note that it caches only white-listed one.
196        mCecMessageCache.cacheMessage(message);
197        return onMessage(message);
198    }
199
200    @ServiceThreadOnly
201    protected final boolean onMessage(HdmiCecMessage message) {
202        assertRunOnServiceThread();
203        if (dispatchMessageToAction(message)) {
204            return true;
205        }
206        switch (message.getOpcode()) {
207            case Constants.MESSAGE_ACTIVE_SOURCE:
208                return handleActiveSource(message);
209            case Constants.MESSAGE_INACTIVE_SOURCE:
210                return handleInactiveSource(message);
211            case Constants.MESSAGE_REQUEST_ACTIVE_SOURCE:
212                return handleRequestActiveSource(message);
213            case Constants.MESSAGE_GET_MENU_LANGUAGE:
214                return handleGetMenuLanguage(message);
215            case Constants.MESSAGE_GIVE_PHYSICAL_ADDRESS:
216                return handleGivePhysicalAddress();
217            case Constants.MESSAGE_GIVE_OSD_NAME:
218                return handleGiveOsdName(message);
219            case Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID:
220                return handleGiveDeviceVendorId();
221            case Constants.MESSAGE_GET_CEC_VERSION:
222                return handleGetCecVersion(message);
223            case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS:
224                return handleReportPhysicalAddress(message);
225            case Constants.MESSAGE_ROUTING_CHANGE:
226                return handleRoutingChange(message);
227            case Constants.MESSAGE_ROUTING_INFORMATION:
228                return handleRoutingInformation(message);
229            case Constants.MESSAGE_INITIATE_ARC:
230                return handleInitiateArc(message);
231            case Constants.MESSAGE_TERMINATE_ARC:
232                return handleTerminateArc(message);
233            case Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE:
234                return handleSetSystemAudioMode(message);
235            case Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS:
236                return handleSystemAudioModeStatus(message);
237            case Constants.MESSAGE_REPORT_AUDIO_STATUS:
238                return handleReportAudioStatus(message);
239            case Constants.MESSAGE_STANDBY:
240                return handleStandby(message);
241            case Constants.MESSAGE_TEXT_VIEW_ON:
242                return handleTextViewOn(message);
243            case Constants.MESSAGE_IMAGE_VIEW_ON:
244                return handleImageViewOn(message);
245            case Constants.MESSAGE_USER_CONTROL_PRESSED:
246                return handleUserControlPressed(message);
247            case Constants.MESSAGE_USER_CONTROL_RELEASED:
248                return handleUserControlReleased();
249            case Constants.MESSAGE_SET_STREAM_PATH:
250                return handleSetStreamPath(message);
251            case Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS:
252                return handleGiveDevicePowerStatus(message);
253            case Constants.MESSAGE_MENU_REQUEST:
254                return handleGiveDeviceMenuStatus(message);
255            case Constants.MESSAGE_VENDOR_COMMAND:
256                return handleVendorCommand(message);
257            case Constants.MESSAGE_VENDOR_COMMAND_WITH_ID:
258                return handleVendorCommandWithId(message);
259            case Constants.MESSAGE_SET_OSD_NAME:
260                return handleSetOsdName(message);
261            case Constants.MESSAGE_RECORD_TV_SCREEN:
262                return handleRecordTvScreen(message);
263            case Constants.MESSAGE_TIMER_CLEARED_STATUS:
264                return handleTimerClearedStatus(message);
265            default:
266                return false;
267        }
268    }
269
270    @ServiceThreadOnly
271    private boolean dispatchMessageToAction(HdmiCecMessage message) {
272        assertRunOnServiceThread();
273        for (HdmiCecFeatureAction action : mActions) {
274            if (action.processCommand(message)) {
275                return true;
276            }
277        }
278        return false;
279    }
280
281    @ServiceThreadOnly
282    protected boolean handleGivePhysicalAddress() {
283        assertRunOnServiceThread();
284
285        int physicalAddress = mService.getPhysicalAddress();
286        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
287                mAddress, physicalAddress, mDeviceType);
288        mService.sendCecCommand(cecMessage);
289        return true;
290    }
291
292    @ServiceThreadOnly
293    protected boolean handleGiveDeviceVendorId() {
294        assertRunOnServiceThread();
295        int vendorId = mService.getVendorId();
296        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
297                mAddress, vendorId);
298        mService.sendCecCommand(cecMessage);
299        return true;
300    }
301
302    @ServiceThreadOnly
303    protected boolean handleGetCecVersion(HdmiCecMessage message) {
304        assertRunOnServiceThread();
305        int version = mService.getCecVersion();
306        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(),
307                message.getSource(), version);
308        mService.sendCecCommand(cecMessage);
309        return true;
310    }
311
312    @ServiceThreadOnly
313    protected boolean handleActiveSource(HdmiCecMessage message) {
314        return false;
315    }
316
317    @ServiceThreadOnly
318    protected boolean handleInactiveSource(HdmiCecMessage message) {
319        return false;
320    }
321
322    @ServiceThreadOnly
323    protected boolean handleRequestActiveSource(HdmiCecMessage message) {
324        return false;
325    }
326
327    @ServiceThreadOnly
328    protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
329        assertRunOnServiceThread();
330        Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString());
331        // 'return false' will cause to reply with <Feature Abort>.
332        return false;
333    }
334
335    @ServiceThreadOnly
336    protected boolean handleGiveOsdName(HdmiCecMessage message) {
337        assertRunOnServiceThread();
338        // Note that since this method is called after logical address allocation is done,
339        // mDeviceInfo should not be null.
340        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand(
341                mAddress, message.getSource(), mDeviceInfo.getDisplayName());
342        if (cecMessage != null) {
343            mService.sendCecCommand(cecMessage);
344        } else {
345            Slog.w(TAG, "Failed to build <Get Osd Name>:" + mDeviceInfo.getDisplayName());
346        }
347        return true;
348    }
349
350    protected boolean handleRoutingChange(HdmiCecMessage message) {
351        return false;
352    }
353
354    protected boolean handleRoutingInformation(HdmiCecMessage message) {
355        return false;
356    }
357
358    protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
359        return false;
360    }
361
362    protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
363        return false;
364    }
365
366    protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
367        return false;
368    }
369
370    protected boolean handleTerminateArc(HdmiCecMessage message) {
371        return false;
372    }
373
374    protected boolean handleInitiateArc(HdmiCecMessage message) {
375        return false;
376    }
377
378    protected boolean handleReportAudioStatus(HdmiCecMessage message) {
379        return false;
380    }
381
382    @ServiceThreadOnly
383    protected boolean handleStandby(HdmiCecMessage message) {
384        assertRunOnServiceThread();
385        // Seq #12
386        if (mService.isControlEnabled() && !mService.isProhibitMode()
387                && mService.isPowerOnOrTransient()) {
388            mService.standby();
389            return true;
390        }
391        return false;
392    }
393
394    @ServiceThreadOnly
395    protected boolean handleUserControlPressed(HdmiCecMessage message) {
396        assertRunOnServiceThread();
397        mHandler.removeMessages(MSG_USER_CONTROL_RELEASE_TIMEOUT);
398        if (mService.isPowerOnOrTransient() && isPowerOffOrToggleCommand(message)) {
399            mService.standby();
400            return true;
401        } else if (mService.isPowerStandbyOrTransient() && isPowerOnOrToggleCommand(message)) {
402            mService.wakeUp();
403            return true;
404        }
405
406        final long downTime = SystemClock.uptimeMillis();
407        final byte[] params = message.getParams();
408        // Note that we don't support parameterized keycode now.
409        // TODO: translate parameterized keycode as well.
410        final int keycode = HdmiCecKeycode.cecKeyToAndroidKey(params[0]);
411        int keyRepeatCount = 0;
412        if (mLastKeycode != HdmiCecKeycode.UNSUPPORTED_KEYCODE) {
413            if (keycode == mLastKeycode) {
414                keyRepeatCount = mLastKeyRepeatCount + 1;
415            } else {
416                injectKeyEvent(downTime, KeyEvent.ACTION_UP, mLastKeycode, 0);
417            }
418        }
419        mLastKeycode = keycode;
420        mLastKeyRepeatCount = keyRepeatCount;
421
422        if (keycode != HdmiCecKeycode.UNSUPPORTED_KEYCODE) {
423            injectKeyEvent(downTime, KeyEvent.ACTION_DOWN, keycode, keyRepeatCount);
424            mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_USER_CONTROL_RELEASE_TIMEOUT),
425                    FOLLOWER_SAFETY_TIMEOUT);
426            return true;
427        }
428        return false;
429    }
430
431    @ServiceThreadOnly
432    protected boolean handleUserControlReleased() {
433        assertRunOnServiceThread();
434        mHandler.removeMessages(MSG_USER_CONTROL_RELEASE_TIMEOUT);
435        mLastKeyRepeatCount = 0;
436        if (mLastKeycode != HdmiCecKeycode.UNSUPPORTED_KEYCODE) {
437            final long upTime = SystemClock.uptimeMillis();
438            injectKeyEvent(upTime, KeyEvent.ACTION_UP, mLastKeycode, 0);
439            mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE;
440            return true;
441        }
442        return false;
443    }
444
445    static void injectKeyEvent(long time, int action, int keycode, int repeat) {
446        KeyEvent keyEvent = KeyEvent.obtain(time, time, action, keycode,
447                repeat, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM,
448                InputDevice.SOURCE_HDMI, null);
449        InputManager.getInstance().injectInputEvent(keyEvent,
450                InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
451        keyEvent.recycle();
452   }
453
454    static boolean isPowerOnOrToggleCommand(HdmiCecMessage message) {
455        byte[] params = message.getParams();
456        return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED
457                && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER
458                        || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION
459                        || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
460    }
461
462    static boolean isPowerOffOrToggleCommand(HdmiCecMessage message) {
463        byte[] params = message.getParams();
464        return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED
465                && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER
466                        || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_OFF_FUNCTION
467                        || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
468    }
469
470    protected boolean handleTextViewOn(HdmiCecMessage message) {
471        return false;
472    }
473
474    protected boolean handleImageViewOn(HdmiCecMessage message) {
475        return false;
476    }
477
478    protected boolean handleSetStreamPath(HdmiCecMessage message) {
479        return false;
480    }
481
482    protected boolean handleGiveDevicePowerStatus(HdmiCecMessage message) {
483        mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPowerStatus(
484                mAddress, message.getSource(), mService.getPowerStatus()));
485        return true;
486    }
487
488    protected boolean handleGiveDeviceMenuStatus(HdmiCecMessage message) {
489        // Always report menu active to receive Remote Control.
490        mService.sendCecCommand(HdmiCecMessageBuilder.buildReportMenuStatus(
491                mAddress, message.getSource(), Constants.MENU_STATE_ACTIVATED));
492        return true;
493    }
494
495    protected boolean handleVendorCommand(HdmiCecMessage message) {
496        mService.invokeVendorCommandListeners(mDeviceType, message.getSource(),
497                message.getParams(), false);
498        return true;
499    }
500
501    protected boolean handleVendorCommandWithId(HdmiCecMessage message) {
502        byte[] params = message.getParams();
503        int vendorId = HdmiUtils.threeBytesToInt(params);
504        if (vendorId == mService.getVendorId()) {
505            mService.invokeVendorCommandListeners(mDeviceType, message.getSource(), params, true);
506        } else if (message.getDestination() != Constants.ADDR_BROADCAST &&
507                message.getSource() != Constants.ADDR_UNREGISTERED) {
508            Slog.v(TAG, "Wrong direct vendor command. Replying with <Feature Abort>");
509            mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
510        } else {
511            Slog.v(TAG, "Wrong broadcast vendor command. Ignoring");
512        }
513        return true;
514    }
515
516    protected boolean handleSetOsdName(HdmiCecMessage message) {
517        // The default behavior of <Set Osd Name> is doing nothing.
518        return true;
519    }
520
521    protected boolean handleRecordTvScreen(HdmiCecMessage message) {
522        // The default behavior of <Record TV Screen> is replying <Feature Abort> with
523        // "Cannot provide source".
524        mService.maySendFeatureAbortCommand(message, Constants.ABORT_CANNOT_PROVIDE_SOURCE);
525        return true;
526    }
527
528    protected boolean handleTimerClearedStatus(HdmiCecMessage message) {
529        return false;
530    }
531
532    @ServiceThreadOnly
533    final void handleAddressAllocated(int logicalAddress, int reason) {
534        assertRunOnServiceThread();
535        mAddress = mPreferredAddress = logicalAddress;
536        onAddressAllocated(logicalAddress, reason);
537        setPreferredAddress(logicalAddress);
538    }
539
540    @ServiceThreadOnly
541    HdmiDeviceInfo getDeviceInfo() {
542        assertRunOnServiceThread();
543        return mDeviceInfo;
544    }
545
546    @ServiceThreadOnly
547    void setDeviceInfo(HdmiDeviceInfo info) {
548        assertRunOnServiceThread();
549        mDeviceInfo = info;
550    }
551
552    // Returns true if the logical address is same as the argument.
553    @ServiceThreadOnly
554    boolean isAddressOf(int addr) {
555        assertRunOnServiceThread();
556        return addr == mAddress;
557    }
558
559    // Resets the logical address to unregistered(15), meaning the logical device is invalid.
560    @ServiceThreadOnly
561    void clearAddress() {
562        assertRunOnServiceThread();
563        mAddress = Constants.ADDR_UNREGISTERED;
564    }
565
566    @ServiceThreadOnly
567    void addAndStartAction(final HdmiCecFeatureAction action) {
568        assertRunOnServiceThread();
569        if (mService.isPowerStandbyOrTransient()) {
570            Slog.w(TAG, "Skip the action during Standby: " + action);
571            return;
572        }
573        mActions.add(action);
574        action.start();
575    }
576
577    // See if we have an action of a given type in progress.
578    @ServiceThreadOnly
579    <T extends HdmiCecFeatureAction> boolean hasAction(final Class<T> clazz) {
580        assertRunOnServiceThread();
581        for (HdmiCecFeatureAction action : mActions) {
582            if (action.getClass().equals(clazz)) {
583                return true;
584            }
585        }
586        return false;
587    }
588
589    // Returns all actions matched with given class type.
590    @ServiceThreadOnly
591    <T extends HdmiCecFeatureAction> List<T> getActions(final Class<T> clazz) {
592        assertRunOnServiceThread();
593        List<T> actions = Collections.<T>emptyList();
594        for (HdmiCecFeatureAction action : mActions) {
595            if (action.getClass().equals(clazz)) {
596                if (actions.isEmpty()) {
597                    actions = new ArrayList<T>();
598                }
599                actions.add((T) action);
600            }
601        }
602        return actions;
603    }
604
605    /**
606     * Remove the given {@link HdmiCecFeatureAction} object from the action queue.
607     *
608     * @param action {@link HdmiCecFeatureAction} to remove
609     */
610    @ServiceThreadOnly
611    void removeAction(final HdmiCecFeatureAction action) {
612        assertRunOnServiceThread();
613        action.finish(false);
614        mActions.remove(action);
615        checkIfPendingActionsCleared();
616    }
617
618    // Remove all actions matched with the given Class type.
619    @ServiceThreadOnly
620    <T extends HdmiCecFeatureAction> void removeAction(final Class<T> clazz) {
621        assertRunOnServiceThread();
622        removeActionExcept(clazz, null);
623    }
624
625    // Remove all actions matched with the given Class type besides |exception|.
626    @ServiceThreadOnly
627    <T extends HdmiCecFeatureAction> void removeActionExcept(final Class<T> clazz,
628            final HdmiCecFeatureAction exception) {
629        assertRunOnServiceThread();
630        Iterator<HdmiCecFeatureAction> iter = mActions.iterator();
631        while (iter.hasNext()) {
632            HdmiCecFeatureAction action = iter.next();
633            if (action != exception && action.getClass().equals(clazz)) {
634                action.finish(false);
635                iter.remove();
636            }
637        }
638        checkIfPendingActionsCleared();
639    }
640
641    protected void checkIfPendingActionsCleared() {
642        if (mActions.isEmpty() && mPendingActionClearedCallback != null) {
643            PendingActionClearedCallback callback = mPendingActionClearedCallback;
644            // To prevent from calling the callback again during handling the callback itself.
645            mPendingActionClearedCallback = null;
646            callback.onCleared(this);
647        }
648    }
649
650    protected void assertRunOnServiceThread() {
651        if (Looper.myLooper() != mService.getServiceLooper()) {
652            throw new IllegalStateException("Should run on service thread.");
653        }
654    }
655
656    /**
657     * Called when a hot-plug event issued.
658     *
659     * @param portId id of port where a hot-plug event happened
660     * @param connected whether to connected or not on the event
661     */
662    void onHotplug(int portId, boolean connected) {
663    }
664
665    final HdmiControlService getService() {
666        return mService;
667    }
668
669    @ServiceThreadOnly
670    final boolean isConnectedToArcPort(int path) {
671        assertRunOnServiceThread();
672        return mService.isConnectedToArcPort(path);
673    }
674
675    ActiveSource getActiveSource() {
676        synchronized (mLock) {
677            return mActiveSource;
678        }
679    }
680
681    void setActiveSource(ActiveSource newActive) {
682        setActiveSource(newActive.logicalAddress, newActive.physicalAddress);
683    }
684
685    void setActiveSource(HdmiDeviceInfo info) {
686        setActiveSource(info.getLogicalAddress(), info.getPhysicalAddress());
687    }
688
689    void setActiveSource(int logicalAddress, int physicalAddress) {
690        synchronized (mLock) {
691            mActiveSource.logicalAddress = logicalAddress;
692            mActiveSource.physicalAddress = physicalAddress;
693        }
694        mService.setLastInputForMhl(Constants.INVALID_PORT_ID);
695    }
696
697    int getActivePath() {
698        synchronized (mLock) {
699            return mActiveRoutingPath;
700        }
701    }
702
703    void setActivePath(int path) {
704        synchronized (mLock) {
705            mActiveRoutingPath = path;
706        }
707        mService.setActivePortId(pathToPortId(path));
708    }
709
710    /**
711     * Returns the ID of the active HDMI port. The active port is the one that has the active
712     * routing path connected to it directly or indirectly under the device hierarchy.
713     */
714    int getActivePortId() {
715        synchronized (mLock) {
716            return mService.pathToPortId(mActiveRoutingPath);
717        }
718    }
719
720    /**
721     * Update the active port.
722     *
723     * @param portId the new active port id
724     */
725    void setActivePortId(int portId) {
726        // We update active routing path instead, since we get the active port id from
727        // the active routing path.
728        setActivePath(mService.portIdToPath(portId));
729    }
730
731    @ServiceThreadOnly
732    HdmiCecMessageCache getCecMessageCache() {
733        assertRunOnServiceThread();
734        return mCecMessageCache;
735    }
736
737    @ServiceThreadOnly
738    int pathToPortId(int newPath) {
739        assertRunOnServiceThread();
740        return mService.pathToPortId(newPath);
741    }
742
743    /**
744     * Called when the system goes to standby mode.
745     *
746     * @param initiatedByCec true if this power sequence is initiated
747     *        by the reception the CEC messages like &lt;Standby&gt;
748     */
749    protected void onStandby(boolean initiatedByCec) {}
750
751    /**
752     * Disable device. {@code callback} is used to get notified when all pending
753     * actions are completed or timeout is issued.
754     *
755     * @param initiatedByCec true if this sequence is initiated
756     *        by the reception the CEC messages like &lt;Standby&gt;
757     * @param origialCallback callback interface to get notified when all pending actions are
758     *        cleared
759     */
760    protected void disableDevice(boolean initiatedByCec,
761            final PendingActionClearedCallback origialCallback) {
762        mPendingActionClearedCallback = new PendingActionClearedCallback() {
763            @Override
764            public void onCleared(HdmiCecLocalDevice device) {
765                mHandler.removeMessages(MSG_DISABLE_DEVICE_TIMEOUT);
766                origialCallback.onCleared(device);
767            }
768        };
769        mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_DISABLE_DEVICE_TIMEOUT),
770                DEVICE_CLEANUP_TIMEOUT);
771    }
772
773    @ServiceThreadOnly
774    private void handleDisableDeviceTimeout() {
775        assertRunOnServiceThread();
776
777        // If all actions are not cleared in DEVICE_CLEANUP_TIMEOUT, enforce to finish them.
778        // onCleard will be called at the last action's finish method.
779        Iterator<HdmiCecFeatureAction> iter = mActions.iterator();
780        while (iter.hasNext()) {
781            HdmiCecFeatureAction action = iter.next();
782            action.finish(false);
783            iter.remove();
784        }
785    }
786
787    /**
788     * Send a key event to other device.
789     *
790     * @param keyCode key code defined in {@link android.view.KeyEvent}
791     * @param isPressed {@code true} for key down event
792     */
793    protected void sendKeyEvent(int keyCode, boolean isPressed) {
794        Slog.w(TAG, "sendKeyEvent not implemented");
795    }
796}
797