Home | History | Annotate | Download | only in hdmi
      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 
     17 package com.android.server.hdmi;
     18 
     19 import android.hardware.hdmi.HdmiDeviceInfo;
     20 import android.hardware.input.InputManager;
     21 import android.os.Handler;
     22 import android.os.Looper;
     23 import android.os.Message;
     24 import android.os.SystemClock;
     25 import android.util.Slog;
     26 import android.view.InputDevice;
     27 import android.view.KeyCharacterMap;
     28 import android.view.KeyEvent;
     29 
     30 import com.android.internal.annotations.GuardedBy;
     31 import com.android.internal.util.IndentingPrintWriter;
     32 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
     33 
     34 import java.util.ArrayList;
     35 import java.util.Collections;
     36 import java.util.Iterator;
     37 import 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  */
     43 abstract 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         // Use copied action list in that start() may remove itself.
    664         for (HdmiCecFeatureAction action : new ArrayList<>(mActions)) {
    665             if (!action.started()) {
    666                 Slog.i(TAG, "Starting queued action:" + action);
    667                 action.start();
    668             }
    669         }
    670     }
    671 
    672     // See if we have an action of a given type in progress.
    673     @ServiceThreadOnly
    674     <T extends HdmiCecFeatureAction> boolean hasAction(final Class<T> clazz) {
    675         assertRunOnServiceThread();
    676         for (HdmiCecFeatureAction action : mActions) {
    677             if (action.getClass().equals(clazz)) {
    678                 return true;
    679             }
    680         }
    681         return false;
    682     }
    683 
    684     // Returns all actions matched with given class type.
    685     @ServiceThreadOnly
    686     <T extends HdmiCecFeatureAction> List<T> getActions(final Class<T> clazz) {
    687         assertRunOnServiceThread();
    688         List<T> actions = Collections.<T>emptyList();
    689         for (HdmiCecFeatureAction action : mActions) {
    690             if (action.getClass().equals(clazz)) {
    691                 if (actions.isEmpty()) {
    692                     actions = new ArrayList<T>();
    693                 }
    694                 actions.add((T) action);
    695             }
    696         }
    697         return actions;
    698     }
    699 
    700     /**
    701      * Remove the given {@link HdmiCecFeatureAction} object from the action queue.
    702      *
    703      * @param action {@link HdmiCecFeatureAction} to remove
    704      */
    705     @ServiceThreadOnly
    706     void removeAction(final HdmiCecFeatureAction action) {
    707         assertRunOnServiceThread();
    708         action.finish(false);
    709         mActions.remove(action);
    710         checkIfPendingActionsCleared();
    711     }
    712 
    713     // Remove all actions matched with the given Class type.
    714     @ServiceThreadOnly
    715     <T extends HdmiCecFeatureAction> void removeAction(final Class<T> clazz) {
    716         assertRunOnServiceThread();
    717         removeActionExcept(clazz, null);
    718     }
    719 
    720     // Remove all actions matched with the given Class type besides |exception|.
    721     @ServiceThreadOnly
    722     <T extends HdmiCecFeatureAction> void removeActionExcept(final Class<T> clazz,
    723             final HdmiCecFeatureAction exception) {
    724         assertRunOnServiceThread();
    725         Iterator<HdmiCecFeatureAction> iter = mActions.iterator();
    726         while (iter.hasNext()) {
    727             HdmiCecFeatureAction action = iter.next();
    728             if (action != exception && action.getClass().equals(clazz)) {
    729                 action.finish(false);
    730                 iter.remove();
    731             }
    732         }
    733         checkIfPendingActionsCleared();
    734     }
    735 
    736     protected void checkIfPendingActionsCleared() {
    737         if (mActions.isEmpty() && mPendingActionClearedCallback != null) {
    738             PendingActionClearedCallback callback = mPendingActionClearedCallback;
    739             // To prevent from calling the callback again during handling the callback itself.
    740             mPendingActionClearedCallback = null;
    741             callback.onCleared(this);
    742         }
    743     }
    744 
    745     protected void assertRunOnServiceThread() {
    746         if (Looper.myLooper() != mService.getServiceLooper()) {
    747             throw new IllegalStateException("Should run on service thread.");
    748         }
    749     }
    750 
    751     void setAutoDeviceOff(boolean enabled) {
    752     }
    753 
    754     /**
    755      * Called when a hot-plug event issued.
    756      *
    757      * @param portId id of port where a hot-plug event happened
    758      * @param connected whether to connected or not on the event
    759      */
    760     void onHotplug(int portId, boolean connected) {
    761     }
    762 
    763     final HdmiControlService getService() {
    764         return mService;
    765     }
    766 
    767     @ServiceThreadOnly
    768     final boolean isConnectedToArcPort(int path) {
    769         assertRunOnServiceThread();
    770         return mService.isConnectedToArcPort(path);
    771     }
    772 
    773     ActiveSource getActiveSource() {
    774         synchronized (mLock) {
    775             return mActiveSource;
    776         }
    777     }
    778 
    779     void setActiveSource(ActiveSource newActive) {
    780         setActiveSource(newActive.logicalAddress, newActive.physicalAddress);
    781     }
    782 
    783     void setActiveSource(HdmiDeviceInfo info) {
    784         setActiveSource(info.getLogicalAddress(), info.getPhysicalAddress());
    785     }
    786 
    787     void setActiveSource(int logicalAddress, int physicalAddress) {
    788         synchronized (mLock) {
    789             mActiveSource.logicalAddress = logicalAddress;
    790             mActiveSource.physicalAddress = physicalAddress;
    791         }
    792         mService.setLastInputForMhl(Constants.INVALID_PORT_ID);
    793     }
    794 
    795     int getActivePath() {
    796         synchronized (mLock) {
    797             return mActiveRoutingPath;
    798         }
    799     }
    800 
    801     void setActivePath(int path) {
    802         synchronized (mLock) {
    803             mActiveRoutingPath = path;
    804         }
    805         mService.setActivePortId(pathToPortId(path));
    806     }
    807 
    808     /**
    809      * Returns the ID of the active HDMI port. The active port is the one that has the active
    810      * routing path connected to it directly or indirectly under the device hierarchy.
    811      */
    812     int getActivePortId() {
    813         synchronized (mLock) {
    814             return mService.pathToPortId(mActiveRoutingPath);
    815         }
    816     }
    817 
    818     /**
    819      * Update the active port.
    820      *
    821      * @param portId the new active port id
    822      */
    823     void setActivePortId(int portId) {
    824         // We update active routing path instead, since we get the active port id from
    825         // the active routing path.
    826         setActivePath(mService.portIdToPath(portId));
    827     }
    828 
    829     @ServiceThreadOnly
    830     HdmiCecMessageCache getCecMessageCache() {
    831         assertRunOnServiceThread();
    832         return mCecMessageCache;
    833     }
    834 
    835     @ServiceThreadOnly
    836     int pathToPortId(int newPath) {
    837         assertRunOnServiceThread();
    838         return mService.pathToPortId(newPath);
    839     }
    840 
    841     /**
    842      * Called when the system goes to standby mode.
    843      *
    844      * @param initiatedByCec true if this power sequence is initiated
    845      *        by the reception the CEC messages like &lt;Standby&gt;
    846      * @param standbyAction Intent action that drives the standby process,
    847      *        either {@link HdmiControlService#STANDBY_SCREEN_OFF} or
    848      *        {@link HdmiControlService#STANDBY_SHUTDOWN}
    849      */
    850     protected void onStandby(boolean initiatedByCec, int standbyAction) {}
    851 
    852     /**
    853      * Disable device. {@code callback} is used to get notified when all pending
    854      * actions are completed or timeout is issued.
    855      *
    856      * @param initiatedByCec true if this sequence is initiated
    857      *        by the reception the CEC messages like &lt;Standby&gt;
    858      * @param originalCallback callback interface to get notified when all pending actions are
    859      *        cleared
    860      */
    861     protected void disableDevice(boolean initiatedByCec,
    862             final PendingActionClearedCallback originalCallback) {
    863         mPendingActionClearedCallback = new PendingActionClearedCallback() {
    864             @Override
    865             public void onCleared(HdmiCecLocalDevice device) {
    866                 mHandler.removeMessages(MSG_DISABLE_DEVICE_TIMEOUT);
    867                 originalCallback.onCleared(device);
    868             }
    869         };
    870         mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_DISABLE_DEVICE_TIMEOUT),
    871                 DEVICE_CLEANUP_TIMEOUT);
    872     }
    873 
    874     @ServiceThreadOnly
    875     private void handleDisableDeviceTimeout() {
    876         assertRunOnServiceThread();
    877 
    878         // If all actions are not cleared in DEVICE_CLEANUP_TIMEOUT, enforce to finish them.
    879         // onCleard will be called at the last action's finish method.
    880         Iterator<HdmiCecFeatureAction> iter = mActions.iterator();
    881         while (iter.hasNext()) {
    882             HdmiCecFeatureAction action = iter.next();
    883             action.finish(false);
    884             iter.remove();
    885         }
    886         if (mPendingActionClearedCallback != null) {
    887             mPendingActionClearedCallback.onCleared(this);
    888         }
    889     }
    890 
    891     /**
    892      * Send a key event to other CEC device. The logical address of target device will be given by
    893      * {@link #findKeyReceiverAddress}.
    894      *
    895      * @param keyCode key code defined in {@link android.view.KeyEvent}
    896      * @param isPressed {@code true} for key down event
    897      * @see #findKeyReceiverAddress()
    898      */
    899     @ServiceThreadOnly
    900     protected void sendKeyEvent(int keyCode, boolean isPressed) {
    901         assertRunOnServiceThread();
    902         if (!HdmiCecKeycode.isSupportedKeycode(keyCode)) {
    903             Slog.w(TAG, "Unsupported key: " + keyCode);
    904             return;
    905         }
    906         List<SendKeyAction> action = getActions(SendKeyAction.class);
    907         int logicalAddress = findKeyReceiverAddress();
    908         if (logicalAddress == Constants.ADDR_INVALID || logicalAddress == mAddress) {
    909             // Don't send key event to invalid device or itself.
    910             Slog.w(TAG, "Discard key event: " + keyCode + ", pressed:" + isPressed
    911                     + ", receiverAddr=" + logicalAddress);
    912         } else if (!action.isEmpty()) {
    913             action.get(0).processKeyEvent(keyCode, isPressed);
    914         } else if (isPressed) {
    915             addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode));
    916         }
    917     }
    918 
    919     /**
    920      * Returns the logical address of the device which will receive key events via
    921      * {@link #sendKeyEvent}.
    922      *
    923      * @see #sendKeyEvent(int, boolean)
    924      */
    925     protected int findKeyReceiverAddress() {
    926         Slog.w(TAG, "findKeyReceiverAddress is not implemented");
    927         return Constants.ADDR_INVALID;
    928     }
    929 
    930     void sendUserControlPressedAndReleased(int targetAddress, int cecKeycode) {
    931         mService.sendCecCommand(HdmiCecMessageBuilder.buildUserControlPressed(
    932                 mAddress, targetAddress, cecKeycode));
    933         mService.sendCecCommand(HdmiCecMessageBuilder.buildUserControlReleased(
    934                 mAddress, targetAddress));
    935     }
    936 
    937     /**
    938      * Dump internal status of HdmiCecLocalDevice object.
    939      */
    940     protected void dump(final IndentingPrintWriter pw) {
    941         pw.println("mDeviceType: " + mDeviceType);
    942         pw.println("mAddress: " + mAddress);
    943         pw.println("mPreferredAddress: " + mPreferredAddress);
    944         pw.println("mDeviceInfo: " + mDeviceInfo);
    945         pw.println("mActiveSource: " + mActiveSource);
    946         pw.println(String.format("mActiveRoutingPath: 0x%04x", mActiveRoutingPath));
    947     }
    948 }
    949