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