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