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