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