HdmiCecLocalDevice.java revision 73483b6bc9046cbb7a54748c31ee724358a631ef
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        final int keycode = HdmiCecKeycode.cecKeycodeAndParamsToAndroidKey(params);
430        int keyRepeatCount = 0;
431        if (mLastKeycode != HdmiCecKeycode.UNSUPPORTED_KEYCODE) {
432            if (keycode == mLastKeycode) {
433                keyRepeatCount = mLastKeyRepeatCount + 1;
434            } else {
435                injectKeyEvent(downTime, KeyEvent.ACTION_UP, mLastKeycode, 0);
436            }
437        }
438        mLastKeycode = keycode;
439        mLastKeyRepeatCount = keyRepeatCount;
440
441        if (keycode != HdmiCecKeycode.UNSUPPORTED_KEYCODE) {
442            injectKeyEvent(downTime, KeyEvent.ACTION_DOWN, keycode, keyRepeatCount);
443            mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_USER_CONTROL_RELEASE_TIMEOUT),
444                    FOLLOWER_SAFETY_TIMEOUT);
445            return true;
446        }
447        return false;
448    }
449
450    @ServiceThreadOnly
451    protected boolean handleUserControlReleased() {
452        assertRunOnServiceThread();
453        mHandler.removeMessages(MSG_USER_CONTROL_RELEASE_TIMEOUT);
454        mLastKeyRepeatCount = 0;
455        if (mLastKeycode != HdmiCecKeycode.UNSUPPORTED_KEYCODE) {
456            final long upTime = SystemClock.uptimeMillis();
457            injectKeyEvent(upTime, KeyEvent.ACTION_UP, mLastKeycode, 0);
458            mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE;
459            return true;
460        }
461        return false;
462    }
463
464    static void injectKeyEvent(long time, int action, int keycode, int repeat) {
465        KeyEvent keyEvent = KeyEvent.obtain(time, time, action, keycode,
466                repeat, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM,
467                InputDevice.SOURCE_HDMI, null);
468        InputManager.getInstance().injectInputEvent(keyEvent,
469                InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
470        keyEvent.recycle();
471   }
472
473    static boolean isPowerOnOrToggleCommand(HdmiCecMessage message) {
474        byte[] params = message.getParams();
475        return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED
476                && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER
477                        || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION
478                        || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
479    }
480
481    static boolean isPowerOffOrToggleCommand(HdmiCecMessage message) {
482        byte[] params = message.getParams();
483        return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED
484                && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER
485                        || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_OFF_FUNCTION
486                        || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
487    }
488
489    protected boolean handleTextViewOn(HdmiCecMessage message) {
490        return false;
491    }
492
493    protected boolean handleImageViewOn(HdmiCecMessage message) {
494        return false;
495    }
496
497    protected boolean handleSetStreamPath(HdmiCecMessage message) {
498        return false;
499    }
500
501    protected boolean handleGiveDevicePowerStatus(HdmiCecMessage message) {
502        mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPowerStatus(
503                mAddress, message.getSource(), mService.getPowerStatus()));
504        return true;
505    }
506
507    protected boolean handleMenuRequest(HdmiCecMessage message) {
508        // Always report menu active to receive Remote Control.
509        mService.sendCecCommand(HdmiCecMessageBuilder.buildReportMenuStatus(
510                mAddress, message.getSource(), Constants.MENU_STATE_ACTIVATED));
511        return true;
512    }
513
514    protected boolean handleMenuStatus(HdmiCecMessage message) {
515        return false;
516    }
517
518    protected boolean handleVendorCommand(HdmiCecMessage message) {
519        if (!mService.invokeVendorCommandListeners(mDeviceType, message.getSource(),
520                message.getParams(), false)) {
521            // Vendor command listener may not have been registered yet. Respond with
522            // <Feature Abort> [NOT_IN_CORRECT_MODE] so that the sender can try again later.
523            mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE);
524        }
525        return true;
526    }
527
528    protected boolean handleVendorCommandWithId(HdmiCecMessage message) {
529        byte[] params = message.getParams();
530        int vendorId = HdmiUtils.threeBytesToInt(params);
531        if (vendorId == mService.getVendorId()) {
532            if (!mService.invokeVendorCommandListeners(mDeviceType, message.getSource(), params,
533                    true)) {
534                mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE);
535            }
536        } else if (message.getDestination() != Constants.ADDR_BROADCAST &&
537                message.getSource() != Constants.ADDR_UNREGISTERED) {
538            Slog.v(TAG, "Wrong direct vendor command. Replying with <Feature Abort>");
539            mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
540        } else {
541            Slog.v(TAG, "Wrong broadcast vendor command. Ignoring");
542        }
543        return true;
544    }
545
546    protected void sendStandby(int deviceId) {
547        // Do nothing.
548    }
549
550    protected boolean handleSetOsdName(HdmiCecMessage message) {
551        // The default behavior of <Set Osd Name> is doing nothing.
552        return true;
553    }
554
555    protected boolean handleRecordTvScreen(HdmiCecMessage message) {
556        // The default behavior of <Record TV Screen> is replying <Feature Abort> with
557        // "Cannot provide source".
558        mService.maySendFeatureAbortCommand(message, Constants.ABORT_CANNOT_PROVIDE_SOURCE);
559        return true;
560    }
561
562    protected boolean handleTimerClearedStatus(HdmiCecMessage message) {
563        return false;
564    }
565
566    protected boolean handleReportPowerStatus(HdmiCecMessage message) {
567        return false;
568    }
569
570    protected boolean handleTimerStatus(HdmiCecMessage message) {
571        return false;
572    }
573
574    protected boolean handleRecordStatus(HdmiCecMessage message) {
575        return false;
576    }
577
578    @ServiceThreadOnly
579    final void handleAddressAllocated(int logicalAddress, int reason) {
580        assertRunOnServiceThread();
581        mAddress = mPreferredAddress = logicalAddress;
582        onAddressAllocated(logicalAddress, reason);
583        setPreferredAddress(logicalAddress);
584    }
585
586    int getType() {
587        return mDeviceType;
588    }
589
590    @ServiceThreadOnly
591    HdmiDeviceInfo getDeviceInfo() {
592        assertRunOnServiceThread();
593        return mDeviceInfo;
594    }
595
596    @ServiceThreadOnly
597    void setDeviceInfo(HdmiDeviceInfo info) {
598        assertRunOnServiceThread();
599        mDeviceInfo = info;
600    }
601
602    // Returns true if the logical address is same as the argument.
603    @ServiceThreadOnly
604    boolean isAddressOf(int addr) {
605        assertRunOnServiceThread();
606        return addr == mAddress;
607    }
608
609    // Resets the logical address to unregistered(15), meaning the logical device is invalid.
610    @ServiceThreadOnly
611    void clearAddress() {
612        assertRunOnServiceThread();
613        mAddress = Constants.ADDR_UNREGISTERED;
614    }
615
616    @ServiceThreadOnly
617    void addAndStartAction(final HdmiCecFeatureAction action) {
618        assertRunOnServiceThread();
619        if (mService.isPowerStandbyOrTransient()) {
620            Slog.w(TAG, "Skip the action during Standby: " + action);
621            return;
622        }
623        mActions.add(action);
624        action.start();
625    }
626
627    // See if we have an action of a given type in progress.
628    @ServiceThreadOnly
629    <T extends HdmiCecFeatureAction> boolean hasAction(final Class<T> clazz) {
630        assertRunOnServiceThread();
631        for (HdmiCecFeatureAction action : mActions) {
632            if (action.getClass().equals(clazz)) {
633                return true;
634            }
635        }
636        return false;
637    }
638
639    // Returns all actions matched with given class type.
640    @ServiceThreadOnly
641    <T extends HdmiCecFeatureAction> List<T> getActions(final Class<T> clazz) {
642        assertRunOnServiceThread();
643        List<T> actions = Collections.<T>emptyList();
644        for (HdmiCecFeatureAction action : mActions) {
645            if (action.getClass().equals(clazz)) {
646                if (actions.isEmpty()) {
647                    actions = new ArrayList<T>();
648                }
649                actions.add((T) action);
650            }
651        }
652        return actions;
653    }
654
655    /**
656     * Remove the given {@link HdmiCecFeatureAction} object from the action queue.
657     *
658     * @param action {@link HdmiCecFeatureAction} to remove
659     */
660    @ServiceThreadOnly
661    void removeAction(final HdmiCecFeatureAction action) {
662        assertRunOnServiceThread();
663        action.finish(false);
664        mActions.remove(action);
665        checkIfPendingActionsCleared();
666    }
667
668    // Remove all actions matched with the given Class type.
669    @ServiceThreadOnly
670    <T extends HdmiCecFeatureAction> void removeAction(final Class<T> clazz) {
671        assertRunOnServiceThread();
672        removeActionExcept(clazz, null);
673    }
674
675    // Remove all actions matched with the given Class type besides |exception|.
676    @ServiceThreadOnly
677    <T extends HdmiCecFeatureAction> void removeActionExcept(final Class<T> clazz,
678            final HdmiCecFeatureAction exception) {
679        assertRunOnServiceThread();
680        Iterator<HdmiCecFeatureAction> iter = mActions.iterator();
681        while (iter.hasNext()) {
682            HdmiCecFeatureAction action = iter.next();
683            if (action != exception && action.getClass().equals(clazz)) {
684                action.finish(false);
685                iter.remove();
686            }
687        }
688        checkIfPendingActionsCleared();
689    }
690
691    protected void checkIfPendingActionsCleared() {
692        if (mActions.isEmpty() && mPendingActionClearedCallback != null) {
693            PendingActionClearedCallback callback = mPendingActionClearedCallback;
694            // To prevent from calling the callback again during handling the callback itself.
695            mPendingActionClearedCallback = null;
696            callback.onCleared(this);
697        }
698    }
699
700    protected void assertRunOnServiceThread() {
701        if (Looper.myLooper() != mService.getServiceLooper()) {
702            throw new IllegalStateException("Should run on service thread.");
703        }
704    }
705
706    /**
707     * Called when a hot-plug event issued.
708     *
709     * @param portId id of port where a hot-plug event happened
710     * @param connected whether to connected or not on the event
711     */
712    void onHotplug(int portId, boolean connected) {
713    }
714
715    final HdmiControlService getService() {
716        return mService;
717    }
718
719    @ServiceThreadOnly
720    final boolean isConnectedToArcPort(int path) {
721        assertRunOnServiceThread();
722        return mService.isConnectedToArcPort(path);
723    }
724
725    ActiveSource getActiveSource() {
726        synchronized (mLock) {
727            return mActiveSource;
728        }
729    }
730
731    void setActiveSource(ActiveSource newActive) {
732        setActiveSource(newActive.logicalAddress, newActive.physicalAddress);
733    }
734
735    void setActiveSource(HdmiDeviceInfo info) {
736        setActiveSource(info.getLogicalAddress(), info.getPhysicalAddress());
737    }
738
739    void setActiveSource(int logicalAddress, int physicalAddress) {
740        synchronized (mLock) {
741            mActiveSource.logicalAddress = logicalAddress;
742            mActiveSource.physicalAddress = physicalAddress;
743        }
744        mService.setLastInputForMhl(Constants.INVALID_PORT_ID);
745    }
746
747    int getActivePath() {
748        synchronized (mLock) {
749            return mActiveRoutingPath;
750        }
751    }
752
753    void setActivePath(int path) {
754        synchronized (mLock) {
755            mActiveRoutingPath = path;
756        }
757        mService.setActivePortId(pathToPortId(path));
758    }
759
760    /**
761     * Returns the ID of the active HDMI port. The active port is the one that has the active
762     * routing path connected to it directly or indirectly under the device hierarchy.
763     */
764    int getActivePortId() {
765        synchronized (mLock) {
766            return mService.pathToPortId(mActiveRoutingPath);
767        }
768    }
769
770    /**
771     * Update the active port.
772     *
773     * @param portId the new active port id
774     */
775    void setActivePortId(int portId) {
776        // We update active routing path instead, since we get the active port id from
777        // the active routing path.
778        setActivePath(mService.portIdToPath(portId));
779    }
780
781    @ServiceThreadOnly
782    HdmiCecMessageCache getCecMessageCache() {
783        assertRunOnServiceThread();
784        return mCecMessageCache;
785    }
786
787    @ServiceThreadOnly
788    int pathToPortId(int newPath) {
789        assertRunOnServiceThread();
790        return mService.pathToPortId(newPath);
791    }
792
793    /**
794     * Called when the system goes to standby mode.
795     *
796     * @param initiatedByCec true if this power sequence is initiated
797     *        by the reception the CEC messages like &lt;Standby&gt;
798     */
799    protected void onStandby(boolean initiatedByCec) {}
800
801    /**
802     * Disable device. {@code callback} is used to get notified when all pending
803     * actions are completed or timeout is issued.
804     *
805     * @param initiatedByCec true if this sequence is initiated
806     *        by the reception the CEC messages like &lt;Standby&gt;
807     * @param origialCallback callback interface to get notified when all pending actions are
808     *        cleared
809     */
810    protected void disableDevice(boolean initiatedByCec,
811            final PendingActionClearedCallback origialCallback) {
812        mPendingActionClearedCallback = new PendingActionClearedCallback() {
813            @Override
814            public void onCleared(HdmiCecLocalDevice device) {
815                mHandler.removeMessages(MSG_DISABLE_DEVICE_TIMEOUT);
816                origialCallback.onCleared(device);
817            }
818        };
819        mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_DISABLE_DEVICE_TIMEOUT),
820                DEVICE_CLEANUP_TIMEOUT);
821    }
822
823    @ServiceThreadOnly
824    private void handleDisableDeviceTimeout() {
825        assertRunOnServiceThread();
826
827        // If all actions are not cleared in DEVICE_CLEANUP_TIMEOUT, enforce to finish them.
828        // onCleard will be called at the last action's finish method.
829        Iterator<HdmiCecFeatureAction> iter = mActions.iterator();
830        while (iter.hasNext()) {
831            HdmiCecFeatureAction action = iter.next();
832            action.finish(false);
833            iter.remove();
834        }
835    }
836
837    /**
838     * Send a key event to other device.
839     *
840     * @param keyCode key code defined in {@link android.view.KeyEvent}
841     * @param isPressed {@code true} for key down event
842     */
843    protected void sendKeyEvent(int keyCode, boolean isPressed) {
844        Slog.w(TAG, "sendKeyEvent not implemented");
845    }
846
847    void sendUserControlPressedAndReleased(int targetAddress, int cecKeycode) {
848        mService.sendCecCommand(HdmiCecMessageBuilder.buildUserControlPressed(
849                mAddress, targetAddress, cecKeycode));
850        mService.sendCecCommand(HdmiCecMessageBuilder.buildUserControlReleased(
851                mAddress, targetAddress));
852    }
853
854    /**
855     * Dump internal status of HdmiCecLocalDevice object.
856     */
857    protected void dump(final IndentingPrintWriter pw) {
858        pw.println("mDeviceType: " + mDeviceType);
859        pw.println("mAddress: " + mAddress);
860        pw.println("mPreferredAddress: " + mPreferredAddress);
861        pw.println("mDeviceInfo: " + mDeviceInfo);
862        pw.println("mActiveSource: " + mActiveSource);
863        pw.println(String.format("mActiveRoutingPath: 0x%04x", mActiveRoutingPath));
864    }
865}
866