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