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