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