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