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             int flag = AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME;
    993             if (0 <= volume && volume <= 100) {
    994                 Slog.i(TAG, "volume: " + volume);
    995                 flag |= AudioManager.FLAG_SHOW_UI;
    996                 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, flag);
    997             }
    998         }
    999     }
   1000 
   1001     void announceSystemAudioModeChange(boolean enabled) {
   1002         synchronized (mLock) {
   1003             for (SystemAudioModeChangeListenerRecord record :
   1004                     mSystemAudioModeChangeListenerRecords) {
   1005                 invokeSystemAudioModeChangeLocked(record.mListener, enabled);
   1006             }
   1007         }
   1008     }
   1009 
   1010     private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
   1011         // TODO: find better name instead of model name.
   1012         String displayName = Build.MODEL;
   1013         return new HdmiDeviceInfo(logicalAddress,
   1014                 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
   1015                 getVendorId(), displayName);
   1016     }
   1017 
   1018     @ServiceThreadOnly
   1019     void handleMhlHotplugEvent(int portId, boolean connected) {
   1020         assertRunOnServiceThread();
   1021         // Hotplug event is used to add/remove MHL devices as TV input.
   1022         if (connected) {
   1023             HdmiMhlLocalDeviceStub newDevice = new HdmiMhlLocalDeviceStub(this, portId);
   1024             HdmiMhlLocalDeviceStub oldDevice = mMhlController.addLocalDevice(newDevice);
   1025             if (oldDevice != null) {
   1026                 oldDevice.onDeviceRemoved();
   1027                 Slog.i(TAG, "Old device of port " + portId + " is removed");
   1028             }
   1029             invokeDeviceEventListeners(newDevice.getInfo(), DEVICE_EVENT_ADD_DEVICE);
   1030             updateSafeMhlInput();
   1031         } else {
   1032             HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId);
   1033             if (device != null) {
   1034                 device.onDeviceRemoved();
   1035                 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE);
   1036                 updateSafeMhlInput();
   1037             } else {
   1038                 Slog.w(TAG, "No device to remove:[portId=" + portId);
   1039             }
   1040         }
   1041         announceHotplugEvent(portId, connected);
   1042     }
   1043 
   1044     @ServiceThreadOnly
   1045     void handleMhlBusModeChanged(int portId, int busmode) {
   1046         assertRunOnServiceThread();
   1047         HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
   1048         if (device != null) {
   1049             device.setBusMode(busmode);
   1050         } else {
   1051             Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId +
   1052                     ", busmode:" + busmode + "]");
   1053         }
   1054     }
   1055 
   1056     @ServiceThreadOnly
   1057     void handleMhlBusOvercurrent(int portId, boolean on) {
   1058         assertRunOnServiceThread();
   1059         HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
   1060         if (device != null) {
   1061             device.onBusOvercurrentDetected(on);
   1062         } else {
   1063             Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]");
   1064         }
   1065     }
   1066 
   1067     @ServiceThreadOnly
   1068     void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) {
   1069         assertRunOnServiceThread();
   1070         HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
   1071 
   1072         if (device != null) {
   1073             device.setDeviceStatusChange(adopterId, deviceId);
   1074         } else {
   1075             Slog.w(TAG, "No mhl device exists for device status event[portId:"
   1076                     + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]");
   1077         }
   1078     }
   1079 
   1080     @ServiceThreadOnly
   1081     private void updateSafeMhlInput() {
   1082         assertRunOnServiceThread();
   1083         List<HdmiDeviceInfo> inputs = Collections.emptyList();
   1084         SparseArray<HdmiMhlLocalDeviceStub> devices = mMhlController.getAllLocalDevices();
   1085         for (int i = 0; i < devices.size(); ++i) {
   1086             HdmiMhlLocalDeviceStub device = devices.valueAt(i);
   1087             HdmiDeviceInfo info = device.getInfo();
   1088             if (info != null) {
   1089                 if (inputs.isEmpty()) {
   1090                     inputs = new ArrayList<>();
   1091                 }
   1092                 inputs.add(device.getInfo());
   1093             }
   1094         }
   1095         synchronized (mLock) {
   1096             mMhlDevices = inputs;
   1097         }
   1098     }
   1099 
   1100     @GuardedBy("mLock")
   1101     private List<HdmiDeviceInfo> getMhlDevicesLocked() {
   1102         return mMhlDevices;
   1103     }
   1104 
   1105     private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient {
   1106         private final IHdmiMhlVendorCommandListener mListener;
   1107 
   1108         public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) {
   1109             mListener = listener;
   1110         }
   1111 
   1112         @Override
   1113         public void binderDied() {
   1114             mMhlVendorCommandListenerRecords.remove(this);
   1115         }
   1116     }
   1117 
   1118     // Record class that monitors the event of the caller of being killed. Used to clean up
   1119     // the listener list and record list accordingly.
   1120     private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
   1121         private final IHdmiHotplugEventListener mListener;
   1122 
   1123         public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
   1124             mListener = listener;
   1125         }
   1126 
   1127         @Override
   1128         public void binderDied() {
   1129             synchronized (mLock) {
   1130                 mHotplugEventListenerRecords.remove(this);
   1131             }
   1132         }
   1133 
   1134         @Override
   1135         public boolean equals(Object obj) {
   1136             if (!(obj instanceof HotplugEventListenerRecord)) return false;
   1137             if (obj == this) return true;
   1138             HotplugEventListenerRecord other = (HotplugEventListenerRecord) obj;
   1139             return other.mListener == this.mListener;
   1140         }
   1141 
   1142         @Override
   1143         public int hashCode() {
   1144             return mListener.hashCode();
   1145         }
   1146     }
   1147 
   1148     private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
   1149         private final IHdmiDeviceEventListener mListener;
   1150 
   1151         public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
   1152             mListener = listener;
   1153         }
   1154 
   1155         @Override
   1156         public void binderDied() {
   1157             synchronized (mLock) {
   1158                 mDeviceEventListenerRecords.remove(this);
   1159             }
   1160         }
   1161     }
   1162 
   1163     private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
   1164         private final IHdmiSystemAudioModeChangeListener mListener;
   1165 
   1166         public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
   1167             mListener = listener;
   1168         }
   1169 
   1170         @Override
   1171         public void binderDied() {
   1172             synchronized (mLock) {
   1173                 mSystemAudioModeChangeListenerRecords.remove(this);
   1174             }
   1175         }
   1176     }
   1177 
   1178     class VendorCommandListenerRecord implements IBinder.DeathRecipient {
   1179         private final IHdmiVendorCommandListener mListener;
   1180         private final int mDeviceType;
   1181 
   1182         public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
   1183             mListener = listener;
   1184             mDeviceType = deviceType;
   1185         }
   1186 
   1187         @Override
   1188         public void binderDied() {
   1189             synchronized (mLock) {
   1190                 mVendorCommandListenerRecords.remove(this);
   1191             }
   1192         }
   1193     }
   1194 
   1195     private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
   1196         private final IHdmiRecordListener mListener;
   1197 
   1198         public HdmiRecordListenerRecord(IHdmiRecordListener listener) {
   1199             mListener = listener;
   1200         }
   1201 
   1202         @Override
   1203         public void binderDied() {
   1204             synchronized (mLock) {
   1205                 if (mRecordListenerRecord == this) {
   1206                     mRecordListenerRecord = null;
   1207                 }
   1208             }
   1209         }
   1210     }
   1211 
   1212     private void enforceAccessPermission() {
   1213         getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
   1214     }
   1215 
   1216     private final class BinderService extends IHdmiControlService.Stub {
   1217         @Override
   1218         public int[] getSupportedTypes() {
   1219             enforceAccessPermission();
   1220             // mLocalDevices is an unmodifiable list - no lock necesary.
   1221             int[] localDevices = new int[mLocalDevices.size()];
   1222             for (int i = 0; i < localDevices.length; ++i) {
   1223                 localDevices[i] = mLocalDevices.get(i);
   1224             }
   1225             return localDevices;
   1226         }
   1227 
   1228         @Override
   1229         public HdmiDeviceInfo getActiveSource() {
   1230             enforceAccessPermission();
   1231             HdmiCecLocalDeviceTv tv = tv();
   1232             if (tv == null) {
   1233                 Slog.w(TAG, "Local tv device not available");
   1234                 return null;
   1235             }
   1236             ActiveSource activeSource = tv.getActiveSource();
   1237             if (activeSource.isValid()) {
   1238                 return new HdmiDeviceInfo(activeSource.logicalAddress,
   1239                         activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID,
   1240                         HdmiDeviceInfo.DEVICE_INACTIVE, 0, "");
   1241             }
   1242             int activePath = tv.getActivePath();
   1243             if (activePath != HdmiDeviceInfo.PATH_INVALID) {
   1244                 HdmiDeviceInfo info = tv.getSafeDeviceInfoByPath(activePath);
   1245                 return (info != null) ? info : new HdmiDeviceInfo(activePath, tv.getActivePortId());
   1246             }
   1247             return null;
   1248         }
   1249 
   1250         @Override
   1251         public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) {
   1252             enforceAccessPermission();
   1253             runOnServiceThread(new Runnable() {
   1254                 @Override
   1255                 public void run() {
   1256                     if (callback == null) {
   1257                         Slog.e(TAG, "Callback cannot be null");
   1258                         return;
   1259                     }
   1260                     HdmiCecLocalDeviceTv tv = tv();
   1261                     if (tv == null) {
   1262                         if (!mAddressAllocated) {
   1263                             mSelectRequestBuffer.set(SelectRequestBuffer.newDeviceSelect(
   1264                                     HdmiControlService.this, deviceId, callback));
   1265                             return;
   1266                         }
   1267                         Slog.w(TAG, "Local tv device not available");
   1268                         invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
   1269                         return;
   1270                     }
   1271                     HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId);
   1272                     if (device != null) {
   1273                         if (device.getPortId() == tv.getActivePortId()) {
   1274                             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
   1275                             return;
   1276                         }
   1277                         // Upon selecting MHL device, we send RAP[Content On] to wake up
   1278                         // the connected mobile device, start routing control to switch ports.
   1279                         // callback is handled by MHL action.
   1280                         device.turnOn(callback);
   1281                         tv.doManualPortSwitching(device.getPortId(), null);
   1282                         return;
   1283                     }
   1284                     tv.deviceSelect(deviceId, callback);
   1285                 }
   1286             });
   1287         }
   1288 
   1289         @Override
   1290         public void portSelect(final int portId, final IHdmiControlCallback callback) {
   1291             enforceAccessPermission();
   1292             runOnServiceThread(new Runnable() {
   1293                 @Override
   1294                 public void run() {
   1295                     if (callback == null) {
   1296                         Slog.e(TAG, "Callback cannot be null");
   1297                         return;
   1298                     }
   1299                     HdmiCecLocalDeviceTv tv = tv();
   1300                     if (tv == null) {
   1301                         if (!mAddressAllocated) {
   1302                             mSelectRequestBuffer.set(SelectRequestBuffer.newPortSelect(
   1303                                     HdmiControlService.this, portId, callback));
   1304                             return;
   1305                         }
   1306                         Slog.w(TAG, "Local tv device not available");
   1307                         invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
   1308                         return;
   1309                     }
   1310                     tv.doManualPortSwitching(portId, callback);
   1311                 }
   1312             });
   1313         }
   1314 
   1315         @Override
   1316         public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
   1317             enforceAccessPermission();
   1318             runOnServiceThread(new Runnable() {
   1319                 @Override
   1320                 public void run() {
   1321                     HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId);
   1322                     if (device != null) {
   1323                         device.sendKeyEvent(keyCode, isPressed);
   1324                         return;
   1325                     }
   1326                     if (mCecController != null) {
   1327                         HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
   1328                         if (localDevice == null) {
   1329                             Slog.w(TAG, "Local device not available");
   1330                             return;
   1331                         }
   1332                         localDevice.sendKeyEvent(keyCode, isPressed);
   1333                     }
   1334                 }
   1335             });
   1336         }
   1337 
   1338         @Override
   1339         public void oneTouchPlay(final IHdmiControlCallback callback) {
   1340             enforceAccessPermission();
   1341             runOnServiceThread(new Runnable() {
   1342                 @Override
   1343                 public void run() {
   1344                     HdmiControlService.this.oneTouchPlay(callback);
   1345                 }
   1346             });
   1347         }
   1348 
   1349         @Override
   1350         public void queryDisplayStatus(final IHdmiControlCallback callback) {
   1351             enforceAccessPermission();
   1352             runOnServiceThread(new Runnable() {
   1353                 @Override
   1354                 public void run() {
   1355                     HdmiControlService.this.queryDisplayStatus(callback);
   1356                 }
   1357             });
   1358         }
   1359 
   1360         @Override
   1361         public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
   1362             enforceAccessPermission();
   1363             HdmiControlService.this.addHotplugEventListener(listener);
   1364         }
   1365 
   1366         @Override
   1367         public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
   1368             enforceAccessPermission();
   1369             HdmiControlService.this.removeHotplugEventListener(listener);
   1370         }
   1371 
   1372         @Override
   1373         public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
   1374             enforceAccessPermission();
   1375             HdmiControlService.this.addDeviceEventListener(listener);
   1376         }
   1377 
   1378         @Override
   1379         public List<HdmiPortInfo> getPortInfo() {
   1380             enforceAccessPermission();
   1381             return HdmiControlService.this.getPortInfo();
   1382         }
   1383 
   1384         @Override
   1385         public boolean canChangeSystemAudioMode() {
   1386             enforceAccessPermission();
   1387             HdmiCecLocalDeviceTv tv = tv();
   1388             if (tv == null) {
   1389                 return false;
   1390             }
   1391             return tv.hasSystemAudioDevice();
   1392         }
   1393 
   1394         @Override
   1395         public boolean getSystemAudioMode() {
   1396             enforceAccessPermission();
   1397             HdmiCecLocalDeviceTv tv = tv();
   1398             if (tv == null) {
   1399                 return false;
   1400             }
   1401             return tv.isSystemAudioActivated();
   1402         }
   1403 
   1404         @Override
   1405         public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
   1406             enforceAccessPermission();
   1407             runOnServiceThread(new Runnable() {
   1408                 @Override
   1409                 public void run() {
   1410                     HdmiCecLocalDeviceTv tv = tv();
   1411                     if (tv == null) {
   1412                         Slog.w(TAG, "Local tv device not available");
   1413                         invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
   1414                         return;
   1415                     }
   1416                     tv.changeSystemAudioMode(enabled, callback);
   1417                 }
   1418             });
   1419         }
   1420 
   1421         @Override
   1422         public void addSystemAudioModeChangeListener(
   1423                 final IHdmiSystemAudioModeChangeListener listener) {
   1424             enforceAccessPermission();
   1425             HdmiControlService.this.addSystemAudioModeChangeListner(listener);
   1426         }
   1427 
   1428         @Override
   1429         public void removeSystemAudioModeChangeListener(
   1430                 final IHdmiSystemAudioModeChangeListener listener) {
   1431             enforceAccessPermission();
   1432             HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
   1433         }
   1434 
   1435         @Override
   1436         public void setInputChangeListener(final IHdmiInputChangeListener listener) {
   1437             enforceAccessPermission();
   1438             HdmiControlService.this.setInputChangeListener(listener);
   1439         }
   1440 
   1441         @Override
   1442         public List<HdmiDeviceInfo> getInputDevices() {
   1443             enforceAccessPermission();
   1444             // No need to hold the lock for obtaining TV device as the local device instance
   1445             // is preserved while the HDMI control is enabled.
   1446             HdmiCecLocalDeviceTv tv = tv();
   1447             synchronized (mLock) {
   1448                 List<HdmiDeviceInfo> cecDevices = (tv == null)
   1449                         ? Collections.<HdmiDeviceInfo>emptyList()
   1450                         : tv.getSafeExternalInputsLocked();
   1451                 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked());
   1452             }
   1453         }
   1454 
   1455         // Returns all the CEC devices on the bus including system audio, switch,
   1456         // even those of reserved type.
   1457         @Override
   1458         public List<HdmiDeviceInfo> getDeviceList() {
   1459             enforceAccessPermission();
   1460             HdmiCecLocalDeviceTv tv = tv();
   1461             synchronized (mLock) {
   1462                 return (tv == null)
   1463                         ? Collections.<HdmiDeviceInfo>emptyList()
   1464                         : tv.getSafeCecDevicesLocked();
   1465             }
   1466         }
   1467 
   1468         @Override
   1469         public void setSystemAudioVolume(final int oldIndex, final int newIndex,
   1470                 final int maxIndex) {
   1471             enforceAccessPermission();
   1472             runOnServiceThread(new Runnable() {
   1473                 @Override
   1474                 public void run() {
   1475                     HdmiCecLocalDeviceTv tv = tv();
   1476                     if (tv == null) {
   1477                         Slog.w(TAG, "Local tv device not available");
   1478                         return;
   1479                     }
   1480                     tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
   1481                 }
   1482             });
   1483         }
   1484 
   1485         @Override
   1486         public void setSystemAudioMute(final boolean mute) {
   1487             enforceAccessPermission();
   1488             runOnServiceThread(new Runnable() {
   1489                 @Override
   1490                 public void run() {
   1491                     HdmiCecLocalDeviceTv tv = tv();
   1492                     if (tv == null) {
   1493                         Slog.w(TAG, "Local tv device not available");
   1494                         return;
   1495                     }
   1496                     tv.changeMute(mute);
   1497                 }
   1498             });
   1499         }
   1500 
   1501         @Override
   1502         public void setArcMode(final boolean enabled) {
   1503             enforceAccessPermission();
   1504             runOnServiceThread(new Runnable() {
   1505                 @Override
   1506                 public void run() {
   1507                     HdmiCecLocalDeviceTv tv = tv();
   1508                     if (tv == null) {
   1509                         Slog.w(TAG, "Local tv device not available to change arc mode.");
   1510                         return;
   1511                     }
   1512                 }
   1513             });
   1514         }
   1515 
   1516         @Override
   1517         public void setProhibitMode(final boolean enabled) {
   1518             enforceAccessPermission();
   1519             if (!isTvDevice()) {
   1520                 return;
   1521             }
   1522             HdmiControlService.this.setProhibitMode(enabled);
   1523         }
   1524 
   1525         @Override
   1526         public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
   1527                 final int deviceType) {
   1528             enforceAccessPermission();
   1529             HdmiControlService.this.addVendorCommandListener(listener, deviceType);
   1530         }
   1531 
   1532         @Override
   1533         public void sendVendorCommand(final int deviceType, final int targetAddress,
   1534                 final byte[] params, final boolean hasVendorId) {
   1535             enforceAccessPermission();
   1536             runOnServiceThread(new Runnable() {
   1537                 @Override
   1538                 public void run() {
   1539                     HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
   1540                     if (device == null) {
   1541                         Slog.w(TAG, "Local device not available");
   1542                         return;
   1543                     }
   1544                     if (hasVendorId) {
   1545                         sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
   1546                                 device.getDeviceInfo().getLogicalAddress(), targetAddress,
   1547                                 getVendorId(), params));
   1548                     } else {
   1549                         sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
   1550                                 device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
   1551                     }
   1552                 }
   1553             });
   1554         }
   1555 
   1556         @Override
   1557         public void sendStandby(final int deviceType, final int deviceId) {
   1558             enforceAccessPermission();
   1559             runOnServiceThread(new Runnable() {
   1560                 @Override
   1561                 public void run() {
   1562                     HdmiMhlLocalDeviceStub mhlDevice = mMhlController.getLocalDeviceById(deviceId);
   1563                     if (mhlDevice != null) {
   1564                         mhlDevice.sendStandby();
   1565                         return;
   1566                     }
   1567                     HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
   1568                     if (device == null) {
   1569                         Slog.w(TAG, "Local device not available");
   1570                         return;
   1571                     }
   1572                     device.sendStandby(deviceId);
   1573                 }
   1574             });
   1575         }
   1576 
   1577         @Override
   1578         public void setHdmiRecordListener(IHdmiRecordListener listener) {
   1579             enforceAccessPermission();
   1580             HdmiControlService.this.setHdmiRecordListener(listener);
   1581         }
   1582 
   1583         @Override
   1584         public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
   1585             enforceAccessPermission();
   1586             runOnServiceThread(new Runnable() {
   1587                 @Override
   1588                 public void run() {
   1589                     if (!isTvDeviceEnabled()) {
   1590                         Slog.w(TAG, "TV device is not enabled.");
   1591                         return;
   1592                     }
   1593                     tv().startOneTouchRecord(recorderAddress, recordSource);
   1594                 }
   1595             });
   1596         }
   1597 
   1598         @Override
   1599         public void stopOneTouchRecord(final int recorderAddress) {
   1600             enforceAccessPermission();
   1601             runOnServiceThread(new Runnable() {
   1602                 @Override
   1603                 public void run() {
   1604                     if (!isTvDeviceEnabled()) {
   1605                         Slog.w(TAG, "TV device is not enabled.");
   1606                         return;
   1607                     }
   1608                     tv().stopOneTouchRecord(recorderAddress);
   1609                 }
   1610             });
   1611         }
   1612 
   1613         @Override
   1614         public void startTimerRecording(final int recorderAddress, final int sourceType,
   1615                 final byte[] recordSource) {
   1616             enforceAccessPermission();
   1617             runOnServiceThread(new Runnable() {
   1618                 @Override
   1619                 public void run() {
   1620                     if (!isTvDeviceEnabled()) {
   1621                         Slog.w(TAG, "TV device is not enabled.");
   1622                         return;
   1623                     }
   1624                     tv().startTimerRecording(recorderAddress, sourceType, recordSource);
   1625                 }
   1626             });
   1627         }
   1628 
   1629         @Override
   1630         public void clearTimerRecording(final int recorderAddress, final int sourceType,
   1631                 final byte[] recordSource) {
   1632             enforceAccessPermission();
   1633             runOnServiceThread(new Runnable() {
   1634                 @Override
   1635                 public void run() {
   1636                     if (!isTvDeviceEnabled()) {
   1637                         Slog.w(TAG, "TV device is not enabled.");
   1638                         return;
   1639                     }
   1640                     tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
   1641                 }
   1642             });
   1643         }
   1644 
   1645         @Override
   1646         public void sendMhlVendorCommand(final int portId, final int offset, final int length,
   1647                 final byte[] data) {
   1648             enforceAccessPermission();
   1649             runOnServiceThread(new Runnable() {
   1650                 @Override
   1651                 public void run() {
   1652                     if (!isControlEnabled()) {
   1653                         Slog.w(TAG, "Hdmi control is disabled.");
   1654                         return ;
   1655                     }
   1656                     HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
   1657                     if (device == null) {
   1658                         Slog.w(TAG, "Invalid port id:" + portId);
   1659                         return;
   1660                     }
   1661                     mMhlController.sendVendorCommand(portId, offset, length, data);
   1662                 }
   1663             });
   1664         }
   1665 
   1666         @Override
   1667         public void addHdmiMhlVendorCommandListener(
   1668                 IHdmiMhlVendorCommandListener listener) {
   1669             enforceAccessPermission();
   1670             HdmiControlService.this.addHdmiMhlVendorCommandListener(listener);
   1671         }
   1672 
   1673         @Override
   1674         public void setStandbyMode(final boolean isStandbyModeOn) {
   1675             enforceAccessPermission();
   1676             runOnServiceThread(new Runnable() {
   1677                 @Override
   1678                 public void run() {
   1679                     HdmiControlService.this.setStandbyMode(isStandbyModeOn);
   1680                 }
   1681             });
   1682         }
   1683 
   1684         @Override
   1685         protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
   1686             if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) return;
   1687             final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
   1688 
   1689             pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
   1690             pw.println("mProhibitMode: " + mProhibitMode);
   1691             if (mCecController != null) {
   1692                 pw.println("mCecController: ");
   1693                 pw.increaseIndent();
   1694                 mCecController.dump(pw);
   1695                 pw.decreaseIndent();
   1696             }
   1697 
   1698             pw.println("mMhlController: ");
   1699             pw.increaseIndent();
   1700             mMhlController.dump(pw);
   1701             pw.decreaseIndent();
   1702 
   1703             pw.println("mPortInfo: ");
   1704             pw.increaseIndent();
   1705             for (HdmiPortInfo hdmiPortInfo : mPortInfo) {
   1706                 pw.println("- " + hdmiPortInfo);
   1707             }
   1708             pw.decreaseIndent();
   1709             pw.println("mPowerStatus: " + mPowerStatus);
   1710         }
   1711     }
   1712 
   1713     @ServiceThreadOnly
   1714     private void oneTouchPlay(final IHdmiControlCallback callback) {
   1715         assertRunOnServiceThread();
   1716         HdmiCecLocalDevicePlayback source = playback();
   1717         if (source == null) {
   1718             Slog.w(TAG, "Local playback device not available");
   1719             invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
   1720             return;
   1721         }
   1722         source.oneTouchPlay(callback);
   1723     }
   1724 
   1725     @ServiceThreadOnly
   1726     private void queryDisplayStatus(final IHdmiControlCallback callback) {
   1727         assertRunOnServiceThread();
   1728         HdmiCecLocalDevicePlayback source = playback();
   1729         if (source == null) {
   1730             Slog.w(TAG, "Local playback device not available");
   1731             invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
   1732             return;
   1733         }
   1734         source.queryDisplayStatus(callback);
   1735     }
   1736 
   1737     private void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
   1738         final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
   1739         try {
   1740             listener.asBinder().linkToDeath(record, 0);
   1741         } catch (RemoteException e) {
   1742             Slog.w(TAG, "Listener already died");
   1743             return;
   1744         }
   1745         synchronized (mLock) {
   1746             mHotplugEventListenerRecords.add(record);
   1747         }
   1748 
   1749         // Inform the listener of the initial state of each HDMI port by generating
   1750         // hotplug events.
   1751         runOnServiceThread(new Runnable() {
   1752             @Override
   1753             public void run() {
   1754                 synchronized (mLock) {
   1755                     if (!mHotplugEventListenerRecords.contains(record)) return;
   1756                 }
   1757                 for (HdmiPortInfo port : mPortInfo) {
   1758                     HdmiHotplugEvent event = new HdmiHotplugEvent(port.getId(),
   1759                             mCecController.isConnected(port.getId()));
   1760                     synchronized (mLock) {
   1761                         invokeHotplugEventListenerLocked(listener, event);
   1762                     }
   1763                 }
   1764             }
   1765         });
   1766     }
   1767 
   1768     private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
   1769         synchronized (mLock) {
   1770             for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
   1771                 if (record.mListener.asBinder() == listener.asBinder()) {
   1772                     listener.asBinder().unlinkToDeath(record, 0);
   1773                     mHotplugEventListenerRecords.remove(record);
   1774                     break;
   1775                 }
   1776             }
   1777         }
   1778     }
   1779 
   1780     private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
   1781         DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
   1782         try {
   1783             listener.asBinder().linkToDeath(record, 0);
   1784         } catch (RemoteException e) {
   1785             Slog.w(TAG, "Listener already died");
   1786             return;
   1787         }
   1788         synchronized (mLock) {
   1789             mDeviceEventListenerRecords.add(record);
   1790         }
   1791     }
   1792 
   1793     void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
   1794         synchronized (mLock) {
   1795             for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
   1796                 try {
   1797                     record.mListener.onStatusChanged(device, status);
   1798                 } catch (RemoteException e) {
   1799                     Slog.e(TAG, "Failed to report device event:" + e);
   1800                 }
   1801             }
   1802         }
   1803     }
   1804 
   1805     private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
   1806         SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
   1807                 listener);
   1808         try {
   1809             listener.asBinder().linkToDeath(record, 0);
   1810         } catch (RemoteException e) {
   1811             Slog.w(TAG, "Listener already died");
   1812             return;
   1813         }
   1814         synchronized (mLock) {
   1815             mSystemAudioModeChangeListenerRecords.add(record);
   1816         }
   1817     }
   1818 
   1819     private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
   1820         synchronized (mLock) {
   1821             for (SystemAudioModeChangeListenerRecord record :
   1822                     mSystemAudioModeChangeListenerRecords) {
   1823                 if (record.mListener.asBinder() == listener) {
   1824                     listener.asBinder().unlinkToDeath(record, 0);
   1825                     mSystemAudioModeChangeListenerRecords.remove(record);
   1826                     break;
   1827                 }
   1828             }
   1829         }
   1830     }
   1831 
   1832     private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
   1833         private final IHdmiInputChangeListener mListener;
   1834 
   1835         public InputChangeListenerRecord(IHdmiInputChangeListener listener) {
   1836             mListener = listener;
   1837         }
   1838 
   1839         @Override
   1840         public void binderDied() {
   1841             synchronized (mLock) {
   1842                 if (mInputChangeListenerRecord == this) {
   1843                     mInputChangeListenerRecord = null;
   1844                 }
   1845             }
   1846         }
   1847     }
   1848 
   1849     private void setInputChangeListener(IHdmiInputChangeListener listener) {
   1850         synchronized (mLock) {
   1851             mInputChangeListenerRecord = new InputChangeListenerRecord(listener);
   1852             try {
   1853                 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
   1854             } catch (RemoteException e) {
   1855                 Slog.w(TAG, "Listener already died");
   1856                 return;
   1857             }
   1858         }
   1859     }
   1860 
   1861     void invokeInputChangeListener(HdmiDeviceInfo info) {
   1862         synchronized (mLock) {
   1863             if (mInputChangeListenerRecord != null) {
   1864                 try {
   1865                     mInputChangeListenerRecord.mListener.onChanged(info);
   1866                 } catch (RemoteException e) {
   1867                     Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
   1868                 }
   1869             }
   1870         }
   1871     }
   1872 
   1873     private void setHdmiRecordListener(IHdmiRecordListener listener) {
   1874         synchronized (mLock) {
   1875             mRecordListenerRecord = new HdmiRecordListenerRecord(listener);
   1876             try {
   1877                 listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
   1878             } catch (RemoteException e) {
   1879                 Slog.w(TAG, "Listener already died.", e);
   1880             }
   1881         }
   1882     }
   1883 
   1884     byte[] invokeRecordRequestListener(int recorderAddress) {
   1885         synchronized (mLock) {
   1886             if (mRecordListenerRecord != null) {
   1887                 try {
   1888                     return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress);
   1889                 } catch (RemoteException e) {
   1890                     Slog.w(TAG, "Failed to start record.", e);
   1891                 }
   1892             }
   1893             return EmptyArray.BYTE;
   1894         }
   1895     }
   1896 
   1897     void invokeOneTouchRecordResult(int recorderAddress, int result) {
   1898         synchronized (mLock) {
   1899             if (mRecordListenerRecord != null) {
   1900                 try {
   1901                     mRecordListenerRecord.mListener.onOneTouchRecordResult(recorderAddress, result);
   1902                 } catch (RemoteException e) {
   1903                     Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
   1904                 }
   1905             }
   1906         }
   1907     }
   1908 
   1909     void invokeTimerRecordingResult(int recorderAddress, int result) {
   1910         synchronized (mLock) {
   1911             if (mRecordListenerRecord != null) {
   1912                 try {
   1913                     mRecordListenerRecord.mListener.onTimerRecordingResult(recorderAddress, result);
   1914                 } catch (RemoteException e) {
   1915                     Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
   1916                 }
   1917             }
   1918         }
   1919     }
   1920 
   1921     void invokeClearTimerRecordingResult(int recorderAddress, int result) {
   1922         synchronized (mLock) {
   1923             if (mRecordListenerRecord != null) {
   1924                 try {
   1925                     mRecordListenerRecord.mListener.onClearTimerRecordingResult(recorderAddress,
   1926                             result);
   1927                 } catch (RemoteException e) {
   1928                     Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
   1929                 }
   1930             }
   1931         }
   1932     }
   1933 
   1934     private void invokeCallback(IHdmiControlCallback callback, int result) {
   1935         try {
   1936             callback.onComplete(result);
   1937         } catch (RemoteException e) {
   1938             Slog.e(TAG, "Invoking callback failed:" + e);
   1939         }
   1940     }
   1941 
   1942     private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener,
   1943             boolean enabled) {
   1944         try {
   1945             listener.onStatusChanged(enabled);
   1946         } catch (RemoteException e) {
   1947             Slog.e(TAG, "Invoking callback failed:" + e);
   1948         }
   1949     }
   1950 
   1951     private void announceHotplugEvent(int portId, boolean connected) {
   1952         HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
   1953         synchronized (mLock) {
   1954             for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
   1955                 invokeHotplugEventListenerLocked(record.mListener, event);
   1956             }
   1957         }
   1958     }
   1959 
   1960     private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
   1961             HdmiHotplugEvent event) {
   1962         try {
   1963             listener.onReceived(event);
   1964         } catch (RemoteException e) {
   1965             Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
   1966         }
   1967     }
   1968 
   1969     public HdmiCecLocalDeviceTv tv() {
   1970         return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
   1971     }
   1972 
   1973     boolean isTvDevice() {
   1974         return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
   1975     }
   1976 
   1977     boolean isTvDeviceEnabled() {
   1978         return isTvDevice() && tv() != null;
   1979     }
   1980 
   1981     private HdmiCecLocalDevicePlayback playback() {
   1982         return (HdmiCecLocalDevicePlayback)
   1983                 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
   1984     }
   1985 
   1986     AudioManager getAudioManager() {
   1987         return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
   1988     }
   1989 
   1990     boolean isControlEnabled() {
   1991         synchronized (mLock) {
   1992             return mHdmiControlEnabled;
   1993         }
   1994     }
   1995 
   1996     @ServiceThreadOnly
   1997     int getPowerStatus() {
   1998         assertRunOnServiceThread();
   1999         return mPowerStatus;
   2000     }
   2001 
   2002     @ServiceThreadOnly
   2003     boolean isPowerOnOrTransient() {
   2004         assertRunOnServiceThread();
   2005         return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
   2006                 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
   2007     }
   2008 
   2009     @ServiceThreadOnly
   2010     boolean isPowerStandbyOrTransient() {
   2011         assertRunOnServiceThread();
   2012         return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
   2013                 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
   2014     }
   2015 
   2016     @ServiceThreadOnly
   2017     boolean isPowerStandby() {
   2018         assertRunOnServiceThread();
   2019         return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
   2020     }
   2021 
   2022     @ServiceThreadOnly
   2023     void wakeUp() {
   2024         assertRunOnServiceThread();
   2025         mWakeUpMessageReceived = true;
   2026         mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.server.hdmi:WAKE");
   2027         // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
   2028         // the intent, the sequence will continue at onWakeUp().
   2029     }
   2030 
   2031     @ServiceThreadOnly
   2032     void standby() {
   2033         assertRunOnServiceThread();
   2034         if (!canGoToStandby()) {
   2035             return;
   2036         }
   2037         mStandbyMessageReceived = true;
   2038         mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
   2039         // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
   2040         // the intent, the sequence will continue at onStandby().
   2041     }
   2042 
   2043     boolean isWakeUpMessageReceived() {
   2044         return mWakeUpMessageReceived;
   2045     }
   2046 
   2047     @ServiceThreadOnly
   2048     private void onWakeUp() {
   2049         assertRunOnServiceThread();
   2050         mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
   2051         if (mCecController != null) {
   2052             if (mHdmiControlEnabled) {
   2053                 int startReason = INITIATED_BY_SCREEN_ON;
   2054                 if (mWakeUpMessageReceived) {
   2055                     startReason = INITIATED_BY_WAKE_UP_MESSAGE;
   2056                 }
   2057                 initializeCec(startReason);
   2058             }
   2059         } else {
   2060             Slog.i(TAG, "Device does not support HDMI-CEC.");
   2061         }
   2062         // TODO: Initialize MHL local devices.
   2063     }
   2064 
   2065     @ServiceThreadOnly
   2066     private void onStandby(final int standbyAction) {
   2067         assertRunOnServiceThread();
   2068         mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
   2069         invokeVendorCommandListenersOnControlStateChanged(false,
   2070                 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);
   2071         if (!canGoToStandby()) {
   2072             mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
   2073             return;
   2074         }
   2075 
   2076         final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
   2077         disableDevices(new PendingActionClearedCallback() {
   2078             @Override
   2079             public void onCleared(HdmiCecLocalDevice device) {
   2080                 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
   2081                 devices.remove(device);
   2082                 if (devices.isEmpty()) {
   2083                     onStandbyCompleted(standbyAction);
   2084                     // We will not clear local devices here, since some OEM/SOC will keep passing
   2085                     // the received packets until the application processor enters to the sleep
   2086                     // actually.
   2087                 }
   2088             }
   2089         });
   2090     }
   2091 
   2092     private boolean canGoToStandby() {
   2093         for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
   2094             if (!device.canGoToStandby()) return false;
   2095         }
   2096         return true;
   2097     }
   2098 
   2099     @ServiceThreadOnly
   2100     private void onLanguageChanged(String language) {
   2101         assertRunOnServiceThread();
   2102         mLanguage = language;
   2103 
   2104         if (isTvDeviceEnabled()) {
   2105             tv().broadcastMenuLanguage(language);
   2106             mCecController.setLanguage(language);
   2107         }
   2108     }
   2109 
   2110     @ServiceThreadOnly
   2111     String getLanguage() {
   2112         assertRunOnServiceThread();
   2113         return mLanguage;
   2114     }
   2115 
   2116     private void disableDevices(PendingActionClearedCallback callback) {
   2117         if (mCecController != null) {
   2118             for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
   2119                 device.disableDevice(mStandbyMessageReceived, callback);
   2120             }
   2121         }
   2122 
   2123         mMhlController.clearAllLocalDevices();
   2124     }
   2125 
   2126     @ServiceThreadOnly
   2127     private void clearLocalDevices() {
   2128         assertRunOnServiceThread();
   2129         if (mCecController == null) {
   2130             return;
   2131         }
   2132         mCecController.clearLogicalAddress();
   2133         mCecController.clearLocalDevices();
   2134     }
   2135 
   2136     @ServiceThreadOnly
   2137     private void onStandbyCompleted(int standbyAction) {
   2138         assertRunOnServiceThread();
   2139         Slog.v(TAG, "onStandbyCompleted");
   2140 
   2141         if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
   2142             return;
   2143         }
   2144         mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
   2145         for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
   2146             device.onStandby(mStandbyMessageReceived, standbyAction);
   2147         }
   2148         mStandbyMessageReceived = false;
   2149         mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false);
   2150         mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
   2151     }
   2152 
   2153     private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
   2154         VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
   2155         try {
   2156             listener.asBinder().linkToDeath(record, 0);
   2157         } catch (RemoteException e) {
   2158             Slog.w(TAG, "Listener already died");
   2159             return;
   2160         }
   2161         synchronized (mLock) {
   2162             mVendorCommandListenerRecords.add(record);
   2163         }
   2164     }
   2165 
   2166     boolean invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress,
   2167             byte[] params, boolean hasVendorId) {
   2168         synchronized (mLock) {
   2169             if (mVendorCommandListenerRecords.isEmpty()) {
   2170                 return false;
   2171             }
   2172             for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
   2173                 if (record.mDeviceType != deviceType) {
   2174                     continue;
   2175                 }
   2176                 try {
   2177                     record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId);
   2178                 } catch (RemoteException e) {
   2179                     Slog.e(TAG, "Failed to notify vendor command reception", e);
   2180                 }
   2181             }
   2182             return true;
   2183         }
   2184     }
   2185 
   2186     boolean invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason) {
   2187         synchronized (mLock) {
   2188             if (mVendorCommandListenerRecords.isEmpty()) {
   2189                 return false;
   2190             }
   2191             for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
   2192                 try {
   2193                     record.mListener.onControlStateChanged(enabled, reason);
   2194                 } catch (RemoteException e) {
   2195                     Slog.e(TAG, "Failed to notify control-state-changed to vendor handler", e);
   2196                 }
   2197             }
   2198             return true;
   2199         }
   2200     }
   2201 
   2202     private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {
   2203         HdmiMhlVendorCommandListenerRecord record =
   2204                 new HdmiMhlVendorCommandListenerRecord(listener);
   2205         try {
   2206             listener.asBinder().linkToDeath(record, 0);
   2207         } catch (RemoteException e) {
   2208             Slog.w(TAG, "Listener already died.");
   2209             return;
   2210         }
   2211 
   2212         synchronized (mLock) {
   2213             mMhlVendorCommandListenerRecords.add(record);
   2214         }
   2215     }
   2216 
   2217     void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) {
   2218         synchronized (mLock) {
   2219             for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) {
   2220                 try {
   2221                     record.mListener.onReceived(portId, offest, length, data);
   2222                 } catch (RemoteException e) {
   2223                     Slog.e(TAG, "Failed to notify MHL vendor command", e);
   2224                 }
   2225             }
   2226         }
   2227     }
   2228 
   2229     void setStandbyMode(boolean isStandbyModeOn) {
   2230         assertRunOnServiceThread();
   2231         if (isPowerOnOrTransient() && isStandbyModeOn) {
   2232             mPowerManager.goToSleep(SystemClock.uptimeMillis(),
   2233                     PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
   2234             if (playback() != null) {
   2235                 playback().sendStandby(0 /* unused */);
   2236             }
   2237         } else if (isPowerStandbyOrTransient() && !isStandbyModeOn) {
   2238             mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.server.hdmi:WAKE");
   2239             if (playback() != null) {
   2240                 oneTouchPlay(new IHdmiControlCallback.Stub() {
   2241                     @Override
   2242                     public void onComplete(int result) {
   2243                         if (result != HdmiControlManager.RESULT_SUCCESS) {
   2244                             Slog.w(TAG, "Failed to complete 'one touch play'. result=" + result);
   2245                         }
   2246                     }
   2247                 });
   2248             }
   2249         }
   2250     }
   2251 
   2252     boolean isProhibitMode() {
   2253         synchronized (mLock) {
   2254             return mProhibitMode;
   2255         }
   2256     }
   2257 
   2258     void setProhibitMode(boolean enabled) {
   2259         synchronized (mLock) {
   2260             mProhibitMode = enabled;
   2261         }
   2262     }
   2263 
   2264     @ServiceThreadOnly
   2265     void setCecOption(int key, boolean value) {
   2266         assertRunOnServiceThread();
   2267         mCecController.setOption(key, value);
   2268     }
   2269 
   2270     @ServiceThreadOnly
   2271     void setControlEnabled(boolean enabled) {
   2272         assertRunOnServiceThread();
   2273 
   2274         synchronized (mLock) {
   2275             mHdmiControlEnabled = enabled;
   2276         }
   2277 
   2278         if (enabled) {
   2279             enableHdmiControlService();
   2280             return;
   2281         }
   2282         // Call the vendor handler before the service is disabled.
   2283         invokeVendorCommandListenersOnControlStateChanged(false,
   2284                 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING);
   2285         // Post the remained tasks in the service thread again to give the vendor-issued-tasks
   2286         // a chance to run.
   2287         runOnServiceThread(new Runnable() {
   2288             @Override
   2289             public void run() {
   2290                 disableHdmiControlService();
   2291             }
   2292         });
   2293         return;
   2294     }
   2295 
   2296     @ServiceThreadOnly
   2297     private void enableHdmiControlService() {
   2298         mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
   2299         mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED);
   2300 
   2301         initializeCec(INITIATED_BY_ENABLE_CEC);
   2302     }
   2303 
   2304     @ServiceThreadOnly
   2305     private void disableHdmiControlService() {
   2306         disableDevices(new PendingActionClearedCallback() {
   2307             @Override
   2308             public void onCleared(HdmiCecLocalDevice device) {
   2309                 assertRunOnServiceThread();
   2310                 mCecController.flush(new Runnable() {
   2311                     @Override
   2312                     public void run() {
   2313                         mCecController.setOption(OptionKey.ENABLE_CEC, false);
   2314                         mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED);
   2315                         clearLocalDevices();
   2316                     }
   2317                 });
   2318             }
   2319         });
   2320     }
   2321 
   2322     @ServiceThreadOnly
   2323     void setActivePortId(int portId) {
   2324         assertRunOnServiceThread();
   2325         mActivePortId = portId;
   2326 
   2327         // Resets last input for MHL, which stays valid only after the MHL device was selected,
   2328         // and no further switching is done.
   2329         setLastInputForMhl(Constants.INVALID_PORT_ID);
   2330     }
   2331 
   2332     @ServiceThreadOnly
   2333     void setLastInputForMhl(int portId) {
   2334         assertRunOnServiceThread();
   2335         mLastInputMhl = portId;
   2336     }
   2337 
   2338     @ServiceThreadOnly
   2339     int getLastInputForMhl() {
   2340         assertRunOnServiceThread();
   2341         return mLastInputMhl;
   2342     }
   2343 
   2344     /**
   2345      * Performs input change, routing control for MHL device.
   2346      *
   2347      * @param portId MHL port, or the last port to go back to if {@code contentOn} is false
   2348      * @param contentOn {@code true} if RAP data is content on; otherwise false
   2349      */
   2350     @ServiceThreadOnly
   2351     void changeInputForMhl(int portId, boolean contentOn) {
   2352         assertRunOnServiceThread();
   2353         if (tv() == null) return;
   2354         final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID;
   2355         if (portId != Constants.INVALID_PORT_ID) {
   2356             tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() {
   2357                 @Override
   2358                 public void onComplete(int result) throws RemoteException {
   2359                     // Keep the last input to switch back later when RAP[ContentOff] is received.
   2360                     // This effectively sets the port to invalid one if the switching is for
   2361                     // RAP[ContentOff].
   2362                     setLastInputForMhl(lastInput);
   2363                 }
   2364             });
   2365         }
   2366         // MHL device is always directly connected to the port. Update the active port ID to avoid
   2367         // unnecessary post-routing control task.
   2368         tv().setActivePortId(portId);
   2369 
   2370         // The port is either the MHL-enabled port where the mobile device is connected, or
   2371         // the last port to go back to when turnoff command is received. Note that the last port
   2372         // may not be the MHL-enabled one. In this case the device info to be passed to
   2373         // input change listener should be the one describing the corresponding HDMI port.
   2374         HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
   2375         HdmiDeviceInfo info = (device != null) ? device.getInfo()
   2376                 : mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE);
   2377         invokeInputChangeListener(info);
   2378     }
   2379 
   2380    void setMhlInputChangeEnabled(boolean enabled) {
   2381        mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
   2382 
   2383         synchronized (mLock) {
   2384             mMhlInputChangeEnabled = enabled;
   2385         }
   2386     }
   2387 
   2388     boolean isMhlInputChangeEnabled() {
   2389         synchronized (mLock) {
   2390             return mMhlInputChangeEnabled;
   2391         }
   2392     }
   2393 
   2394     @ServiceThreadOnly
   2395     void displayOsd(int messageId) {
   2396         assertRunOnServiceThread();
   2397         Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
   2398         intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
   2399         getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
   2400                 HdmiControlService.PERMISSION);
   2401     }
   2402 
   2403     @ServiceThreadOnly
   2404     void displayOsd(int messageId, int extra) {
   2405         assertRunOnServiceThread();
   2406         Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
   2407         intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
   2408         intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra);
   2409         getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
   2410                 HdmiControlService.PERMISSION);
   2411     }
   2412 }
   2413