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 static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE;
     20 import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE;
     21 import static com.android.server.hdmi.Constants.DISABLED;
     22 import static com.android.server.hdmi.Constants.ENABLED;
     23 import static com.android.server.hdmi.Constants.OPTION_CEC_AUTO_WAKEUP;
     24 import static com.android.server.hdmi.Constants.OPTION_CEC_ENABLE;
     25 import static com.android.server.hdmi.Constants.OPTION_CEC_SERVICE_CONTROL;
     26 import static com.android.server.hdmi.Constants.OPTION_CEC_SET_LANGUAGE;
     27 import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE;
     28 import static com.android.server.hdmi.Constants.OPTION_MHL_INPUT_SWITCHING;
     29 import static com.android.server.hdmi.Constants.OPTION_MHL_POWER_CHARGE;
     30 import static com.android.server.hdmi.Constants.OPTION_MHL_SERVICE_CONTROL;
     31 
     32 import android.annotation.Nullable;
     33 import android.content.BroadcastReceiver;
     34 import android.content.ContentResolver;
     35 import android.content.Context;
     36 import android.content.Intent;
     37 import android.content.IntentFilter;
     38 import android.database.ContentObserver;
     39 import android.hardware.hdmi.HdmiControlManager;
     40 import android.hardware.hdmi.HdmiDeviceInfo;
     41 import android.hardware.hdmi.HdmiHotplugEvent;
     42 import android.hardware.hdmi.HdmiPortInfo;
     43 import android.hardware.hdmi.IHdmiControlCallback;
     44 import android.hardware.hdmi.IHdmiControlService;
     45 import android.hardware.hdmi.IHdmiDeviceEventListener;
     46 import android.hardware.hdmi.IHdmiHotplugEventListener;
     47 import android.hardware.hdmi.IHdmiInputChangeListener;
     48 import android.hardware.hdmi.IHdmiMhlVendorCommandListener;
     49 import android.hardware.hdmi.IHdmiRecordListener;
     50 import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
     51 import android.hardware.hdmi.IHdmiVendorCommandListener;
     52 import android.media.AudioManager;
     53 import android.media.tv.TvInputManager;
     54 import android.media.tv.TvInputManager.TvInputCallback;
     55 import android.net.Uri;
     56 import android.os.Build;
     57 import android.os.Handler;
     58 import android.os.HandlerThread;
     59 import android.os.IBinder;
     60 import android.os.Looper;
     61 import android.os.PowerManager;
     62 import android.os.RemoteException;
     63 import android.os.SystemClock;
     64 import android.os.SystemProperties;
     65 import android.os.UserHandle;
     66 import android.provider.Settings.Global;
     67 import android.text.TextUtils;
     68 import android.util.ArraySet;
     69 import android.util.Slog;
     70 import android.util.SparseArray;
     71 import android.util.SparseIntArray;
     72 
     73 import com.android.internal.annotations.GuardedBy;
     74 import com.android.internal.util.IndentingPrintWriter;
     75 import com.android.server.SystemService;
     76 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
     77 import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
     78 import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
     79 import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback;
     80 import com.android.server.hdmi.SelectRequestBuffer.DeviceSelectRequest;
     81 import com.android.server.hdmi.SelectRequestBuffer.PortSelectRequest;
     82 
     83 import libcore.util.EmptyArray;
     84 
     85 import java.io.FileDescriptor;
     86 import java.io.PrintWriter;
     87 import java.util.ArrayList;
     88 import java.util.Arrays;
     89 import java.util.Collections;
     90 import java.util.List;
     91 import java.util.Locale;
     92 
     93 /**
     94  * Provides a service for sending and processing HDMI control messages,
     95  * HDMI-CEC and MHL control command, and providing the information on both standard.
     96  */
     97 public final class HdmiControlService extends SystemService {
     98     private static final String TAG = "HdmiControlService";
     99     private final Locale HONG_KONG = new Locale("zh", "HK");
    100     private final Locale MACAU = new Locale("zh", "MO");
    101 
    102     static final String PERMISSION = "android.permission.HDMI_CEC";
    103 
    104     // The reason code to initiate intializeCec().
    105     static final int INITIATED_BY_ENABLE_CEC = 0;
    106     static final int INITIATED_BY_BOOT_UP = 1;
    107     static final int INITIATED_BY_SCREEN_ON = 2;
    108     static final int INITIATED_BY_WAKE_UP_MESSAGE = 3;
    109     static final int INITIATED_BY_HOTPLUG = 4;
    110 
    111     // The reason code representing the intent action that drives the standby
    112     // procedure. The procedure starts either by Intent.ACTION_SCREEN_OFF or
    113     // Intent.ACTION_SHUTDOWN.
    114     static final int STANDBY_SCREEN_OFF = 0;
    115     static final int STANDBY_SHUTDOWN = 1;
    116 
    117     /**
    118      * Interface to report send result.
    119      */
    120     interface SendMessageCallback {
    121         /**
    122          * Called when {@link HdmiControlService#sendCecCommand} is completed.
    123          *
    124          * @param error result of send request.
    125          * <ul>
    126          * <li>{@link Constants#SEND_RESULT_SUCCESS}
    127          * <li>{@link Constants#SEND_RESULT_NAK}
    128          * <li>{@link Constants#SEND_RESULT_FAILURE}
    129          * </ul>
    130          */
    131         void onSendCompleted(int error);
    132     }
    133 
    134     /**
    135      * Interface to get a list of available logical devices.
    136      */
    137     interface DevicePollingCallback {
    138         /**
    139          * Called when device polling is finished.
    140          *
    141          * @param ackedAddress a list of logical addresses of available devices
    142          */
    143         void onPollingFinished(List<Integer> ackedAddress);
    144     }
    145 
    146     private class HdmiControlBroadcastReceiver extends BroadcastReceiver {
    147         @ServiceThreadOnly
    148         @Override
    149         public void onReceive(Context context, Intent intent) {
    150             assertRunOnServiceThread();
    151             switch (intent.getAction()) {
    152                 case Intent.ACTION_SCREEN_OFF:
    153                     if (isPowerOnOrTransient()) {
    154                         onStandby(STANDBY_SCREEN_OFF);
    155                     }
    156                     break;
    157                 case Intent.ACTION_SCREEN_ON:
    158                     if (isPowerStandbyOrTransient()) {
    159                         onWakeUp();
    160                     }
    161                     break;
    162                 case Intent.ACTION_CONFIGURATION_CHANGED:
    163                     String language = getMenuLanguage();
    164                     if (!mLanguage.equals(language)) {
    165                         onLanguageChanged(language);
    166                     }
    167                     break;
    168                 case Intent.ACTION_SHUTDOWN:
    169                     if (isPowerOnOrTransient()) {
    170                         onStandby(STANDBY_SHUTDOWN);
    171                     }
    172                     break;
    173             }
    174         }
    175 
    176         private String getMenuLanguage() {
    177             Locale locale = Locale.getDefault();
    178             if (locale.equals(Locale.TAIWAN) || locale.equals(HONG_KONG) || locale.equals(MACAU)) {
    179                 // Android always returns "zho" for all Chinese variants.
    180                 // Use "bibliographic" code defined in CEC639-2 for traditional
    181                 // Chinese used in Taiwan/Hong Kong/Macau.
    182                 return "chi";
    183             } else {
    184                 return locale.getISO3Language();
    185             }
    186         }
    187     }
    188 
    189     // A thread to handle synchronous IO of CEC and MHL control service.
    190     // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
    191     // and sparse call it shares a thread to handle IO operations.
    192     private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
    193 
    194     // Used to synchronize the access to the service.
    195     private final Object mLock = new Object();
    196 
    197     // Type of logical devices hosted in the system. Stored in the unmodifiable list.
    198     private final List<Integer> mLocalDevices;
    199 
    200     // List of records for hotplug event listener to handle the the caller killed in action.
    201     @GuardedBy("mLock")
    202     private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
    203             new ArrayList<>();
    204 
    205     // List of records for device event listener to handle the caller killed in action.
    206     @GuardedBy("mLock")
    207     private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
    208             new ArrayList<>();
    209 
    210     // List of records for vendor command listener to handle the caller killed in action.
    211     @GuardedBy("mLock")
    212     private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords =
    213             new ArrayList<>();
    214 
    215     @GuardedBy("mLock")
    216     private InputChangeListenerRecord mInputChangeListenerRecord;
    217 
    218     @GuardedBy("mLock")
    219     private HdmiRecordListenerRecord mRecordListenerRecord;
    220 
    221     // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
    222     // handling will be disabled and no request will be handled.
    223     @GuardedBy("mLock")
    224     private boolean mHdmiControlEnabled;
    225 
    226     // Set to true while the service is in normal mode. While set to false, no input change is
    227     // allowed. Used for situations where input change can confuse users such as channel auto-scan,
    228     // system upgrade, etc., a.k.a. "prohibit mode".
    229     @GuardedBy("mLock")
    230     private boolean mProhibitMode;
    231 
    232     // List of records for system audio mode change to handle the the caller killed in action.
    233     private final ArrayList<SystemAudioModeChangeListenerRecord>
    234             mSystemAudioModeChangeListenerRecords = new ArrayList<>();
    235 
    236     // Handler used to run a task in service thread.
    237     private final Handler mHandler = new Handler();
    238 
    239     private final SettingsObserver mSettingsObserver;
    240 
    241     private final HdmiControlBroadcastReceiver
    242             mHdmiControlBroadcastReceiver = new HdmiControlBroadcastReceiver();
    243 
    244     @Nullable
    245     private HdmiCecController mCecController;
    246 
    247     // HDMI port information. Stored in the unmodifiable list to keep the static information
    248     // from being modified.
    249     private List<HdmiPortInfo> mPortInfo;
    250 
    251     // Map from path(physical address) to port ID.
    252     private UnmodifiableSparseIntArray mPortIdMap;
    253 
    254     // Map from port ID to HdmiPortInfo.
    255     private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap;
    256 
    257     // Map from port ID to HdmiDeviceInfo.
    258     private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap;
    259 
    260     private HdmiCecMessageValidator mMessageValidator;
    261 
    262     @ServiceThreadOnly
    263     private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
    264 
    265     @ServiceThreadOnly
    266     private String mLanguage = Locale.getDefault().getISO3Language();
    267 
    268     @ServiceThreadOnly
    269     private boolean mStandbyMessageReceived = false;
    270 
    271     @ServiceThreadOnly
    272     private boolean mWakeUpMessageReceived = false;
    273 
    274     @ServiceThreadOnly
    275     private int mActivePortId = Constants.INVALID_PORT_ID;
    276 
    277     // Set to true while the input change by MHL is allowed.
    278     @GuardedBy("mLock")
    279     private boolean mMhlInputChangeEnabled;
    280 
    281     // List of records for MHL Vendor command listener to handle the caller killed in action.
    282     @GuardedBy("mLock")
    283     private final ArrayList<HdmiMhlVendorCommandListenerRecord>
    284             mMhlVendorCommandListenerRecords = new ArrayList<>();
    285 
    286     @GuardedBy("mLock")
    287     private List<HdmiDeviceInfo> mMhlDevices;
    288 
    289     @Nullable
    290     private HdmiMhlControllerStub mMhlController;
    291 
    292     @Nullable
    293     private TvInputManager mTvInputManager;
    294 
    295     @Nullable
    296     private PowerManager mPowerManager;
    297 
    298     // Last input port before switching to the MHL port. Should switch back to this port
    299     // when the mobile device sends the request one touch play with off.
    300     // Gets invalidated if we go to other port/input.
    301     @ServiceThreadOnly
    302     private int mLastInputMhl = Constants.INVALID_PORT_ID;
    303 
    304     // Set to true if the logical address allocation is completed.
    305     private boolean mAddressAllocated = false;
    306 
    307     // Buffer for processing the incoming cec messages while allocating logical addresses.
    308     private final class CecMessageBuffer {
    309         private List<HdmiCecMessage> mBuffer = new ArrayList<>();
    310 
    311         public void bufferMessage(HdmiCecMessage message) {
    312             switch (message.getOpcode()) {
    313                 case Constants.MESSAGE_ACTIVE_SOURCE:
    314                     bufferActiveSource(message);
    315                     break;
    316                 case Constants.MESSAGE_IMAGE_VIEW_ON:
    317                 case Constants.MESSAGE_TEXT_VIEW_ON:
    318                     bufferImageOrTextViewOn(message);
    319                     break;
    320                     // Add here if new message that needs to buffer
    321                 default:
    322                     // Do not need to buffer messages other than above
    323                     break;
    324             }
    325         }
    326 
    327         public void processMessages() {
    328             for (final HdmiCecMessage message : mBuffer) {
    329                 runOnServiceThread(new Runnable() {
    330                     @Override
    331                     public void run() {
    332                         handleCecCommand(message);
    333                     }
    334                 });
    335             }
    336             mBuffer.clear();
    337         }
    338 
    339         private void bufferActiveSource(HdmiCecMessage message) {
    340             if (!replaceMessageIfBuffered(message, Constants.MESSAGE_ACTIVE_SOURCE)) {
    341                 mBuffer.add(message);
    342             }
    343         }
    344 
    345         private void bufferImageOrTextViewOn(HdmiCecMessage message) {
    346             if (!replaceMessageIfBuffered(message, Constants.MESSAGE_IMAGE_VIEW_ON) &&
    347                 !replaceMessageIfBuffered(message, Constants.MESSAGE_TEXT_VIEW_ON)) {
    348                 mBuffer.add(message);
    349             }
    350         }
    351 
    352         // Returns true if the message is replaced
    353         private boolean replaceMessageIfBuffered(HdmiCecMessage message, int opcode) {
    354             for (int i = 0; i < mBuffer.size(); i++) {
    355                 HdmiCecMessage bufferedMessage = mBuffer.get(i);
    356                 if (bufferedMessage.getOpcode() == opcode) {
    357                     mBuffer.set(i, message);
    358                     return true;
    359                 }
    360             }
    361             return false;
    362         }
    363     }
    364 
    365     private final CecMessageBuffer mCecMessageBuffer = new CecMessageBuffer();
    366 
    367     private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer();
    368 
    369     public HdmiControlService(Context context) {
    370         super(context);
    371         mLocalDevices = getIntList(SystemProperties.get(Constants.PROPERTY_DEVICE_TYPE));
    372         mSettingsObserver = new SettingsObserver(mHandler);
    373     }
    374 
    375     private static List<Integer> getIntList(String string) {
    376         ArrayList<Integer> list = new ArrayList<>();
    377         TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
    378         splitter.setString(string);
    379         for (String item : splitter) {
    380             try {
    381                 list.add(Integer.parseInt(item));
    382             } catch (NumberFormatException e) {
    383                 Slog.w(TAG, "Can't parseInt: " + item);
    384             }
    385         }
    386         return Collections.unmodifiableList(list);
    387     }
    388 
    389     @Override
    390     public void onStart() {
    391         mIoThread.start();
    392         mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
    393         mProhibitMode = false;
    394         mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
    395         mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
    396 
    397         mCecController = HdmiCecController.create(this);
    398         if (mCecController != null) {
    399             if (mHdmiControlEnabled) {
    400                 initializeCec(INITIATED_BY_BOOT_UP);
    401             }
    402         } else {
    403             Slog.i(TAG, "Device does not support HDMI-CEC.");
    404             return;
    405         }
    406 
    407         mMhlController = HdmiMhlControllerStub.create(this);
    408         if (!mMhlController.isReady()) {
    409             Slog.i(TAG, "Device does not support MHL-control.");
    410         }
    411         mMhlDevices = Collections.emptyList();
    412 
    413         initPortInfo();
    414         mMessageValidator = new HdmiCecMessageValidator(this);
    415         publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
    416 
    417         if (mCecController != null) {
    418             // Register broadcast receiver for power state change.
    419             IntentFilter filter = new IntentFilter();
    420             filter.addAction(Intent.ACTION_SCREEN_OFF);
    421             filter.addAction(Intent.ACTION_SCREEN_ON);
    422             filter.addAction(Intent.ACTION_SHUTDOWN);
    423             filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
    424             getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter);
    425 
    426             // Register ContentObserver to monitor the settings change.
    427             registerContentObserver();
    428         }
    429         mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED);
    430     }
    431 
    432     @Override
    433     public void onBootPhase(int phase) {
    434         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
    435             mTvInputManager = (TvInputManager) getContext().getSystemService(
    436                     Context.TV_INPUT_SERVICE);
    437             mPowerManager = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
    438         }
    439     }
    440 
    441     TvInputManager getTvInputManager() {
    442         return mTvInputManager;
    443     }
    444 
    445     void registerTvInputCallback(TvInputCallback callback) {
    446         if (mTvInputManager == null) return;
    447         mTvInputManager.registerCallback(callback, mHandler);
    448     }
    449 
    450     void unregisterTvInputCallback(TvInputCallback callback) {
    451         if (mTvInputManager == null) return;
    452         mTvInputManager.unregisterCallback(callback);
    453     }
    454 
    455     PowerManager getPowerManager() {
    456         return mPowerManager;
    457     }
    458 
    459     /**
    460      * Called when the initialization of local devices is complete.
    461      */
    462     private void onInitializeCecComplete(int initiatedBy) {
    463         if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
    464             mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
    465         }
    466         mWakeUpMessageReceived = false;
    467 
    468         if (isTvDeviceEnabled()) {
    469             mCecController.setOption(OPTION_CEC_AUTO_WAKEUP, toInt(tv().getAutoWakeup()));
    470         }
    471         int reason = -1;
    472         switch (initiatedBy) {
    473             case INITIATED_BY_BOOT_UP:
    474                 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_START;
    475                 break;
    476             case INITIATED_BY_ENABLE_CEC:
    477                 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING;
    478                 break;
    479             case INITIATED_BY_SCREEN_ON:
    480             case INITIATED_BY_WAKE_UP_MESSAGE:
    481                 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_WAKEUP;
    482                 break;
    483         }
    484         if (reason != -1) {
    485             invokeVendorCommandListenersOnControlStateChanged(true, reason);
    486         }
    487     }
    488 
    489     private void registerContentObserver() {
    490         ContentResolver resolver = getContext().getContentResolver();
    491         String[] settings = new String[] {
    492                 Global.HDMI_CONTROL_ENABLED,
    493                 Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
    494                 Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
    495                 Global.MHL_INPUT_SWITCHING_ENABLED,
    496                 Global.MHL_POWER_CHARGE_ENABLED
    497         };
    498         for (String s : settings) {
    499             resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
    500                     UserHandle.USER_ALL);
    501         }
    502     }
    503 
    504     private class SettingsObserver extends ContentObserver {
    505         public SettingsObserver(Handler handler) {
    506             super(handler);
    507         }
    508 
    509         // onChange is set up to run in service thread.
    510         @Override
    511         public void onChange(boolean selfChange, Uri uri) {
    512             String option = uri.getLastPathSegment();
    513             boolean enabled = readBooleanSetting(option, true);
    514             switch (option) {
    515                 case Global.HDMI_CONTROL_ENABLED:
    516                     setControlEnabled(enabled);
    517                     break;
    518                 case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
    519                     if (isTvDeviceEnabled()) {
    520                         tv().setAutoWakeup(enabled);
    521                     }
    522                     setCecOption(OPTION_CEC_AUTO_WAKEUP, toInt(enabled));
    523                     break;
    524                 case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
    525                     for (int type : mLocalDevices) {
    526                         HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
    527                         if (localDevice != null) {
    528                             localDevice.setAutoDeviceOff(enabled);
    529                         }
    530                     }
    531                     // No need to propagate to HAL.
    532                     break;
    533                 case Global.MHL_INPUT_SWITCHING_ENABLED:
    534                     setMhlInputChangeEnabled(enabled);
    535                     break;
    536                 case Global.MHL_POWER_CHARGE_ENABLED:
    537                     mMhlController.setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled));
    538                     break;
    539             }
    540         }
    541     }
    542 
    543     private static int toInt(boolean enabled) {
    544         return enabled ? ENABLED : DISABLED;
    545     }
    546 
    547     boolean readBooleanSetting(String key, boolean defVal) {
    548         ContentResolver cr = getContext().getContentResolver();
    549         return Global.getInt(cr, key, toInt(defVal)) == ENABLED;
    550     }
    551 
    552     void writeBooleanSetting(String key, boolean value) {
    553         ContentResolver cr = getContext().getContentResolver();
    554         Global.putInt(cr, key, toInt(value));
    555     }
    556 
    557     private void initializeCec(int initiatedBy) {
    558         mAddressAllocated = false;
    559         mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, ENABLED);
    560         mCecController.setOption(OPTION_CEC_SET_LANGUAGE, HdmiUtils.languageToInt(mLanguage));
    561         initializeLocalDevices(initiatedBy);
    562     }
    563 
    564     @ServiceThreadOnly
    565     private void initializeLocalDevices(final int initiatedBy) {
    566         assertRunOnServiceThread();
    567         // A container for [Device type, Local device info].
    568         ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
    569         for (int type : mLocalDevices) {
    570             HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
    571             if (localDevice == null) {
    572                 localDevice = HdmiCecLocalDevice.create(this, type);
    573             }
    574             localDevice.init();
    575             localDevices.add(localDevice);
    576         }
    577         // It's now safe to flush existing local devices from mCecController since they were
    578         // already moved to 'localDevices'.
    579         clearLocalDevices();
    580         allocateLogicalAddress(localDevices, initiatedBy);
    581     }
    582 
    583     @ServiceThreadOnly
    584     private void allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices,
    585             final int initiatedBy) {
    586         assertRunOnServiceThread();
    587         mCecController.clearLogicalAddress();
    588         final ArrayList<HdmiCecLocalDevice> allocatedDevices = new ArrayList<>();
    589         final int[] finished = new int[1];
    590         mAddressAllocated = allocatingDevices.isEmpty();
    591 
    592         // For TV device, select request can be invoked while address allocation or device
    593         // discovery is in progress. Initialize the request here at the start of allocation,
    594         // and process the collected requests later when the allocation and device discovery
    595         // is all completed.
    596         mSelectRequestBuffer.clear();
    597 
    598         for (final HdmiCecLocalDevice localDevice : allocatingDevices) {
    599             mCecController.allocateLogicalAddress(localDevice.getType(),
    600                     localDevice.getPreferredAddress(), new AllocateAddressCallback() {
    601                 @Override
    602                 public void onAllocated(int deviceType, int logicalAddress) {
    603                     if (logicalAddress == Constants.ADDR_UNREGISTERED) {
    604                         Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
    605                     } else {
    606                         // Set POWER_STATUS_ON to all local devices because they share lifetime
    607                         // with system.
    608                         HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
    609                                 HdmiControlManager.POWER_STATUS_ON);
    610                         localDevice.setDeviceInfo(deviceInfo);
    611                         mCecController.addLocalDevice(deviceType, localDevice);
    612                         mCecController.addLogicalAddress(logicalAddress);
    613                         allocatedDevices.add(localDevice);
    614                     }
    615 
    616                     // Address allocation completed for all devices. Notify each device.
    617                     if (allocatingDevices.size() == ++finished[0]) {
    618                         mAddressAllocated = true;
    619                         if (initiatedBy != INITIATED_BY_HOTPLUG) {
    620                             // In case of the hotplug we don't call onInitializeCecComplete()
    621                             // since we reallocate the logical address only.
    622                             onInitializeCecComplete(initiatedBy);
    623                         }
    624                         notifyAddressAllocated(allocatedDevices, initiatedBy);
    625                         mCecMessageBuffer.processMessages();
    626                     }
    627                 }
    628             });
    629         }
    630     }
    631 
    632     @ServiceThreadOnly
    633     private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
    634         assertRunOnServiceThread();
    635         for (HdmiCecLocalDevice device : devices) {
    636             int address = device.getDeviceInfo().getLogicalAddress();
    637             device.handleAddressAllocated(address, initiatedBy);
    638         }
    639         if (isTvDeviceEnabled()) {
    640             tv().setSelectRequestBuffer(mSelectRequestBuffer);
    641         }
    642     }
    643 
    644     boolean isAddressAllocated() {
    645         return mAddressAllocated;
    646     }
    647 
    648     // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
    649     // keep them in one place.
    650     @ServiceThreadOnly
    651     private void initPortInfo() {
    652         assertRunOnServiceThread();
    653         HdmiPortInfo[] cecPortInfo = null;
    654 
    655         // CEC HAL provides majority of the info while MHL does only MHL support flag for
    656         // each port. Return empty array if CEC HAL didn't provide the info.
    657         if (mCecController != null) {
    658             cecPortInfo = mCecController.getPortInfos();
    659         }
    660         if (cecPortInfo == null) {
    661             return;
    662         }
    663 
    664         SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
    665         SparseIntArray portIdMap = new SparseIntArray();
    666         SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>();
    667         for (HdmiPortInfo info : cecPortInfo) {
    668             portIdMap.put(info.getAddress(), info.getId());
    669             portInfoMap.put(info.getId(), info);
    670             portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
    671         }
    672         mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
    673         mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
    674         mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
    675 
    676         HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
    677         ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
    678         for (HdmiPortInfo info : mhlPortInfo) {
    679             if (info.isMhlSupported()) {
    680                 mhlSupportedPorts.add(info.getId());
    681             }
    682         }
    683 
    684         // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use
    685         // cec port info if we do not have have port that supports MHL.
    686         if (mhlSupportedPorts.isEmpty()) {
    687             mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo));
    688             return;
    689         }
    690         ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
    691         for (HdmiPortInfo info : cecPortInfo) {
    692             if (mhlSupportedPorts.contains(info.getId())) {
    693                 result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
    694                         info.isCecSupported(), true, info.isArcSupported()));
    695             } else {
    696                 result.add(info);
    697             }
    698         }
    699         mPortInfo = Collections.unmodifiableList(result);
    700     }
    701 
    702     List<HdmiPortInfo> getPortInfo() {
    703         return mPortInfo;
    704     }
    705 
    706     /**
    707      * Returns HDMI port information for the given port id.
    708      *
    709      * @param portId HDMI port id
    710      * @return {@link HdmiPortInfo} for the given port
    711      */
    712     HdmiPortInfo getPortInfo(int portId) {
    713         return mPortInfoMap.get(portId, null);
    714     }
    715 
    716     /**
    717      * Returns the routing path (physical address) of the HDMI port for the given
    718      * port id.
    719      */
    720     int portIdToPath(int portId) {
    721         HdmiPortInfo portInfo = getPortInfo(portId);
    722         if (portInfo == null) {
    723             Slog.e(TAG, "Cannot find the port info: " + portId);
    724             return Constants.INVALID_PHYSICAL_ADDRESS;
    725         }
    726         return portInfo.getAddress();
    727     }
    728 
    729     /**
    730      * Returns the id of HDMI port located at the top of the hierarchy of
    731      * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
    732      * the port id to be returned is the ID associated with the port address
    733      * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
    734      */
    735     int pathToPortId(int path) {
    736         int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
    737         return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
    738     }
    739 
    740     boolean isValidPortId(int portId) {
    741         return getPortInfo(portId) != null;
    742     }
    743 
    744     /**
    745      * Returns {@link Looper} for IO operation.
    746      *
    747      * <p>Declared as package-private.
    748      */
    749     Looper getIoLooper() {
    750         return mIoThread.getLooper();
    751     }
    752 
    753     /**
    754      * Returns {@link Looper} of main thread. Use this {@link Looper} instance
    755      * for tasks that are running on main service thread.
    756      *
    757      * <p>Declared as package-private.
    758      */
    759     Looper getServiceLooper() {
    760         return mHandler.getLooper();
    761     }
    762 
    763     /**
    764      * Returns physical address of the device.
    765      */
    766     int getPhysicalAddress() {
    767         return mCecController.getPhysicalAddress();
    768     }
    769 
    770     /**
    771      * Returns vendor id of CEC service.
    772      */
    773     int getVendorId() {
    774         return mCecController.getVendorId();
    775     }
    776 
    777     @ServiceThreadOnly
    778     HdmiDeviceInfo getDeviceInfo(int logicalAddress) {
    779         assertRunOnServiceThread();
    780         return tv() == null ? null : tv().getCecDeviceInfo(logicalAddress);
    781     }
    782 
    783     @ServiceThreadOnly
    784     HdmiDeviceInfo getDeviceInfoByPort(int port) {
    785         assertRunOnServiceThread();
    786         HdmiMhlLocalDeviceStub info = mMhlController.getLocalDevice(port);
    787         if (info != null) {
    788             return info.getInfo();
    789         }
    790         return null;
    791     }
    792 
    793     /**
    794      * Returns version of CEC.
    795      */
    796     int getCecVersion() {
    797         return mCecController.getVersion();
    798     }
    799 
    800     /**
    801      * Whether a device of the specified physical address is connected to ARC enabled port.
    802      */
    803     boolean isConnectedToArcPort(int physicalAddress) {
    804         int portId = pathToPortId(physicalAddress);
    805         if (portId != Constants.INVALID_PORT_ID) {
    806             return mPortInfoMap.get(portId).isArcSupported();
    807         }
    808         return false;
    809     }
    810 
    811     @ServiceThreadOnly
    812     boolean isConnected(int portId) {
    813         assertRunOnServiceThread();
    814         return mCecController.isConnected(portId);
    815     }
    816 
    817     void runOnServiceThread(Runnable runnable) {
    818         mHandler.post(runnable);
    819     }
    820 
    821     void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
    822         mHandler.postAtFrontOfQueue(runnable);
    823     }
    824 
    825     private void assertRunOnServiceThread() {
    826         if (Looper.myLooper() != mHandler.getLooper()) {
    827             throw new IllegalStateException("Should run on service thread.");
    828         }
    829     }
    830 
    831     /**
    832      * Transmit a CEC command to CEC bus.
    833      *
    834      * @param command CEC command to send out
    835      * @param callback interface used to the result of send command
    836      */
    837     @ServiceThreadOnly
    838     void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
    839         assertRunOnServiceThread();
    840         if (mMessageValidator.isValid(command) == HdmiCecMessageValidator.OK) {
    841             mCecController.sendCommand(command, callback);
    842         } else {
    843             HdmiLogger.error("Invalid message type:" + command);
    844             if (callback != null) {
    845                 callback.onSendCompleted(Constants.SEND_RESULT_FAILURE);
    846             }
    847         }
    848     }
    849 
    850     @ServiceThreadOnly
    851     void sendCecCommand(HdmiCecMessage command) {
    852         assertRunOnServiceThread();
    853         sendCecCommand(command, null);
    854     }
    855 
    856     /**
    857      * Send <Feature Abort> command on the given CEC message if possible.
    858      * If the aborted message is invalid, then it wont send the message.
    859      * @param command original command to be aborted
    860      * @param reason reason of feature abort
    861      */
    862     @ServiceThreadOnly
    863     void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) {
    864         assertRunOnServiceThread();
    865         mCecController.maySendFeatureAbortCommand(command, reason);
    866     }
    867 
    868     @ServiceThreadOnly
    869     boolean handleCecCommand(HdmiCecMessage message) {
    870         assertRunOnServiceThread();
    871         if (!mAddressAllocated) {
    872             mCecMessageBuffer.bufferMessage(message);
    873             return true;
    874         }
    875         int errorCode = mMessageValidator.isValid(message);
    876         if (errorCode != HdmiCecMessageValidator.OK) {
    877             // We'll not response on the messages with the invalid source or destination
    878             // or with parameter length shorter than specified in the standard.
    879             if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) {
    880                 maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
    881             }
    882             return true;
    883         }
    884         return dispatchMessageToLocalDevice(message);
    885     }
    886 
    887     void setAudioReturnChannel(int portId, boolean enabled) {
    888         mCecController.setAudioReturnChannel(portId, enabled);
    889     }
    890 
    891     @ServiceThreadOnly
    892     private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
    893         assertRunOnServiceThread();
    894         for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
    895             if (device.dispatchMessage(message)
    896                     && message.getDestination() != Constants.ADDR_BROADCAST) {
    897                 return true;
    898             }
    899         }
    900 
    901         if (message.getDestination() != Constants.ADDR_BROADCAST) {
    902             HdmiLogger.warning("Unhandled cec command:" + message);
    903         }
    904         return false;
    905     }
    906 
    907     /**
    908      * Called when a new hotplug event is issued.
    909      *
    910      * @param portId hdmi port number where hot plug event issued.
    911      * @param connected whether to be plugged in or not
    912      */
    913     @ServiceThreadOnly
    914     void onHotplug(int portId, boolean connected) {
    915         assertRunOnServiceThread();
    916 
    917         if (connected && !isTvDevice()) {
    918             ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
    919             for (int type : mLocalDevices) {
    920                 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
    921                 if (localDevice == null) {
    922                     localDevice = HdmiCecLocalDevice.create(this, type);
    923                     localDevice.init();
    924                 }
    925                 localDevices.add(localDevice);
    926             }
    927             allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
    928         }
    929 
    930         for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
    931             device.onHotplug(portId, connected);
    932         }
    933         announceHotplugEvent(portId, connected);
    934     }
    935 
    936     /**
    937      * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
    938      * devices.
    939      *
    940      * @param callback an interface used to get a list of all remote devices' address
    941      * @param sourceAddress a logical address of source device where sends polling message
    942      * @param pickStrategy strategy how to pick polling candidates
    943      * @param retryCount the number of retry used to send polling message to remote devices
    944      * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
    945      */
    946     @ServiceThreadOnly
    947     void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
    948             int retryCount) {
    949         assertRunOnServiceThread();
    950         mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
    951                 retryCount);
    952     }
    953 
    954     private int checkPollStrategy(int pickStrategy) {
    955         int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
    956         if (strategy == 0) {
    957             throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
    958         }
    959         int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
    960         if (iterationStrategy == 0) {
    961             throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
    962         }
    963         return strategy | iterationStrategy;
    964     }
    965 
    966     List<HdmiCecLocalDevice> getAllLocalDevices() {
    967         assertRunOnServiceThread();
    968         return mCecController.getLocalDeviceList();
    969     }
    970 
    971     Object getServiceLock() {
    972         return mLock;
    973     }
    974 
    975     void setAudioStatus(boolean mute, int volume) {
    976         AudioManager audioManager = getAudioManager();
    977         boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
    978         if (mute) {
    979             if (!muted) {
    980                 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
    981             }
    982         } else {
    983             if (muted) {
    984                 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
    985             }
    986             // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
    987             // volume change notification back to hdmi control service.
    988             audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
    989                     AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
    990         }
    991     }
    992 
    993     void announceSystemAudioModeChange(boolean enabled) {
    994         synchronized (mLock) {
    995             for (SystemAudioModeChangeListenerRecord record :
    996                     mSystemAudioModeChangeListenerRecords) {
    997                 invokeSystemAudioModeChangeLocked(record.mListener, enabled);
    998             }
    999         }
   1000     }
   1001 
   1002     private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
   1003         // TODO: find better name instead of model name.
   1004         String displayName = Build.MODEL;
   1005         return new HdmiDeviceInfo(logicalAddress,
   1006                 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
   1007                 getVendorId(), displayName);
   1008     }
   1009 
   1010     @ServiceThreadOnly
   1011     void handleMhlHotplugEvent(int portId, boolean connected) {
   1012         assertRunOnServiceThread();
   1013         // Hotplug event is used to add/remove MHL devices as TV input.
   1014         if (connected) {
   1015             HdmiMhlLocalDeviceStub newDevice = new HdmiMhlLocalDeviceStub(this, portId);
   1016             HdmiMhlLocalDeviceStub oldDevice = mMhlController.addLocalDevice(newDevice);
   1017             if (oldDevice != null) {
   1018                 oldDevice.onDeviceRemoved();
   1019                 Slog.i(TAG, "Old device of port " + portId + " is removed");
   1020             }
   1021             invokeDeviceEventListeners(newDevice.getInfo(), DEVICE_EVENT_ADD_DEVICE);
   1022             updateSafeMhlInput();
   1023         } else {
   1024             HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId);
   1025             if (device != null) {
   1026                 device.onDeviceRemoved();
   1027                 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE);
   1028                 updateSafeMhlInput();
   1029             } else {
   1030                 Slog.w(TAG, "No device to remove:[portId=" + portId);
   1031             }
   1032         }
   1033         announceHotplugEvent(portId, connected);
   1034     }
   1035 
   1036     @ServiceThreadOnly
   1037     void handleMhlBusModeChanged(int portId, int busmode) {
   1038         assertRunOnServiceThread();
   1039         HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
   1040         if (device != null) {
   1041             device.setBusMode(busmode);
   1042         } else {
   1043             Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId +
   1044                     ", busmode:" + busmode + "]");
   1045         }
   1046     }
   1047 
   1048     @ServiceThreadOnly
   1049     void handleMhlBusOvercurrent(int portId, boolean on) {
   1050         assertRunOnServiceThread();
   1051         HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
   1052         if (device != null) {
   1053             device.onBusOvercurrentDetected(on);
   1054         } else {
   1055             Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]");
   1056         }
   1057     }
   1058 
   1059     @ServiceThreadOnly
   1060     void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) {
   1061         assertRunOnServiceThread();
   1062         HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
   1063 
   1064         if (device != null) {
   1065             device.setDeviceStatusChange(adopterId, deviceId);
   1066         } else {
   1067             Slog.w(TAG, "No mhl device exists for device status event[portId:"
   1068                     + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]");
   1069         }
   1070     }
   1071 
   1072     @ServiceThreadOnly
   1073     private void updateSafeMhlInput() {
   1074         assertRunOnServiceThread();
   1075         List<HdmiDeviceInfo> inputs = Collections.emptyList();
   1076         SparseArray<HdmiMhlLocalDeviceStub> devices = mMhlController.getAllLocalDevices();
   1077         for (int i = 0; i < devices.size(); ++i) {
   1078             HdmiMhlLocalDeviceStub device = devices.valueAt(i);
   1079             HdmiDeviceInfo info = device.getInfo();
   1080             if (info != null) {
   1081                 if (inputs.isEmpty()) {
   1082                     inputs = new ArrayList<>();
   1083                 }
   1084                 inputs.add(device.getInfo());
   1085             }
   1086         }
   1087         synchronized (mLock) {
   1088             mMhlDevices = inputs;
   1089         }
   1090     }
   1091 
   1092     private List<HdmiDeviceInfo> getMhlDevicesLocked() {
   1093         return mMhlDevices;
   1094     }
   1095 
   1096     private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient {
   1097         private final IHdmiMhlVendorCommandListener mListener;
   1098 
   1099         public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) {
   1100             mListener = listener;
   1101         }
   1102 
   1103         @Override
   1104         public void binderDied() {
   1105             mMhlVendorCommandListenerRecords.remove(this);
   1106         }
   1107     }
   1108 
   1109     // Record class that monitors the event of the caller of being killed. Used to clean up
   1110     // the listener list and record list accordingly.
   1111     private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
   1112         private final IHdmiHotplugEventListener mListener;
   1113 
   1114         public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
   1115             mListener = listener;
   1116         }
   1117 
   1118         @Override
   1119         public void binderDied() {
   1120             synchronized (mLock) {
   1121                 mHotplugEventListenerRecords.remove(this);
   1122             }
   1123         }
   1124 
   1125         @Override
   1126         public boolean equals(Object obj) {
   1127             if (!(obj instanceof HotplugEventListenerRecord)) return false;
   1128             if (obj == this) return true;
   1129             HotplugEventListenerRecord other = (HotplugEventListenerRecord) obj;
   1130             return other.mListener == this.mListener;
   1131         }
   1132 
   1133         @Override
   1134         public int hashCode() {
   1135             return mListener.hashCode();
   1136         }
   1137     }
   1138 
   1139     private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
   1140         private final IHdmiDeviceEventListener mListener;
   1141 
   1142         public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
   1143             mListener = listener;
   1144         }
   1145 
   1146         @Override
   1147         public void binderDied() {
   1148             synchronized (mLock) {
   1149                 mDeviceEventListenerRecords.remove(this);
   1150             }
   1151         }
   1152     }
   1153 
   1154     private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
   1155         private final IHdmiSystemAudioModeChangeListener mListener;
   1156 
   1157         public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
   1158             mListener = listener;
   1159         }
   1160 
   1161         @Override
   1162         public void binderDied() {
   1163             synchronized (mLock) {
   1164                 mSystemAudioModeChangeListenerRecords.remove(this);
   1165             }
   1166         }
   1167     }
   1168 
   1169     class VendorCommandListenerRecord implements IBinder.DeathRecipient {
   1170         private final IHdmiVendorCommandListener mListener;
   1171         private final int mDeviceType;
   1172 
   1173         public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
   1174             mListener = listener;
   1175             mDeviceType = deviceType;
   1176         }
   1177 
   1178         @Override
   1179         public void binderDied() {
   1180             synchronized (mLock) {
   1181                 mVendorCommandListenerRecords.remove(this);
   1182             }
   1183         }
   1184     }
   1185 
   1186     private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
   1187         private final IHdmiRecordListener mListener;
   1188 
   1189         public HdmiRecordListenerRecord(IHdmiRecordListener listener) {
   1190             mListener = listener;
   1191         }
   1192 
   1193         @Override
   1194         public void binderDied() {
   1195             synchronized (mLock) {
   1196                 if (mRecordListenerRecord == this) {
   1197                     mRecordListenerRecord = null;
   1198                 }
   1199             }
   1200         }
   1201     }
   1202 
   1203     private void enforceAccessPermission() {
   1204         getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
   1205     }
   1206 
   1207     private final class BinderService extends IHdmiControlService.Stub {
   1208         @Override
   1209         public int[] getSupportedTypes() {
   1210             enforceAccessPermission();
   1211             // mLocalDevices is an unmodifiable list - no lock necesary.
   1212             int[] localDevices = new int[mLocalDevices.size()];
   1213             for (int i = 0; i < localDevices.length; ++i) {
   1214                 localDevices[i] = mLocalDevices.get(i);
   1215             }
   1216             return localDevices;
   1217         }
   1218 
   1219         @Override
   1220         public HdmiDeviceInfo getActiveSource() {
   1221             enforceAccessPermission();
   1222             HdmiCecLocalDeviceTv tv = tv();
   1223             if (tv == null) {
   1224                 Slog.w(TAG, "Local tv device not available");
   1225                 return null;
   1226             }
   1227             ActiveSource activeSource = tv.getActiveSource();
   1228             if (activeSource.isValid()) {
   1229                 return new HdmiDeviceInfo(activeSource.logicalAddress,
   1230                         activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID,
   1231                         HdmiDeviceInfo.DEVICE_INACTIVE, 0, "");
   1232             }
   1233             int activePath = tv.getActivePath();
   1234             if (activePath != HdmiDeviceInfo.PATH_INVALID) {
   1235                 HdmiDeviceInfo info = tv.getSafeDeviceInfoByPath(activePath);
   1236                 return (info != null) ? info : new HdmiDeviceInfo(activePath, tv.getActivePortId());
   1237             }
   1238             return null;
   1239         }
   1240 
   1241         @Override
   1242         public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) {
   1243             enforceAccessPermission();
   1244             runOnServiceThread(new Runnable() {
   1245                 @Override
   1246                 public void run() {
   1247                     if (callback == null) {
   1248                         Slog.e(TAG, "Callback cannot be null");
   1249                         return;
   1250                     }
   1251                     HdmiCecLocalDeviceTv tv = tv();
   1252                     if (tv == null) {
   1253                         if (!mAddressAllocated) {
   1254                             mSelectRequestBuffer.set(SelectRequestBuffer.newDeviceSelect(
   1255                                     HdmiControlService.this, deviceId, callback));
   1256                             return;
   1257                         }
   1258                         Slog.w(TAG, "Local tv device not available");
   1259                         invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
   1260                         return;
   1261                     }
   1262                     HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId);
   1263                     if (device != null) {
   1264                         if (device.getPortId() == tv.getActivePortId()) {
   1265                             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
   1266                             return;
   1267                         }
   1268                         // Upon selecting MHL device, we send RAP[Content On] to wake up
   1269                         // the connected mobile device, start routing control to switch ports.
   1270                         // callback is handled by MHL action.
   1271                         device.turnOn(callback);
   1272                         tv.doManualPortSwitching(device.getPortId(), null);
   1273                         return;
   1274                     }
   1275                     tv.deviceSelect(deviceId, callback);
   1276                 }
   1277             });
   1278         }
   1279 
   1280         @Override
   1281         public void portSelect(final int portId, final IHdmiControlCallback callback) {
   1282             enforceAccessPermission();
   1283             runOnServiceThread(new Runnable() {
   1284                 @Override
   1285                 public void run() {
   1286                     if (callback == null) {
   1287                         Slog.e(TAG, "Callback cannot be null");
   1288                         return;
   1289                     }
   1290                     HdmiCecLocalDeviceTv tv = tv();
   1291                     if (tv == null) {
   1292                         if (!mAddressAllocated) {
   1293                             mSelectRequestBuffer.set(SelectRequestBuffer.newPortSelect(
   1294                                     HdmiControlService.this, portId, callback));
   1295                             return;
   1296                         }
   1297                         Slog.w(TAG, "Local tv device not available");
   1298                         invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
   1299                         return;
   1300                     }
   1301                     tv.doManualPortSwitching(portId, callback);
   1302                 }
   1303             });
   1304         }
   1305 
   1306         @Override
   1307         public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
   1308             enforceAccessPermission();
   1309             runOnServiceThread(new Runnable() {
   1310                 @Override
   1311                 public void run() {
   1312                     HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId);
   1313                     if (device != null) {
   1314                         device.sendKeyEvent(keyCode, isPressed);
   1315                         return;
   1316                     }
   1317                     if (mCecController != null) {
   1318                         HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
   1319                         if (localDevice == null) {
   1320                             Slog.w(TAG, "Local device not available");
   1321                             return;
   1322                         }
   1323                         localDevice.sendKeyEvent(keyCode, isPressed);
   1324                     }
   1325                 }
   1326             });
   1327         }
   1328 
   1329         @Override
   1330         public void oneTouchPlay(final IHdmiControlCallback callback) {
   1331             enforceAccessPermission();
   1332             runOnServiceThread(new Runnable() {
   1333                 @Override
   1334                 public void run() {
   1335                     HdmiControlService.this.oneTouchPlay(callback);
   1336                 }
   1337             });
   1338         }
   1339 
   1340         @Override
   1341         public void queryDisplayStatus(final IHdmiControlCallback callback) {
   1342             enforceAccessPermission();
   1343             runOnServiceThread(new Runnable() {
   1344                 @Override
   1345                 public void run() {
   1346                     HdmiControlService.this.queryDisplayStatus(callback);
   1347                 }
   1348             });
   1349         }
   1350 
   1351         @Override
   1352         public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
   1353             enforceAccessPermission();
   1354             HdmiControlService.this.addHotplugEventListener(listener);
   1355         }
   1356 
   1357         @Override
   1358         public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
   1359             enforceAccessPermission();
   1360             HdmiControlService.this.removeHotplugEventListener(listener);
   1361         }
   1362 
   1363         @Override
   1364         public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
   1365             enforceAccessPermission();
   1366             HdmiControlService.this.addDeviceEventListener(listener);
   1367         }
   1368 
   1369         @Override
   1370         public List<HdmiPortInfo> getPortInfo() {
   1371             enforceAccessPermission();
   1372             return HdmiControlService.this.getPortInfo();
   1373         }
   1374 
   1375         @Override
   1376         public boolean canChangeSystemAudioMode() {
   1377             enforceAccessPermission();
   1378             HdmiCecLocalDeviceTv tv = tv();
   1379             if (tv == null) {
   1380                 return false;
   1381             }
   1382             return tv.hasSystemAudioDevice();
   1383         }
   1384 
   1385         @Override
   1386         public boolean getSystemAudioMode() {
   1387             enforceAccessPermission();
   1388             HdmiCecLocalDeviceTv tv = tv();
   1389             if (tv == null) {
   1390                 return false;
   1391             }
   1392             return tv.isSystemAudioActivated();
   1393         }
   1394 
   1395         @Override
   1396         public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
   1397             enforceAccessPermission();
   1398             runOnServiceThread(new Runnable() {
   1399                 @Override
   1400                 public void run() {
   1401                     HdmiCecLocalDeviceTv tv = tv();
   1402                     if (tv == null) {
   1403                         Slog.w(TAG, "Local tv device not available");
   1404                         invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
   1405                         return;
   1406                     }
   1407                     tv.changeSystemAudioMode(enabled, callback);
   1408                 }
   1409             });
   1410         }
   1411 
   1412         @Override
   1413         public void addSystemAudioModeChangeListener(
   1414                 final IHdmiSystemAudioModeChangeListener listener) {
   1415             enforceAccessPermission();
   1416             HdmiControlService.this.addSystemAudioModeChangeListner(listener);
   1417         }
   1418 
   1419         @Override
   1420         public void removeSystemAudioModeChangeListener(
   1421                 final IHdmiSystemAudioModeChangeListener listener) {
   1422             enforceAccessPermission();
   1423             HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
   1424         }
   1425 
   1426         @Override
   1427         public void setInputChangeListener(final IHdmiInputChangeListener listener) {
   1428             enforceAccessPermission();
   1429             HdmiControlService.this.setInputChangeListener(listener);
   1430         }
   1431 
   1432         @Override
   1433         public List<HdmiDeviceInfo> getInputDevices() {
   1434             enforceAccessPermission();
   1435             // No need to hold the lock for obtaining TV device as the local device instance
   1436             // is preserved while the HDMI control is enabled.
   1437             HdmiCecLocalDeviceTv tv = tv();
   1438             synchronized (mLock) {
   1439                 List<HdmiDeviceInfo> cecDevices = (tv == null)
   1440                         ? Collections.<HdmiDeviceInfo>emptyList()
   1441                         : tv.getSafeExternalInputsLocked();
   1442                 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked());
   1443             }
   1444         }
   1445 
   1446         // Returns all the CEC devices on the bus including system audio, switch,
   1447         // even those of reserved type.
   1448         @Override
   1449         public List<HdmiDeviceInfo> getDeviceList() {
   1450             enforceAccessPermission();
   1451             HdmiCecLocalDeviceTv tv = tv();
   1452             synchronized (mLock) {
   1453                 return (tv == null)
   1454                         ? Collections.<HdmiDeviceInfo>emptyList()
   1455                         : tv.getSafeCecDevicesLocked();
   1456             }
   1457         }
   1458 
   1459         @Override
   1460         public void setSystemAudioVolume(final int oldIndex, final int newIndex,
   1461                 final int maxIndex) {
   1462             enforceAccessPermission();
   1463             runOnServiceThread(new Runnable() {
   1464                 @Override
   1465                 public void run() {
   1466                     HdmiCecLocalDeviceTv tv = tv();
   1467                     if (tv == null) {
   1468                         Slog.w(TAG, "Local tv device not available");
   1469                         return;
   1470                     }
   1471                     tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
   1472                 }
   1473             });
   1474         }
   1475 
   1476         @Override
   1477         public void setSystemAudioMute(final boolean mute) {
   1478             enforceAccessPermission();
   1479             runOnServiceThread(new Runnable() {
   1480                 @Override
   1481                 public void run() {
   1482                     HdmiCecLocalDeviceTv tv = tv();
   1483                     if (tv == null) {
   1484                         Slog.w(TAG, "Local tv device not available");
   1485                         return;
   1486                     }
   1487                     tv.changeMute(mute);
   1488                 }
   1489             });
   1490         }
   1491 
   1492         @Override
   1493         public void setArcMode(final boolean enabled) {
   1494             enforceAccessPermission();
   1495             runOnServiceThread(new Runnable() {
   1496                 @Override
   1497                 public void run() {
   1498                     HdmiCecLocalDeviceTv tv = tv();
   1499                     if (tv == null) {
   1500                         Slog.w(TAG, "Local tv device not available to change arc mode.");
   1501                         return;
   1502                     }
   1503                 }
   1504             });
   1505         }
   1506 
   1507         @Override
   1508         public void setProhibitMode(final boolean enabled) {
   1509             enforceAccessPermission();
   1510             if (!isTvDevice()) {
   1511                 return;
   1512             }
   1513             HdmiControlService.this.setProhibitMode(enabled);
   1514         }
   1515 
   1516         @Override
   1517         public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
   1518                 final int deviceType) {
   1519             enforceAccessPermission();
   1520             HdmiControlService.this.addVendorCommandListener(listener, deviceType);
   1521         }
   1522 
   1523         @Override
   1524         public void sendVendorCommand(final int deviceType, final int targetAddress,
   1525                 final byte[] params, final boolean hasVendorId) {
   1526             enforceAccessPermission();
   1527             runOnServiceThread(new Runnable() {
   1528                 @Override
   1529                 public void run() {
   1530                     HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
   1531                     if (device == null) {
   1532                         Slog.w(TAG, "Local device not available");
   1533                         return;
   1534                     }
   1535                     if (hasVendorId) {
   1536                         sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
   1537                                 device.getDeviceInfo().getLogicalAddress(), targetAddress,
   1538                                 getVendorId(), params));
   1539                     } else {
   1540                         sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
   1541                                 device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
   1542                     }
   1543                 }
   1544             });
   1545         }
   1546 
   1547         @Override
   1548         public void sendStandby(final int deviceType, final int deviceId) {
   1549             enforceAccessPermission();
   1550             runOnServiceThread(new Runnable() {
   1551                 @Override
   1552                 public void run() {
   1553                     HdmiMhlLocalDeviceStub mhlDevice = mMhlController.getLocalDeviceById(deviceId);
   1554                     if (mhlDevice != null) {
   1555                         mhlDevice.sendStandby();
   1556                         return;
   1557                     }
   1558                     HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
   1559                     if (device == null) {
   1560                         Slog.w(TAG, "Local device not available");
   1561                         return;
   1562                     }
   1563                     device.sendStandby(deviceId);
   1564                 }
   1565             });
   1566         }
   1567 
   1568         @Override
   1569         public void setHdmiRecordListener(IHdmiRecordListener listener) {
   1570             enforceAccessPermission();
   1571             HdmiControlService.this.setHdmiRecordListener(listener);
   1572         }
   1573 
   1574         @Override
   1575         public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
   1576             enforceAccessPermission();
   1577             runOnServiceThread(new Runnable() {
   1578                 @Override
   1579                 public void run() {
   1580                     if (!isTvDeviceEnabled()) {
   1581                         Slog.w(TAG, "TV device is not enabled.");
   1582                         return;
   1583                     }
   1584                     tv().startOneTouchRecord(recorderAddress, recordSource);
   1585                 }
   1586             });
   1587         }
   1588 
   1589         @Override
   1590         public void stopOneTouchRecord(final int recorderAddress) {
   1591             enforceAccessPermission();
   1592             runOnServiceThread(new Runnable() {
   1593                 @Override
   1594                 public void run() {
   1595                     if (!isTvDeviceEnabled()) {
   1596                         Slog.w(TAG, "TV device is not enabled.");
   1597                         return;
   1598                     }
   1599                     tv().stopOneTouchRecord(recorderAddress);
   1600                 }
   1601             });
   1602         }
   1603 
   1604         @Override
   1605         public void startTimerRecording(final int recorderAddress, final int sourceType,
   1606                 final byte[] recordSource) {
   1607             enforceAccessPermission();
   1608             runOnServiceThread(new Runnable() {
   1609                 @Override
   1610                 public void run() {
   1611                     if (!isTvDeviceEnabled()) {
   1612                         Slog.w(TAG, "TV device is not enabled.");
   1613                         return;
   1614                     }
   1615                     tv().startTimerRecording(recorderAddress, sourceType, recordSource);
   1616                 }
   1617             });
   1618         }
   1619 
   1620         @Override
   1621         public void clearTimerRecording(final int recorderAddress, final int sourceType,
   1622                 final byte[] recordSource) {
   1623             enforceAccessPermission();
   1624             runOnServiceThread(new Runnable() {
   1625                 @Override
   1626                 public void run() {
   1627                     if (!isTvDeviceEnabled()) {
   1628                         Slog.w(TAG, "TV device is not enabled.");
   1629                         return;
   1630                     }
   1631                     tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
   1632                 }
   1633             });
   1634         }
   1635 
   1636         @Override
   1637         public void sendMhlVendorCommand(final int portId, final int offset, final int length,
   1638                 final byte[] data) {
   1639             enforceAccessPermission();
   1640             runOnServiceThread(new Runnable() {
   1641                 @Override
   1642                 public void run() {
   1643                     if (!isControlEnabled()) {
   1644                         Slog.w(TAG, "Hdmi control is disabled.");
   1645                         return ;
   1646                     }
   1647                     HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
   1648                     if (device == null) {
   1649                         Slog.w(TAG, "Invalid port id:" + portId);
   1650                         return;
   1651                     }
   1652                     mMhlController.sendVendorCommand(portId, offset, length, data);
   1653                 }
   1654             });
   1655         }
   1656 
   1657         @Override
   1658         public void addHdmiMhlVendorCommandListener(
   1659                 IHdmiMhlVendorCommandListener listener) {
   1660             enforceAccessPermission();
   1661             HdmiControlService.this.addHdmiMhlVendorCommandListener(listener);
   1662         }
   1663 
   1664         @Override
   1665         protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
   1666             getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
   1667             final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
   1668 
   1669             pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
   1670             pw.println("mProhibitMode: " + mProhibitMode);
   1671             if (mCecController != null) {
   1672                 pw.println("mCecController: ");
   1673                 pw.increaseIndent();
   1674                 mCecController.dump(pw);
   1675                 pw.decreaseIndent();
   1676             }
   1677 
   1678             pw.println("mMhlController: ");
   1679             pw.increaseIndent();
   1680             mMhlController.dump(pw);
   1681             pw.decreaseIndent();
   1682 
   1683             pw.println("mPortInfo: ");
   1684             pw.increaseIndent();
   1685             for (HdmiPortInfo hdmiPortInfo : mPortInfo) {
   1686                 pw.println("- " + hdmiPortInfo);
   1687             }
   1688             pw.decreaseIndent();
   1689             pw.println("mPowerStatus: " + mPowerStatus);
   1690         }
   1691     }
   1692 
   1693     @ServiceThreadOnly
   1694     private void oneTouchPlay(final IHdmiControlCallback callback) {
   1695         assertRunOnServiceThread();
   1696         HdmiCecLocalDevicePlayback source = playback();
   1697         if (source == null) {
   1698             Slog.w(TAG, "Local playback device not available");
   1699             invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
   1700             return;
   1701         }
   1702         source.oneTouchPlay(callback);
   1703     }
   1704 
   1705     @ServiceThreadOnly
   1706     private void queryDisplayStatus(final IHdmiControlCallback callback) {
   1707         assertRunOnServiceThread();
   1708         HdmiCecLocalDevicePlayback source = playback();
   1709         if (source == null) {
   1710             Slog.w(TAG, "Local playback device not available");
   1711             invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
   1712             return;
   1713         }
   1714         source.queryDisplayStatus(callback);
   1715     }
   1716 
   1717     private void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
   1718         final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
   1719         try {
   1720             listener.asBinder().linkToDeath(record, 0);
   1721         } catch (RemoteException e) {
   1722             Slog.w(TAG, "Listener already died");
   1723             return;
   1724         }
   1725         synchronized (mLock) {
   1726             mHotplugEventListenerRecords.add(record);
   1727         }
   1728 
   1729         // Inform the listener of the initial state of each HDMI port by generating
   1730         // hotplug events.
   1731         runOnServiceThread(new Runnable() {
   1732             @Override
   1733             public void run() {
   1734                 synchronized (mLock) {
   1735                     if (!mHotplugEventListenerRecords.contains(record)) return;
   1736                 }
   1737                 for (HdmiPortInfo port : mPortInfo) {
   1738                     HdmiHotplugEvent event = new HdmiHotplugEvent(port.getId(),
   1739                             mCecController.isConnected(port.getId()));
   1740                     synchronized (mLock) {
   1741                         invokeHotplugEventListenerLocked(listener, event);
   1742                     }
   1743                 }
   1744             }
   1745         });
   1746     }
   1747 
   1748     private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
   1749         synchronized (mLock) {
   1750             for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
   1751                 if (record.mListener.asBinder() == listener.asBinder()) {
   1752                     listener.asBinder().unlinkToDeath(record, 0);
   1753                     mHotplugEventListenerRecords.remove(record);
   1754                     break;
   1755                 }
   1756             }
   1757         }
   1758     }
   1759 
   1760     private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
   1761         DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
   1762         try {
   1763             listener.asBinder().linkToDeath(record, 0);
   1764         } catch (RemoteException e) {
   1765             Slog.w(TAG, "Listener already died");
   1766             return;
   1767         }
   1768         synchronized (mLock) {
   1769             mDeviceEventListenerRecords.add(record);
   1770         }
   1771     }
   1772 
   1773     void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
   1774         synchronized (mLock) {
   1775             for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
   1776                 try {
   1777                     record.mListener.onStatusChanged(device, status);
   1778                 } catch (RemoteException e) {
   1779                     Slog.e(TAG, "Failed to report device event:" + e);
   1780                 }
   1781             }
   1782         }
   1783     }
   1784 
   1785     private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
   1786         SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
   1787                 listener);
   1788         try {
   1789             listener.asBinder().linkToDeath(record, 0);
   1790         } catch (RemoteException e) {
   1791             Slog.w(TAG, "Listener already died");
   1792             return;
   1793         }
   1794         synchronized (mLock) {
   1795             mSystemAudioModeChangeListenerRecords.add(record);
   1796         }
   1797     }
   1798 
   1799     private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
   1800         synchronized (mLock) {
   1801             for (SystemAudioModeChangeListenerRecord record :
   1802                     mSystemAudioModeChangeListenerRecords) {
   1803                 if (record.mListener.asBinder() == listener) {
   1804                     listener.asBinder().unlinkToDeath(record, 0);
   1805                     mSystemAudioModeChangeListenerRecords.remove(record);
   1806                     break;
   1807                 }
   1808             }
   1809         }
   1810     }
   1811 
   1812     private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
   1813         private final IHdmiInputChangeListener mListener;
   1814 
   1815         public InputChangeListenerRecord(IHdmiInputChangeListener listener) {
   1816             mListener = listener;
   1817         }
   1818 
   1819         @Override
   1820         public void binderDied() {
   1821             synchronized (mLock) {
   1822                 if (mInputChangeListenerRecord == this) {
   1823                     mInputChangeListenerRecord = null;
   1824                 }
   1825             }
   1826         }
   1827     }
   1828 
   1829     private void setInputChangeListener(IHdmiInputChangeListener listener) {
   1830         synchronized (mLock) {
   1831             mInputChangeListenerRecord = new InputChangeListenerRecord(listener);
   1832             try {
   1833                 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
   1834             } catch (RemoteException e) {
   1835                 Slog.w(TAG, "Listener already died");
   1836                 return;
   1837             }
   1838         }
   1839     }
   1840 
   1841     void invokeInputChangeListener(HdmiDeviceInfo info) {
   1842         synchronized (mLock) {
   1843             if (mInputChangeListenerRecord != null) {
   1844                 try {
   1845                     mInputChangeListenerRecord.mListener.onChanged(info);
   1846                 } catch (RemoteException e) {
   1847                     Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
   1848                 }
   1849             }
   1850         }
   1851     }
   1852 
   1853     private void setHdmiRecordListener(IHdmiRecordListener listener) {
   1854         synchronized (mLock) {
   1855             mRecordListenerRecord = new HdmiRecordListenerRecord(listener);
   1856             try {
   1857                 listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
   1858             } catch (RemoteException e) {
   1859                 Slog.w(TAG, "Listener already died.", e);
   1860             }
   1861         }
   1862     }
   1863 
   1864     byte[] invokeRecordRequestListener(int recorderAddress) {
   1865         synchronized (mLock) {
   1866             if (mRecordListenerRecord != null) {
   1867                 try {
   1868                     return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress);
   1869                 } catch (RemoteException e) {
   1870                     Slog.w(TAG, "Failed to start record.", e);
   1871                 }
   1872             }
   1873             return EmptyArray.BYTE;
   1874         }
   1875     }
   1876 
   1877     void invokeOneTouchRecordResult(int recorderAddress, int result) {
   1878         synchronized (mLock) {
   1879             if (mRecordListenerRecord != null) {
   1880                 try {
   1881                     mRecordListenerRecord.mListener.onOneTouchRecordResult(recorderAddress, result);
   1882                 } catch (RemoteException e) {
   1883                     Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
   1884                 }
   1885             }
   1886         }
   1887     }
   1888 
   1889     void invokeTimerRecordingResult(int recorderAddress, int result) {
   1890         synchronized (mLock) {
   1891             if (mRecordListenerRecord != null) {
   1892                 try {
   1893                     mRecordListenerRecord.mListener.onTimerRecordingResult(recorderAddress, result);
   1894                 } catch (RemoteException e) {
   1895                     Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
   1896                 }
   1897             }
   1898         }
   1899     }
   1900 
   1901     void invokeClearTimerRecordingResult(int recorderAddress, int result) {
   1902         synchronized (mLock) {
   1903             if (mRecordListenerRecord != null) {
   1904                 try {
   1905                     mRecordListenerRecord.mListener.onClearTimerRecordingResult(recorderAddress,
   1906                             result);
   1907                 } catch (RemoteException e) {
   1908                     Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
   1909                 }
   1910             }
   1911         }
   1912     }
   1913 
   1914     private void invokeCallback(IHdmiControlCallback callback, int result) {
   1915         try {
   1916             callback.onComplete(result);
   1917         } catch (RemoteException e) {
   1918             Slog.e(TAG, "Invoking callback failed:" + e);
   1919         }
   1920     }
   1921 
   1922     private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener,
   1923             boolean enabled) {
   1924         try {
   1925             listener.onStatusChanged(enabled);
   1926         } catch (RemoteException e) {
   1927             Slog.e(TAG, "Invoking callback failed:" + e);
   1928         }
   1929     }
   1930 
   1931     private void announceHotplugEvent(int portId, boolean connected) {
   1932         HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
   1933         synchronized (mLock) {
   1934             for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
   1935                 invokeHotplugEventListenerLocked(record.mListener, event);
   1936             }
   1937         }
   1938     }
   1939 
   1940     private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
   1941             HdmiHotplugEvent event) {
   1942         try {
   1943             listener.onReceived(event);
   1944         } catch (RemoteException e) {
   1945             Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
   1946         }
   1947     }
   1948 
   1949     public HdmiCecLocalDeviceTv tv() {
   1950         return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
   1951     }
   1952 
   1953     boolean isTvDevice() {
   1954         return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
   1955     }
   1956 
   1957     boolean isTvDeviceEnabled() {
   1958         return isTvDevice() && tv() != null;
   1959     }
   1960 
   1961     private HdmiCecLocalDevicePlayback playback() {
   1962         return (HdmiCecLocalDevicePlayback)
   1963                 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
   1964     }
   1965 
   1966     AudioManager getAudioManager() {
   1967         return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
   1968     }
   1969 
   1970     boolean isControlEnabled() {
   1971         synchronized (mLock) {
   1972             return mHdmiControlEnabled;
   1973         }
   1974     }
   1975 
   1976     @ServiceThreadOnly
   1977     int getPowerStatus() {
   1978         assertRunOnServiceThread();
   1979         return mPowerStatus;
   1980     }
   1981 
   1982     @ServiceThreadOnly
   1983     boolean isPowerOnOrTransient() {
   1984         assertRunOnServiceThread();
   1985         return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
   1986                 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
   1987     }
   1988 
   1989     @ServiceThreadOnly
   1990     boolean isPowerStandbyOrTransient() {
   1991         assertRunOnServiceThread();
   1992         return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
   1993                 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
   1994     }
   1995 
   1996     @ServiceThreadOnly
   1997     boolean isPowerStandby() {
   1998         assertRunOnServiceThread();
   1999         return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
   2000     }
   2001 
   2002     @ServiceThreadOnly
   2003     void wakeUp() {
   2004         assertRunOnServiceThread();
   2005         mWakeUpMessageReceived = true;
   2006         mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.server.hdmi:WAKE");
   2007         // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
   2008         // the intent, the sequence will continue at onWakeUp().
   2009     }
   2010 
   2011     @ServiceThreadOnly
   2012     void standby() {
   2013         assertRunOnServiceThread();
   2014         if (!canGoToStandby()) {
   2015             return;
   2016         }
   2017         mStandbyMessageReceived = true;
   2018         mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
   2019         // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
   2020         // the intent, the sequence will continue at onStandby().
   2021     }
   2022 
   2023     @ServiceThreadOnly
   2024     private void onWakeUp() {
   2025         assertRunOnServiceThread();
   2026         mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
   2027         if (mCecController != null) {
   2028             if (mHdmiControlEnabled) {
   2029                 int startReason = INITIATED_BY_SCREEN_ON;
   2030                 if (mWakeUpMessageReceived) {
   2031                     startReason = INITIATED_BY_WAKE_UP_MESSAGE;
   2032                 }
   2033                 initializeCec(startReason);
   2034             }
   2035         } else {
   2036             Slog.i(TAG, "Device does not support HDMI-CEC.");
   2037         }
   2038         // TODO: Initialize MHL local devices.
   2039     }
   2040 
   2041     @ServiceThreadOnly
   2042     private void onStandby(final int standbyAction) {
   2043         assertRunOnServiceThread();
   2044         mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
   2045         invokeVendorCommandListenersOnControlStateChanged(false,
   2046                 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);
   2047         if (!canGoToStandby()) {
   2048             mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
   2049             return;
   2050         }
   2051 
   2052         final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
   2053         disableDevices(new PendingActionClearedCallback() {
   2054             @Override
   2055             public void onCleared(HdmiCecLocalDevice device) {
   2056                 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
   2057                 devices.remove(device);
   2058                 if (devices.isEmpty()) {
   2059                     onStandbyCompleted(standbyAction);
   2060                     // We will not clear local devices here, since some OEM/SOC will keep passing
   2061                     // the received packets until the application processor enters to the sleep
   2062                     // actually.
   2063                 }
   2064             }
   2065         });
   2066     }
   2067 
   2068     private boolean canGoToStandby() {
   2069         for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
   2070             if (!device.canGoToStandby()) return false;
   2071         }
   2072         return true;
   2073     }
   2074 
   2075     @ServiceThreadOnly
   2076     private void onLanguageChanged(String language) {
   2077         assertRunOnServiceThread();
   2078         mLanguage = language;
   2079 
   2080         if (isTvDeviceEnabled()) {
   2081             tv().broadcastMenuLanguage(language);
   2082             mCecController.setOption(OPTION_CEC_SET_LANGUAGE, HdmiUtils.languageToInt(language));
   2083         }
   2084     }
   2085 
   2086     @ServiceThreadOnly
   2087     String getLanguage() {
   2088         assertRunOnServiceThread();
   2089         return mLanguage;
   2090     }
   2091 
   2092     private void disableDevices(PendingActionClearedCallback callback) {
   2093         if (mCecController != null) {
   2094             for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
   2095                 device.disableDevice(mStandbyMessageReceived, callback);
   2096             }
   2097         }
   2098 
   2099         mMhlController.clearAllLocalDevices();
   2100     }
   2101 
   2102     @ServiceThreadOnly
   2103     private void clearLocalDevices() {
   2104         assertRunOnServiceThread();
   2105         if (mCecController == null) {
   2106             return;
   2107         }
   2108         mCecController.clearLogicalAddress();
   2109         mCecController.clearLocalDevices();
   2110     }
   2111 
   2112     @ServiceThreadOnly
   2113     private void onStandbyCompleted(int standbyAction) {
   2114         assertRunOnServiceThread();
   2115         Slog.v(TAG, "onStandbyCompleted");
   2116 
   2117         if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
   2118             return;
   2119         }
   2120         mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
   2121         for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
   2122             device.onStandby(mStandbyMessageReceived, standbyAction);
   2123         }
   2124         mStandbyMessageReceived = false;
   2125         mAddressAllocated = false;
   2126         mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, DISABLED);
   2127         mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
   2128     }
   2129 
   2130     private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
   2131         VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
   2132         try {
   2133             listener.asBinder().linkToDeath(record, 0);
   2134         } catch (RemoteException e) {
   2135             Slog.w(TAG, "Listener already died");
   2136             return;
   2137         }
   2138         synchronized (mLock) {
   2139             mVendorCommandListenerRecords.add(record);
   2140         }
   2141     }
   2142 
   2143     boolean invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress,
   2144             byte[] params, boolean hasVendorId) {
   2145         synchronized (mLock) {
   2146             if (mVendorCommandListenerRecords.isEmpty()) {
   2147                 return false;
   2148             }
   2149             for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
   2150                 if (record.mDeviceType != deviceType) {
   2151                     continue;
   2152                 }
   2153                 try {
   2154                     record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId);
   2155                 } catch (RemoteException e) {
   2156                     Slog.e(TAG, "Failed to notify vendor command reception", e);
   2157                 }
   2158             }
   2159             return true;
   2160         }
   2161     }
   2162 
   2163     boolean invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason) {
   2164         synchronized (mLock) {
   2165             if (mVendorCommandListenerRecords.isEmpty()) {
   2166                 return false;
   2167             }
   2168             for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
   2169                 try {
   2170                     record.mListener.onControlStateChanged(enabled, reason);
   2171                 } catch (RemoteException e) {
   2172                     Slog.e(TAG, "Failed to notify control-state-changed to vendor handler", e);
   2173                 }
   2174             }
   2175             return true;
   2176         }
   2177     }
   2178 
   2179     private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {
   2180         HdmiMhlVendorCommandListenerRecord record =
   2181                 new HdmiMhlVendorCommandListenerRecord(listener);
   2182         try {
   2183             listener.asBinder().linkToDeath(record, 0);
   2184         } catch (RemoteException e) {
   2185             Slog.w(TAG, "Listener already died.");
   2186             return;
   2187         }
   2188 
   2189         synchronized (mLock) {
   2190             mMhlVendorCommandListenerRecords.add(record);
   2191         }
   2192     }
   2193 
   2194     void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) {
   2195         synchronized (mLock) {
   2196             for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) {
   2197                 try {
   2198                     record.mListener.onReceived(portId, offest, length, data);
   2199                 } catch (RemoteException e) {
   2200                     Slog.e(TAG, "Failed to notify MHL vendor command", e);
   2201                 }
   2202             }
   2203         }
   2204     }
   2205 
   2206     boolean isProhibitMode() {
   2207         synchronized (mLock) {
   2208             return mProhibitMode;
   2209         }
   2210     }
   2211 
   2212     void setProhibitMode(boolean enabled) {
   2213         synchronized (mLock) {
   2214             mProhibitMode = enabled;
   2215         }
   2216     }
   2217 
   2218     @ServiceThreadOnly
   2219     void setCecOption(int key, int value) {
   2220         assertRunOnServiceThread();
   2221         mCecController.setOption(key, value);
   2222     }
   2223 
   2224     @ServiceThreadOnly
   2225     void setControlEnabled(boolean enabled) {
   2226         assertRunOnServiceThread();
   2227 
   2228         synchronized (mLock) {
   2229             mHdmiControlEnabled = enabled;
   2230         }
   2231 
   2232         if (enabled) {
   2233             enableHdmiControlService();
   2234             return;
   2235         }
   2236         // Call the vendor handler before the service is disabled.
   2237         invokeVendorCommandListenersOnControlStateChanged(false,
   2238                 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING);
   2239         // Post the remained tasks in the service thread again to give the vendor-issued-tasks
   2240         // a chance to run.
   2241         runOnServiceThread(new Runnable() {
   2242             @Override
   2243             public void run() {
   2244                 disableHdmiControlService();
   2245             }
   2246         });
   2247         return;
   2248     }
   2249 
   2250     @ServiceThreadOnly
   2251     private void enableHdmiControlService() {
   2252         mCecController.setOption(OPTION_CEC_ENABLE, ENABLED);
   2253         mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED);
   2254 
   2255         initializeCec(INITIATED_BY_ENABLE_CEC);
   2256     }
   2257 
   2258     @ServiceThreadOnly
   2259     private void disableHdmiControlService() {
   2260         disableDevices(new PendingActionClearedCallback() {
   2261             @Override
   2262             public void onCleared(HdmiCecLocalDevice device) {
   2263                 assertRunOnServiceThread();
   2264                 mCecController.flush(new Runnable() {
   2265                     @Override
   2266                     public void run() {
   2267                         mCecController.setOption(OPTION_CEC_ENABLE, DISABLED);
   2268                         mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED);
   2269                         clearLocalDevices();
   2270                     }
   2271                 });
   2272             }
   2273         });
   2274     }
   2275 
   2276     @ServiceThreadOnly
   2277     void setActivePortId(int portId) {
   2278         assertRunOnServiceThread();
   2279         mActivePortId = portId;
   2280 
   2281         // Resets last input for MHL, which stays valid only after the MHL device was selected,
   2282         // and no further switching is done.
   2283         setLastInputForMhl(Constants.INVALID_PORT_ID);
   2284     }
   2285 
   2286     @ServiceThreadOnly
   2287     void setLastInputForMhl(int portId) {
   2288         assertRunOnServiceThread();
   2289         mLastInputMhl = portId;
   2290     }
   2291 
   2292     @ServiceThreadOnly
   2293     int getLastInputForMhl() {
   2294         assertRunOnServiceThread();
   2295         return mLastInputMhl;
   2296     }
   2297 
   2298     /**
   2299      * Performs input change, routing control for MHL device.
   2300      *
   2301      * @param portId MHL port, or the last port to go back to if {@code contentOn} is false
   2302      * @param contentOn {@code true} if RAP data is content on; otherwise false
   2303      */
   2304     @ServiceThreadOnly
   2305     void changeInputForMhl(int portId, boolean contentOn) {
   2306         assertRunOnServiceThread();
   2307         if (tv() == null) return;
   2308         final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID;
   2309         if (portId != Constants.INVALID_PORT_ID) {
   2310             tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() {
   2311                 @Override
   2312                 public void onComplete(int result) throws RemoteException {
   2313                     // Keep the last input to switch back later when RAP[ContentOff] is received.
   2314                     // This effectively sets the port to invalid one if the switching is for
   2315                     // RAP[ContentOff].
   2316                     setLastInputForMhl(lastInput);
   2317                 }
   2318             });
   2319         }
   2320         // MHL device is always directly connected to the port. Update the active port ID to avoid
   2321         // unnecessary post-routing control task.
   2322         tv().setActivePortId(portId);
   2323 
   2324         // The port is either the MHL-enabled port where the mobile device is connected, or
   2325         // the last port to go back to when turnoff command is received. Note that the last port
   2326         // may not be the MHL-enabled one. In this case the device info to be passed to
   2327         // input change listener should be the one describing the corresponding HDMI port.
   2328         HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
   2329         HdmiDeviceInfo info = (device != null) ? device.getInfo()
   2330                 : mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE);
   2331         invokeInputChangeListener(info);
   2332     }
   2333 
   2334    void setMhlInputChangeEnabled(boolean enabled) {
   2335        mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
   2336 
   2337         synchronized (mLock) {
   2338             mMhlInputChangeEnabled = enabled;
   2339         }
   2340     }
   2341 
   2342     boolean isMhlInputChangeEnabled() {
   2343         synchronized (mLock) {
   2344             return mMhlInputChangeEnabled;
   2345         }
   2346     }
   2347 
   2348     @ServiceThreadOnly
   2349     void displayOsd(int messageId) {
   2350         assertRunOnServiceThread();
   2351         Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
   2352         intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
   2353         getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
   2354                 HdmiControlService.PERMISSION);
   2355     }
   2356 
   2357     @ServiceThreadOnly
   2358     void displayOsd(int messageId, int extra) {
   2359         assertRunOnServiceThread();
   2360         Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
   2361         intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
   2362         intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra);
   2363         getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
   2364                 HdmiControlService.PERMISSION);
   2365     }
   2366 }
   2367