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(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