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