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