HdmiControlService.java revision 61daf6b38e7a7ada2a6ca5a60539a54b9c6810bd
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
17package com.android.server.hdmi;
18
19import static com.android.server.hdmi.Constants.DISABLED;
20import static com.android.server.hdmi.Constants.ENABLED;
21import static com.android.server.hdmi.Constants.OPTION_CEC_AUTO_DEVICE_OFF;
22import static com.android.server.hdmi.Constants.OPTION_CEC_AUTO_WAKEUP;
23import static com.android.server.hdmi.Constants.OPTION_CEC_ENABLE;
24import static com.android.server.hdmi.Constants.OPTION_CEC_SERVICE_CONTROL;
25import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE;
26import static com.android.server.hdmi.Constants.OPTION_MHL_INPUT_SWITCHING;
27import static com.android.server.hdmi.Constants.OPTION_MHL_POWER_CHARGE;
28
29import android.annotation.Nullable;
30import android.content.BroadcastReceiver;
31import android.content.ContentResolver;
32import android.content.Context;
33import android.content.Intent;
34import android.content.IntentFilter;
35import android.database.ContentObserver;
36import android.hardware.hdmi.HdmiDeviceInfo;
37import android.hardware.hdmi.HdmiControlManager;
38import android.hardware.hdmi.HdmiHotplugEvent;
39import android.hardware.hdmi.HdmiPortInfo;
40import android.hardware.hdmi.IHdmiControlCallback;
41import android.hardware.hdmi.IHdmiControlService;
42import android.hardware.hdmi.IHdmiDeviceEventListener;
43import android.hardware.hdmi.IHdmiHotplugEventListener;
44import android.hardware.hdmi.IHdmiInputChangeListener;
45import android.hardware.hdmi.IHdmiRecordListener;
46import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
47import android.hardware.hdmi.IHdmiVendorCommandListener;
48import android.media.AudioManager;
49import android.net.Uri;
50import android.os.Build;
51import android.os.Handler;
52import android.os.HandlerThread;
53import android.os.IBinder;
54import android.os.Looper;
55import android.os.PowerManager;
56import android.os.RemoteException;
57import android.os.SystemClock;
58import android.os.UserHandle;
59import android.provider.Settings.Global;
60import android.util.ArraySet;
61import android.util.Slog;
62import android.util.SparseArray;
63import android.util.SparseIntArray;
64
65import com.android.internal.annotations.GuardedBy;
66import com.android.server.SystemService;
67import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
68import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
69import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
70import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback;
71
72import libcore.util.EmptyArray;
73
74import java.util.ArrayList;
75import java.util.Arrays;
76import java.util.Collections;
77import java.util.List;
78
79/**
80 * Provides a service for sending and processing HDMI control messages,
81 * HDMI-CEC and MHL control command, and providing the information on both standard.
82 */
83public final class HdmiControlService extends SystemService {
84    private static final String TAG = "HdmiControlService";
85
86    static final String PERMISSION = "android.permission.HDMI_CEC";
87
88    // The reason code to initiate intializeCec().
89    static final int INITIATED_BY_ENABLE_CEC = 0;
90    static final int INITIATED_BY_BOOT_UP = 1;
91    static final int INITIATED_BY_SCREEN_ON = 2;
92    static final int INITIATED_BY_WAKE_UP_MESSAGE = 3;
93
94    /**
95     * Interface to report send result.
96     */
97    interface SendMessageCallback {
98        /**
99         * Called when {@link HdmiControlService#sendCecCommand} is completed.
100         *
101         * @param error result of send request.
102         * <ul>
103         * <li>{@link Constants#SEND_RESULT_SUCCESS}
104         * <li>{@link Constants#SEND_RESULT_NAK}
105         * <li>{@link Constants#SEND_RESULT_FAILURE}
106         * </ul>
107         */
108        void onSendCompleted(int error);
109    }
110
111    /**
112     * Interface to get a list of available logical devices.
113     */
114    interface DevicePollingCallback {
115        /**
116         * Called when device polling is finished.
117         *
118         * @param ackedAddress a list of logical addresses of available devices
119         */
120        void onPollingFinished(List<Integer> ackedAddress);
121    }
122
123    private class PowerStateReceiver extends BroadcastReceiver {
124        @Override
125        public void onReceive(Context context, Intent intent) {
126            switch (intent.getAction()) {
127                case Intent.ACTION_SCREEN_OFF:
128                    if (isPowerOnOrTransient()) {
129                        onStandby();
130                    }
131                    break;
132                case Intent.ACTION_SCREEN_ON:
133                    if (isPowerStandbyOrTransient()) {
134                        onWakeUp();
135                    }
136                    break;
137            }
138        }
139    }
140
141    // A thread to handle synchronous IO of CEC and MHL control service.
142    // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
143    // and sparse call it shares a thread to handle IO operations.
144    private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
145
146    // Used to synchronize the access to the service.
147    private final Object mLock = new Object();
148
149    // Type of logical devices hosted in the system. Stored in the unmodifiable list.
150    private final List<Integer> mLocalDevices;
151
152    // List of listeners registered by callers that want to get notified of
153    // hotplug events.
154    @GuardedBy("mLock")
155    private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>();
156
157    // List of records for hotplug event listener to handle the the caller killed in action.
158    @GuardedBy("mLock")
159    private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
160            new ArrayList<>();
161
162    // List of listeners registered by callers that want to get notified of
163    // device status events.
164    @GuardedBy("mLock")
165    private final ArrayList<IHdmiDeviceEventListener> mDeviceEventListeners = new ArrayList<>();
166
167    // List of records for device event listener to handle the the caller killed in action.
168    @GuardedBy("mLock")
169    private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
170            new ArrayList<>();
171
172    // List of records for vendor command listener to handle the the caller killed in action.
173    @GuardedBy("mLock")
174    private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords =
175            new ArrayList<>();
176
177    @GuardedBy("mLock")
178    private IHdmiInputChangeListener mInputChangeListener;
179
180    @GuardedBy("mLock")
181    private InputChangeListenerRecord mInputChangeListenerRecord;
182
183    @GuardedBy("mLock")
184    private IHdmiRecordListener mRecordListener;
185
186    @GuardedBy("mLock")
187    private HdmiRecordListenerRecord mRecordListenerRecord;
188
189    // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
190    // handling will be disabled and no request will be handled.
191    @GuardedBy("mLock")
192    private boolean mHdmiControlEnabled;
193
194    // Set to true while the service is in normal mode. While set to false, no input change is
195    // allowed. Used for situations where input change can confuse users such as channel auto-scan,
196    // system upgrade, etc., a.k.a. "prohibit mode".
197    @GuardedBy("mLock")
198    private boolean mProhibitMode;
199
200    // List of listeners registered by callers that want to get notified of
201    // system audio mode changes.
202    private final ArrayList<IHdmiSystemAudioModeChangeListener>
203            mSystemAudioModeChangeListeners = new ArrayList<>();
204    // List of records for system audio mode change to handle the the caller killed in action.
205    private final ArrayList<SystemAudioModeChangeListenerRecord>
206            mSystemAudioModeChangeListenerRecords = new ArrayList<>();
207
208    // Handler used to run a task in service thread.
209    private final Handler mHandler = new Handler();
210
211    private final SettingsObserver mSettingsObserver;
212
213    @Nullable
214    private HdmiCecController mCecController;
215
216    @Nullable
217    private HdmiMhlController mMhlController;
218
219    // HDMI port information. Stored in the unmodifiable list to keep the static information
220    // from being modified.
221    private List<HdmiPortInfo> mPortInfo;
222
223    // Map from path(physical address) to port ID.
224    private UnmodifiableSparseIntArray mPortIdMap;
225
226    // Map from port ID to HdmiPortInfo.
227    private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap;
228
229    private HdmiCecMessageValidator mMessageValidator;
230
231    private final PowerStateReceiver mPowerStateReceiver = new PowerStateReceiver();
232
233    @ServiceThreadOnly
234    private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
235
236    @ServiceThreadOnly
237    private boolean mStandbyMessageReceived = false;
238
239    @ServiceThreadOnly
240    private boolean mWakeUpMessageReceived = false;
241
242    public HdmiControlService(Context context) {
243        super(context);
244        mLocalDevices = HdmiUtils.asImmutableList(getContext().getResources().getIntArray(
245                com.android.internal.R.array.config_hdmiCecLogicalDeviceType));
246        mSettingsObserver = new SettingsObserver(mHandler);
247    }
248
249    @Override
250    public void onStart() {
251        mIoThread.start();
252        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
253        mProhibitMode = false;
254        mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
255
256        mCecController = HdmiCecController.create(this);
257        if (mCecController != null) {
258            // TODO: Remove this as soon as OEM's HAL implementation is corrected.
259            mCecController.setOption(OPTION_CEC_ENABLE, ENABLED);
260
261            // TODO: load value for mHdmiControlEnabled from preference.
262            if (mHdmiControlEnabled) {
263                initializeCec(INITIATED_BY_BOOT_UP);
264            }
265        } else {
266            Slog.i(TAG, "Device does not support HDMI-CEC.");
267        }
268
269        mMhlController = HdmiMhlController.create(this);
270        if (mMhlController == null) {
271            Slog.i(TAG, "Device does not support MHL-control.");
272        }
273        initPortInfo();
274        mMessageValidator = new HdmiCecMessageValidator(this);
275        publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
276
277        // Register broadcast receiver for power state change.
278        if (mCecController != null || mMhlController != null) {
279            IntentFilter filter = new IntentFilter();
280            filter.addAction(Intent.ACTION_SCREEN_OFF);
281            filter.addAction(Intent.ACTION_SCREEN_ON);
282            getContext().registerReceiver(mPowerStateReceiver, filter);
283        }
284    }
285
286    /**
287     * Called when the initialization of local devices is complete.
288     */
289    private void onInitializeCecComplete() {
290        if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
291            mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
292        }
293        mWakeUpMessageReceived = false;
294
295        if (isTvDevice()) {
296            mCecController.setOption(OPTION_CEC_AUTO_WAKEUP, toInt(tv().getAutoWakeup()));
297            registerContentObserver();
298        }
299    }
300
301
302    private void registerContentObserver() {
303        ContentResolver resolver = getContext().getContentResolver();
304        String[] settings = new String[] {
305                Global.HDMI_CONTROL_ENABLED,
306                Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
307                Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
308                Global.MHL_INPUT_SWITCHING_ENABLED,
309                Global.MHL_POWER_CHARGE_ENABLED
310        };
311        for (String s: settings) {
312            resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
313                    UserHandle.USER_ALL);
314        }
315    }
316
317    private class SettingsObserver extends ContentObserver {
318        public SettingsObserver(Handler handler) {
319            super(handler);
320        }
321
322        @Override
323        public void onChange(boolean selfChange, Uri uri) {
324            String option = uri.getLastPathSegment();
325            boolean enabled = readBooleanSetting(option, true);
326            switch (option) {
327                case Global.HDMI_CONTROL_ENABLED:
328                    setControlEnabled(enabled);
329                    break;
330                case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
331                    tv().setAutoWakeup(enabled);
332                    setOption(OPTION_CEC_AUTO_WAKEUP, toInt(enabled));
333                    break;
334                case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
335                    tv().setAutoDeviceOff(enabled);
336                    // No need to propagate to HAL.
337                    break;
338                case Global.MHL_INPUT_SWITCHING_ENABLED:
339                    setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
340                    break;
341                case Global.MHL_POWER_CHARGE_ENABLED:
342                    setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled));
343                    break;
344            }
345        }
346    }
347
348    private static int toInt(boolean enabled) {
349        return enabled ? ENABLED : DISABLED;
350    }
351
352    boolean readBooleanSetting(String key, boolean defVal) {
353        ContentResolver cr = getContext().getContentResolver();
354        return Global.getInt(cr, key, toInt(defVal)) == ENABLED;
355    }
356
357    void writeBooleanSetting(String key, boolean value) {
358        ContentResolver cr = getContext().getContentResolver();
359        Global.putInt(cr, key, toInt(value));
360    }
361
362    private void unregisterSettingsObserver() {
363        getContext().getContentResolver().unregisterContentObserver(mSettingsObserver);
364    }
365
366    private void initializeCec(int initiatedBy) {
367        mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, ENABLED);
368        initializeLocalDevices(mLocalDevices, initiatedBy);
369    }
370
371    @ServiceThreadOnly
372    private void initializeLocalDevices(final List<Integer> deviceTypes, final int initiatedBy) {
373        assertRunOnServiceThread();
374        // A container for [Logical Address, Local device info].
375        final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>();
376        final int[] finished = new int[1];
377        clearLocalDevices();
378        for (int type : deviceTypes) {
379            final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type);
380            localDevice.init();
381            mCecController.allocateLogicalAddress(type,
382                    localDevice.getPreferredAddress(), new AllocateAddressCallback() {
383                @Override
384                public void onAllocated(int deviceType, int logicalAddress) {
385                    if (logicalAddress == Constants.ADDR_UNREGISTERED) {
386                        Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
387                    } else {
388                        // Set POWER_STATUS_ON to all local devices because they share lifetime
389                        // with system.
390                        HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
391                                HdmiControlManager.POWER_STATUS_ON);
392                        localDevice.setDeviceInfo(deviceInfo);
393                        mCecController.addLocalDevice(deviceType, localDevice);
394                        mCecController.addLogicalAddress(logicalAddress);
395                        devices.append(logicalAddress, localDevice);
396                    }
397
398                    // Address allocation completed for all devices. Notify each device.
399                    if (deviceTypes.size() == ++finished[0]) {
400                        onInitializeCecComplete();
401                        notifyAddressAllocated(devices, initiatedBy);
402                    }
403                }
404            });
405        }
406    }
407
408    @ServiceThreadOnly
409    private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices, int initiatedBy) {
410        assertRunOnServiceThread();
411        for (int i = 0; i < devices.size(); ++i) {
412            int address = devices.keyAt(i);
413            HdmiCecLocalDevice device = devices.valueAt(i);
414            device.handleAddressAllocated(address, initiatedBy);
415        }
416    }
417
418    // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
419    // keep them in one place.
420    @ServiceThreadOnly
421    private void initPortInfo() {
422        assertRunOnServiceThread();
423        HdmiPortInfo[] cecPortInfo = null;
424
425        // CEC HAL provides majority of the info while MHL does only MHL support flag for
426        // each port. Return empty array if CEC HAL didn't provide the info.
427        if (mCecController != null) {
428            cecPortInfo = mCecController.getPortInfos();
429        }
430        if (cecPortInfo == null) {
431            return;
432        }
433
434        SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
435        SparseIntArray portIdMap = new SparseIntArray();
436        for (HdmiPortInfo info : cecPortInfo) {
437            portIdMap.put(info.getAddress(), info.getId());
438            portInfoMap.put(info.getId(), info);
439        }
440        mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
441        mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
442
443        if (mMhlController == null) {
444            mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo));
445            return;
446        } else {
447            HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
448            ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
449            for (HdmiPortInfo info : mhlPortInfo) {
450                if (info.isMhlSupported()) {
451                    mhlSupportedPorts.add(info.getId());
452                }
453            }
454
455            // Build HDMI port info list with CEC port info plus MHL supported flag.
456            ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
457            for (HdmiPortInfo info : cecPortInfo) {
458                if (mhlSupportedPorts.contains(info.getId())) {
459                    result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
460                            info.isCecSupported(), true, info.isArcSupported()));
461                } else {
462                    result.add(info);
463                }
464            }
465            mPortInfo = Collections.unmodifiableList(result);
466        }
467    }
468
469    /**
470     * Returns HDMI port information for the given port id.
471     *
472     * @param portId HDMI port id
473     * @return {@link HdmiPortInfo} for the given port
474     */
475    HdmiPortInfo getPortInfo(int portId) {
476        return mPortInfoMap.get(portId, null);
477    }
478
479    /**
480     * Returns the routing path (physical address) of the HDMI port for the given
481     * port id.
482     */
483    int portIdToPath(int portId) {
484        HdmiPortInfo portInfo = getPortInfo(portId);
485        if (portInfo == null) {
486            Slog.e(TAG, "Cannot find the port info: " + portId);
487            return Constants.INVALID_PHYSICAL_ADDRESS;
488        }
489        return portInfo.getAddress();
490    }
491
492    /**
493     * Returns the id of HDMI port located at the top of the hierarchy of
494     * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
495     * the port id to be returned is the ID associated with the port address
496     * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
497     */
498    int pathToPortId(int path) {
499        int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
500        return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
501    }
502
503    boolean isValidPortId(int portId) {
504        return getPortInfo(portId) != null;
505    }
506
507    /**
508     * Returns {@link Looper} for IO operation.
509     *
510     * <p>Declared as package-private.
511     */
512    Looper getIoLooper() {
513        return mIoThread.getLooper();
514    }
515
516    /**
517     * Returns {@link Looper} of main thread. Use this {@link Looper} instance
518     * for tasks that are running on main service thread.
519     *
520     * <p>Declared as package-private.
521     */
522    Looper getServiceLooper() {
523        return mHandler.getLooper();
524    }
525
526    /**
527     * Returns physical address of the device.
528     */
529    int getPhysicalAddress() {
530        return mCecController.getPhysicalAddress();
531    }
532
533    /**
534     * Returns vendor id of CEC service.
535     */
536    int getVendorId() {
537        return mCecController.getVendorId();
538    }
539
540    @ServiceThreadOnly
541    HdmiDeviceInfo getDeviceInfo(int logicalAddress) {
542        assertRunOnServiceThread();
543        HdmiCecLocalDeviceTv tv = tv();
544        if (tv == null) {
545            return null;
546        }
547        return tv.getDeviceInfo(logicalAddress);
548    }
549
550    /**
551     * Returns version of CEC.
552     */
553    int getCecVersion() {
554        return mCecController.getVersion();
555    }
556
557    /**
558     * Whether a device of the specified physical address is connected to ARC enabled port.
559     */
560    boolean isConnectedToArcPort(int physicalAddress) {
561        int portId = mPortIdMap.get(physicalAddress);
562        if (portId != Constants.INVALID_PORT_ID) {
563            return mPortInfoMap.get(portId).isArcSupported();
564        }
565        return false;
566    }
567
568    void runOnServiceThread(Runnable runnable) {
569        mHandler.post(runnable);
570    }
571
572    void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
573        mHandler.postAtFrontOfQueue(runnable);
574    }
575
576    private void assertRunOnServiceThread() {
577        if (Looper.myLooper() != mHandler.getLooper()) {
578            throw new IllegalStateException("Should run on service thread.");
579        }
580    }
581
582    /**
583     * Transmit a CEC command to CEC bus.
584     *
585     * @param command CEC command to send out
586     * @param callback interface used to the result of send command
587     */
588    @ServiceThreadOnly
589    void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
590        assertRunOnServiceThread();
591        if (mMessageValidator.isValid(command)) {
592            mCecController.sendCommand(command, callback);
593        } else {
594            Slog.e(TAG, "Invalid message type:" + command);
595            if (callback != null) {
596                callback.onSendCompleted(Constants.SEND_RESULT_FAILURE);
597            }
598        }
599    }
600
601    @ServiceThreadOnly
602    void sendCecCommand(HdmiCecMessage command) {
603        assertRunOnServiceThread();
604        sendCecCommand(command, null);
605    }
606
607    /**
608     * Send <Feature Abort> command on the given CEC message if possible.
609     * If the aborted message is invalid, then it wont send the message.
610     * @param command original command to be aborted
611     * @param reason reason of feature abort
612     */
613    @ServiceThreadOnly
614    void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) {
615        assertRunOnServiceThread();
616        mCecController.maySendFeatureAbortCommand(command, reason);
617    }
618
619    @ServiceThreadOnly
620    boolean handleCecCommand(HdmiCecMessage message) {
621        assertRunOnServiceThread();
622        if (!mMessageValidator.isValid(message)) {
623            return false;
624        }
625        return dispatchMessageToLocalDevice(message);
626    }
627
628    void setAudioReturnChannel(boolean enabled) {
629        mCecController.setAudioReturnChannel(enabled);
630    }
631
632    @ServiceThreadOnly
633    private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
634        assertRunOnServiceThread();
635        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
636            if (device.dispatchMessage(message)
637                    && message.getDestination() != Constants.ADDR_BROADCAST) {
638                return true;
639            }
640        }
641
642        if (message.getDestination() != Constants.ADDR_BROADCAST) {
643            Slog.w(TAG, "Unhandled cec command:" + message);
644        }
645        return false;
646    }
647
648    /**
649     * Called when a new hotplug event is issued.
650     *
651     * @param portNo hdmi port number where hot plug event issued.
652     * @param connected whether to be plugged in or not
653     */
654    @ServiceThreadOnly
655    void onHotplug(int portNo, boolean connected) {
656        assertRunOnServiceThread();
657        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
658            device.onHotplug(portNo, connected);
659        }
660        announceHotplugEvent(portNo, connected);
661    }
662
663    /**
664     * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
665     * devices.
666     *
667     * @param callback an interface used to get a list of all remote devices' address
668     * @param sourceAddress a logical address of source device where sends polling message
669     * @param pickStrategy strategy how to pick polling candidates
670     * @param retryCount the number of retry used to send polling message to remote devices
671     * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
672     */
673    @ServiceThreadOnly
674    void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
675            int retryCount) {
676        assertRunOnServiceThread();
677        mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
678                retryCount);
679    }
680
681    private int checkPollStrategy(int pickStrategy) {
682        int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
683        if (strategy == 0) {
684            throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
685        }
686        int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
687        if (iterationStrategy == 0) {
688            throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
689        }
690        return strategy | iterationStrategy;
691    }
692
693    List<HdmiCecLocalDevice> getAllLocalDevices() {
694        assertRunOnServiceThread();
695        return mCecController.getLocalDeviceList();
696    }
697
698    Object getServiceLock() {
699        return mLock;
700    }
701
702    void setAudioStatus(boolean mute, int volume) {
703        AudioManager audioManager = getAudioManager();
704        boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
705        if (mute) {
706            if (!muted) {
707                audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
708            }
709        } else {
710            if (muted) {
711                audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
712            }
713            // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
714            // volume change notification back to hdmi control service.
715            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
716                    AudioManager.FLAG_SHOW_UI |
717                    AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
718        }
719    }
720
721    void announceSystemAudioModeChange(boolean enabled) {
722        for (IHdmiSystemAudioModeChangeListener listener : mSystemAudioModeChangeListeners) {
723            invokeSystemAudioModeChange(listener, enabled);
724        }
725    }
726
727    private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
728        // TODO: find better name instead of model name.
729        String displayName = Build.MODEL;
730        return new HdmiDeviceInfo(logicalAddress,
731                getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
732                getVendorId(), displayName);
733    }
734
735    // Record class that monitors the event of the caller of being killed. Used to clean up
736    // the listener list and record list accordingly.
737    private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
738        private final IHdmiHotplugEventListener mListener;
739
740        public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
741            mListener = listener;
742        }
743
744        @Override
745        public void binderDied() {
746            synchronized (mLock) {
747                mHotplugEventListenerRecords.remove(this);
748                mHotplugEventListeners.remove(mListener);
749            }
750        }
751    }
752
753    private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
754        private final IHdmiDeviceEventListener mListener;
755
756        public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
757            mListener = listener;
758        }
759
760        @Override
761        public void binderDied() {
762            synchronized (mLock) {
763                mDeviceEventListenerRecords.remove(this);
764                mDeviceEventListeners.remove(mListener);
765            }
766        }
767    }
768
769    private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
770        private final IHdmiSystemAudioModeChangeListener mListener;
771
772        public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
773            mListener = listener;
774        }
775
776        @Override
777        public void binderDied() {
778            synchronized (mLock) {
779                mSystemAudioModeChangeListenerRecords.remove(this);
780                mSystemAudioModeChangeListeners.remove(mListener);
781            }
782        }
783    }
784
785    class VendorCommandListenerRecord implements IBinder.DeathRecipient {
786        private final IHdmiVendorCommandListener mListener;
787        private final int mDeviceType;
788
789        public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
790            mListener = listener;
791            mDeviceType = deviceType;
792        }
793
794        @Override
795        public void binderDied() {
796            synchronized (mLock) {
797                mVendorCommandListenerRecords.remove(this);
798            }
799        }
800    }
801
802    private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
803        @Override
804        public void binderDied() {
805            synchronized (mLock) {
806                mRecordListener = null;
807            }
808        }
809    }
810
811    private void enforceAccessPermission() {
812        getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
813    }
814
815    private final class BinderService extends IHdmiControlService.Stub {
816        @Override
817        public int[] getSupportedTypes() {
818            enforceAccessPermission();
819            // mLocalDevices is an unmodifiable list - no lock necesary.
820            int[] localDevices = new int[mLocalDevices.size()];
821            for (int i = 0; i < localDevices.length; ++i) {
822                localDevices[i] = mLocalDevices.get(i);
823            }
824            return localDevices;
825        }
826
827        @Override
828        public HdmiDeviceInfo getActiveSource() {
829            HdmiCecLocalDeviceTv tv = tv();
830            if (tv == null) {
831                Slog.w(TAG, "Local tv device not available");
832                return null;
833            }
834            ActiveSource activeSource = tv.getActiveSource();
835            if (activeSource.isValid()) {
836                return new HdmiDeviceInfo(activeSource.logicalAddress,
837                        activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID,
838                        HdmiDeviceInfo.DEVICE_INACTIVE, 0, "");
839            }
840            int activePath = tv.getActivePath();
841            if (activePath != HdmiDeviceInfo.PATH_INVALID) {
842                return new HdmiDeviceInfo(activePath, tv.getActivePortId());
843            }
844            return null;
845        }
846
847        @Override
848        public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) {
849            enforceAccessPermission();
850            runOnServiceThread(new Runnable() {
851                @Override
852                public void run() {
853                    if (callback == null) {
854                        Slog.e(TAG, "Callback cannot be null");
855                        return;
856                    }
857                    HdmiCecLocalDeviceTv tv = tv();
858                    if (tv == null) {
859                        Slog.w(TAG, "Local tv device not available");
860                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
861                        return;
862                    }
863                    tv.deviceSelect(logicalAddress, callback);
864                }
865            });
866        }
867
868        @Override
869        public void portSelect(final int portId, final IHdmiControlCallback callback) {
870            enforceAccessPermission();
871            runOnServiceThread(new Runnable() {
872                @Override
873                public void run() {
874                    if (callback == null) {
875                        Slog.e(TAG, "Callback cannot be null");
876                        return;
877                    }
878                    HdmiCecLocalDeviceTv tv = tv();
879                    if (tv == null) {
880                        Slog.w(TAG, "Local tv device not available");
881                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
882                        return;
883                    }
884                    tv.doManualPortSwitching(portId, callback);
885                }
886            });
887        }
888
889        @Override
890        public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
891            enforceAccessPermission();
892            runOnServiceThread(new Runnable() {
893                @Override
894                public void run() {
895                    HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
896                    if (localDevice == null) {
897                        Slog.w(TAG, "Local device not available");
898                        return;
899                    }
900                    localDevice.sendKeyEvent(keyCode, isPressed);
901                }
902            });
903        }
904
905        @Override
906        public void oneTouchPlay(final IHdmiControlCallback callback) {
907            enforceAccessPermission();
908            runOnServiceThread(new Runnable() {
909                @Override
910                public void run() {
911                    HdmiControlService.this.oneTouchPlay(callback);
912                }
913            });
914        }
915
916        @Override
917        public void queryDisplayStatus(final IHdmiControlCallback callback) {
918            enforceAccessPermission();
919            runOnServiceThread(new Runnable() {
920                @Override
921                public void run() {
922                    HdmiControlService.this.queryDisplayStatus(callback);
923                }
924            });
925        }
926
927        @Override
928        public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
929            enforceAccessPermission();
930            runOnServiceThread(new Runnable() {
931                @Override
932                public void run() {
933                    HdmiControlService.this.addHotplugEventListener(listener);
934                }
935            });
936        }
937
938        @Override
939        public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
940            enforceAccessPermission();
941            runOnServiceThread(new Runnable() {
942                @Override
943                public void run() {
944                    HdmiControlService.this.removeHotplugEventListener(listener);
945                }
946            });
947        }
948
949        @Override
950        public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
951            enforceAccessPermission();
952            runOnServiceThread(new Runnable() {
953                @Override
954                public void run() {
955                    HdmiControlService.this.addDeviceEventListener(listener);
956                }
957            });
958        }
959
960        @Override
961        public List<HdmiPortInfo> getPortInfo() {
962            enforceAccessPermission();
963            return mPortInfo;
964        }
965
966        @Override
967        public boolean canChangeSystemAudioMode() {
968            enforceAccessPermission();
969            HdmiCecLocalDeviceTv tv = tv();
970            if (tv == null) {
971                return false;
972            }
973            return tv.hasSystemAudioDevice();
974        }
975
976        @Override
977        public boolean getSystemAudioMode() {
978            enforceAccessPermission();
979            HdmiCecLocalDeviceTv tv = tv();
980            if (tv == null) {
981                return false;
982            }
983            return tv.isSystemAudioActivated();
984        }
985
986        @Override
987        public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
988            enforceAccessPermission();
989            runOnServiceThread(new Runnable() {
990                @Override
991                public void run() {
992                    HdmiCecLocalDeviceTv tv = tv();
993                    if (tv == null) {
994                        Slog.w(TAG, "Local tv device not available");
995                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
996                        return;
997                    }
998                    tv.changeSystemAudioMode(enabled, callback);
999                }
1000            });
1001        }
1002
1003        @Override
1004        public void addSystemAudioModeChangeListener(
1005                final IHdmiSystemAudioModeChangeListener listener) {
1006            enforceAccessPermission();
1007            HdmiControlService.this.addSystemAudioModeChangeListner(listener);
1008        }
1009
1010        @Override
1011        public void removeSystemAudioModeChangeListener(
1012                final IHdmiSystemAudioModeChangeListener listener) {
1013            enforceAccessPermission();
1014            HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
1015        }
1016
1017        @Override
1018        public void setInputChangeListener(final IHdmiInputChangeListener listener) {
1019            enforceAccessPermission();
1020            HdmiControlService.this.setInputChangeListener(listener);
1021        }
1022
1023        @Override
1024        public List<HdmiDeviceInfo> getInputDevices() {
1025            enforceAccessPermission();
1026            // No need to hold the lock for obtaining TV device as the local device instance
1027            // is preserved while the HDMI control is enabled.
1028            HdmiCecLocalDeviceTv tv = tv();
1029            if (tv == null) {
1030                return Collections.emptyList();
1031            }
1032            return tv.getSafeExternalInputs();
1033        }
1034
1035        @Override
1036        public void setSystemAudioVolume(final int oldIndex, final int newIndex,
1037                final int maxIndex) {
1038            enforceAccessPermission();
1039            runOnServiceThread(new Runnable() {
1040                @Override
1041                public void run() {
1042                    HdmiCecLocalDeviceTv tv = tv();
1043                    if (tv == null) {
1044                        Slog.w(TAG, "Local tv device not available");
1045                        return;
1046                    }
1047                    tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
1048                }
1049            });
1050        }
1051
1052        @Override
1053        public void setSystemAudioMute(final boolean mute) {
1054            enforceAccessPermission();
1055            runOnServiceThread(new Runnable() {
1056                @Override
1057                public void run() {
1058                    HdmiCecLocalDeviceTv tv = tv();
1059                    if (tv == null) {
1060                        Slog.w(TAG, "Local tv device not available");
1061                        return;
1062                    }
1063                    tv.changeMute(mute);
1064                }
1065            });
1066        }
1067
1068        @Override
1069        public void setArcMode(final boolean enabled) {
1070            enforceAccessPermission();
1071            runOnServiceThread(new Runnable() {
1072                @Override
1073                public void run() {
1074                    HdmiCecLocalDeviceTv tv = tv();
1075                    if (tv == null) {
1076                        Slog.w(TAG, "Local tv device not available to change arc mode.");
1077                        return;
1078                    }
1079                }
1080            });
1081        }
1082
1083        @Override
1084        public void setProhibitMode(final boolean enabled) {
1085            enforceAccessPermission();
1086            if (!isTvDevice()) {
1087                return;
1088            }
1089            HdmiControlService.this.setProhibitMode(enabled);
1090        }
1091
1092        @Override
1093        public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
1094                final int deviceType) {
1095            enforceAccessPermission();
1096            runOnServiceThread(new Runnable() {
1097                @Override
1098                public void run() {
1099                    HdmiControlService.this.addVendorCommandListener(listener, deviceType);
1100                }
1101            });
1102        }
1103
1104        @Override
1105        public void sendVendorCommand(final int deviceType, final int targetAddress,
1106                final byte[] params, final boolean hasVendorId) {
1107            enforceAccessPermission();
1108            runOnServiceThread(new Runnable() {
1109                @Override
1110                public void run() {
1111                    HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1112                    if (device == null) {
1113                        Slog.w(TAG, "Local device not available");
1114                        return;
1115                    }
1116                    if (hasVendorId) {
1117                        sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1118                                device.getDeviceInfo().getLogicalAddress(), targetAddress,
1119                                getVendorId(), params));
1120                    } else {
1121                        sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1122                                device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1123                    }
1124                }
1125            });
1126        }
1127
1128        @Override
1129        public void setHdmiRecordListener(IHdmiRecordListener listener) {
1130            HdmiControlService.this.setHdmiRecordListener(listener);
1131        }
1132
1133        @Override
1134        public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
1135            runOnServiceThread(new Runnable() {
1136                @Override
1137                public void run() {
1138                    if (!isTvDevice()) {
1139                        Slog.w(TAG, "No TV is available.");
1140                        return;
1141                    }
1142                    tv().startOneTouchRecord(recorderAddress, recordSource);
1143                }
1144            });
1145        }
1146
1147        @Override
1148        public void stopOneTouchRecord(final int recorderAddress) {
1149            runOnServiceThread(new Runnable() {
1150                @Override
1151                public void run() {
1152                    if (!isTvDevice()) {
1153                        Slog.w(TAG, "No TV is available.");
1154                        return;
1155                    }
1156                    tv().stopOneTouchRecord(recorderAddress);
1157                }
1158            });
1159        }
1160
1161        @Override
1162        public void startTimerRecording(final int recorderAddress, final int sourceType,
1163                final byte[] recordSource) {
1164            runOnServiceThread(new Runnable() {
1165                @Override
1166                public void run() {
1167                    if (!isTvDevice()) {
1168                        Slog.w(TAG, "No TV is available.");
1169                        return;
1170                    }
1171                    tv().startTimerRecording(recorderAddress, sourceType, recordSource);
1172                }
1173            });
1174        }
1175
1176        @Override
1177        public void clearTimerRecording(final int recorderAddress, final int sourceType,
1178                final byte[] recordSource) {
1179            runOnServiceThread(new Runnable() {
1180                @Override
1181                public void run() {
1182                    if (!isTvDevice()) {
1183                        Slog.w(TAG, "No TV is available.");
1184                        return;
1185                    }
1186                    tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
1187                }
1188            });
1189        }
1190    }
1191
1192    @ServiceThreadOnly
1193    private void oneTouchPlay(final IHdmiControlCallback callback) {
1194        assertRunOnServiceThread();
1195        HdmiCecLocalDevicePlayback source = playback();
1196        if (source == null) {
1197            Slog.w(TAG, "Local playback device not available");
1198            invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1199            return;
1200        }
1201        source.oneTouchPlay(callback);
1202    }
1203
1204    @ServiceThreadOnly
1205    private void queryDisplayStatus(final IHdmiControlCallback callback) {
1206        assertRunOnServiceThread();
1207        HdmiCecLocalDevicePlayback source = playback();
1208        if (source == null) {
1209            Slog.w(TAG, "Local playback device not available");
1210            invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1211            return;
1212        }
1213        source.queryDisplayStatus(callback);
1214    }
1215
1216    private void addHotplugEventListener(IHdmiHotplugEventListener listener) {
1217        HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
1218        try {
1219            listener.asBinder().linkToDeath(record, 0);
1220        } catch (RemoteException e) {
1221            Slog.w(TAG, "Listener already died");
1222            return;
1223        }
1224        synchronized (mLock) {
1225            mHotplugEventListenerRecords.add(record);
1226            mHotplugEventListeners.add(listener);
1227        }
1228    }
1229
1230    private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
1231        synchronized (mLock) {
1232            for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1233                if (record.mListener.asBinder() == listener.asBinder()) {
1234                    listener.asBinder().unlinkToDeath(record, 0);
1235                    mHotplugEventListenerRecords.remove(record);
1236                    break;
1237                }
1238            }
1239            mHotplugEventListeners.remove(listener);
1240        }
1241    }
1242
1243    private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
1244        DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
1245        try {
1246            listener.asBinder().linkToDeath(record, 0);
1247        } catch (RemoteException e) {
1248            Slog.w(TAG, "Listener already died");
1249            return;
1250        }
1251        synchronized (mLock) {
1252            mDeviceEventListeners.add(listener);
1253            mDeviceEventListenerRecords.add(record);
1254        }
1255    }
1256
1257    void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
1258        synchronized (mLock) {
1259            for (IHdmiDeviceEventListener listener : mDeviceEventListeners) {
1260                try {
1261                    listener.onStatusChanged(device, status);
1262                } catch (RemoteException e) {
1263                    Slog.e(TAG, "Failed to report device event:" + e);
1264                }
1265            }
1266        }
1267    }
1268
1269    private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
1270        SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
1271                listener);
1272        try {
1273            listener.asBinder().linkToDeath(record, 0);
1274        } catch (RemoteException e) {
1275            Slog.w(TAG, "Listener already died");
1276            return;
1277        }
1278        synchronized (mLock) {
1279            mSystemAudioModeChangeListeners.add(listener);
1280            mSystemAudioModeChangeListenerRecords.add(record);
1281        }
1282    }
1283
1284    private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
1285        synchronized (mLock) {
1286            for (SystemAudioModeChangeListenerRecord record :
1287                    mSystemAudioModeChangeListenerRecords) {
1288                if (record.mListener.asBinder() == listener) {
1289                    listener.asBinder().unlinkToDeath(record, 0);
1290                    mSystemAudioModeChangeListenerRecords.remove(record);
1291                    break;
1292                }
1293            }
1294            mSystemAudioModeChangeListeners.remove(listener);
1295        }
1296    }
1297
1298    private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
1299        @Override
1300        public void binderDied() {
1301            synchronized (mLock) {
1302                mInputChangeListener = null;
1303            }
1304        }
1305    }
1306
1307    private void setInputChangeListener(IHdmiInputChangeListener listener) {
1308        synchronized (mLock) {
1309            mInputChangeListenerRecord = new InputChangeListenerRecord();
1310            try {
1311                listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
1312            } catch (RemoteException e) {
1313                Slog.w(TAG, "Listener already died");
1314                return;
1315            }
1316            mInputChangeListener = listener;
1317        }
1318    }
1319
1320    void invokeInputChangeListener(HdmiDeviceInfo info) {
1321        synchronized (mLock) {
1322            if (mInputChangeListener != null) {
1323                try {
1324                    mInputChangeListener.onChanged(info);
1325                } catch (RemoteException e) {
1326                    Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
1327                }
1328            }
1329        }
1330    }
1331
1332    private void setHdmiRecordListener(IHdmiRecordListener listener) {
1333        synchronized (mLock) {
1334            mRecordListenerRecord = new HdmiRecordListenerRecord();
1335            try {
1336                listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
1337            } catch (RemoteException e) {
1338                Slog.w(TAG, "Listener already died.", e);
1339            }
1340            mRecordListener = listener;
1341        }
1342    }
1343
1344    byte[] invokeRecordRequestListener(int recorderAddress) {
1345        synchronized (mLock) {
1346            if (mRecordListener != null) {
1347                try {
1348                    return mRecordListener.getOneTouchRecordSource(recorderAddress);
1349                } catch (RemoteException e) {
1350                    Slog.w(TAG, "Failed to start record.", e);
1351                }
1352            }
1353            return EmptyArray.BYTE;
1354        }
1355    }
1356
1357    void invokeOneTouchRecordResult(int result) {
1358        synchronized (mLock) {
1359            if (mRecordListener != null) {
1360                try {
1361                    mRecordListener.onOneTouchRecordResult(result);
1362                } catch (RemoteException e) {
1363                    Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
1364                }
1365            }
1366        }
1367    }
1368
1369    void invokeTimerRecordingResult(int result) {
1370        synchronized (mLock) {
1371            if (mRecordListener != null) {
1372                try {
1373                    mRecordListener.onTimerRecordingResult(result);
1374                } catch (RemoteException e) {
1375                    Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
1376                }
1377            }
1378        }
1379    }
1380
1381    void invokeClearTimerRecordingResult(int result) {
1382        synchronized (mLock) {
1383            if (mRecordListener != null) {
1384                try {
1385                    mRecordListener.onClearTimerRecordingResult(result);
1386                } catch (RemoteException e) {
1387                    Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
1388                }
1389            }
1390        }
1391    }
1392
1393    private void invokeCallback(IHdmiControlCallback callback, int result) {
1394        try {
1395            callback.onComplete(result);
1396        } catch (RemoteException e) {
1397            Slog.e(TAG, "Invoking callback failed:" + e);
1398        }
1399    }
1400
1401    private void invokeSystemAudioModeChange(IHdmiSystemAudioModeChangeListener listener,
1402            boolean enabled) {
1403        try {
1404            listener.onStatusChanged(enabled);
1405        } catch (RemoteException e) {
1406            Slog.e(TAG, "Invoking callback failed:" + e);
1407        }
1408    }
1409
1410    private void announceHotplugEvent(int portId, boolean connected) {
1411        HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
1412        synchronized (mLock) {
1413            for (IHdmiHotplugEventListener listener : mHotplugEventListeners) {
1414                invokeHotplugEventListenerLocked(listener, event);
1415            }
1416        }
1417    }
1418
1419    private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
1420            HdmiHotplugEvent event) {
1421        try {
1422            listener.onReceived(event);
1423        } catch (RemoteException e) {
1424            Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
1425        }
1426    }
1427
1428    private HdmiCecLocalDeviceTv tv() {
1429        return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
1430    }
1431
1432    boolean isTvDevice() {
1433        return tv() != null;
1434    }
1435
1436    private HdmiCecLocalDevicePlayback playback() {
1437        return (HdmiCecLocalDevicePlayback)
1438                mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
1439    }
1440
1441    AudioManager getAudioManager() {
1442        return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
1443    }
1444
1445    boolean isControlEnabled() {
1446        synchronized (mLock) {
1447            return mHdmiControlEnabled;
1448        }
1449    }
1450
1451    int getPowerStatus() {
1452        return mPowerStatus;
1453    }
1454
1455    boolean isPowerOnOrTransient() {
1456        return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
1457                || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1458    }
1459
1460    boolean isPowerStandbyOrTransient() {
1461        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
1462                || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1463    }
1464
1465    boolean isPowerStandby() {
1466        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
1467    }
1468
1469    @ServiceThreadOnly
1470    void wakeUp() {
1471        assertRunOnServiceThread();
1472        mWakeUpMessageReceived = true;
1473        PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1474        pm.wakeUp(SystemClock.uptimeMillis());
1475        // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
1476        // the intent, the sequence will continue at onWakeUp().
1477    }
1478
1479    @ServiceThreadOnly
1480    void standby() {
1481        assertRunOnServiceThread();
1482        mStandbyMessageReceived = true;
1483        PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1484        pm.goToSleep(SystemClock.uptimeMillis());
1485        // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
1486        // the intent, the sequence will continue at onStandby().
1487    }
1488
1489    void nap() {
1490        PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1491        pm.nap(SystemClock.uptimeMillis());
1492    }
1493
1494    @ServiceThreadOnly
1495    private void onWakeUp() {
1496        assertRunOnServiceThread();
1497        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1498        if (mCecController != null) {
1499            if (mHdmiControlEnabled) {
1500                int startReason = INITIATED_BY_SCREEN_ON;
1501                if (mWakeUpMessageReceived) {
1502                    startReason = INITIATED_BY_WAKE_UP_MESSAGE;
1503                }
1504                initializeCec(startReason);
1505            }
1506        } else {
1507            Slog.i(TAG, "Device does not support HDMI-CEC.");
1508        }
1509        // TODO: Initialize MHL local devices.
1510    }
1511
1512    @ServiceThreadOnly
1513    private void onStandby() {
1514        assertRunOnServiceThread();
1515        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1516
1517        final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
1518        disableDevices(new PendingActionClearedCallback() {
1519            @Override
1520            public void onCleared(HdmiCecLocalDevice device) {
1521                Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
1522                devices.remove(device);
1523                if (devices.isEmpty()) {
1524                    onStandbyCompleted();
1525                    // We will not clear local devices here, since some OEM/SOC will keep passing
1526                    // the received packets until the application processor enters to the sleep
1527                    // actually.
1528                }
1529            }
1530        });
1531    }
1532
1533    private void disableDevices(PendingActionClearedCallback callback) {
1534        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1535            device.disableDevice(mStandbyMessageReceived, callback);
1536        }
1537        if (isTvDevice()) {
1538            unregisterSettingsObserver();
1539        }
1540    }
1541
1542    @ServiceThreadOnly
1543    private void clearLocalDevices() {
1544        assertRunOnServiceThread();
1545        if (mCecController == null) {
1546            return;
1547        }
1548        mCecController.clearLogicalAddress();
1549        mCecController.clearLocalDevices();
1550    }
1551
1552    @ServiceThreadOnly
1553    private void onStandbyCompleted() {
1554        assertRunOnServiceThread();
1555        Slog.v(TAG, "onStandbyCompleted");
1556
1557        if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
1558            return;
1559        }
1560        mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
1561        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1562            device.onStandby(mStandbyMessageReceived);
1563        }
1564        mStandbyMessageReceived = false;
1565        mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, DISABLED);
1566    }
1567
1568    private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
1569        VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
1570        try {
1571            listener.asBinder().linkToDeath(record, 0);
1572        } catch (RemoteException e) {
1573            Slog.w(TAG, "Listener already died");
1574            return;
1575        }
1576        synchronized (mLock) {
1577            mVendorCommandListenerRecords.add(record);
1578        }
1579    }
1580
1581    void invokeVendorCommandListeners(int deviceType, int srcAddress, byte[] params,
1582            boolean hasVendorId) {
1583        synchronized (mLock) {
1584            for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
1585                if (record.mDeviceType != deviceType) {
1586                    continue;
1587                }
1588                try {
1589                    record.mListener.onReceived(srcAddress, params, hasVendorId);
1590                } catch (RemoteException e) {
1591                    Slog.e(TAG, "Failed to notify vendor command reception", e);
1592                }
1593            }
1594        }
1595    }
1596
1597    boolean isProhibitMode() {
1598        synchronized (mLock) {
1599            return mProhibitMode;
1600        }
1601    }
1602
1603    void setProhibitMode(boolean enabled) {
1604        synchronized (mLock) {
1605            mProhibitMode = enabled;
1606        }
1607    }
1608
1609    @ServiceThreadOnly
1610    void setOption(int key, int value) {
1611        assertRunOnServiceThread();
1612        mCecController.setOption(key, value);
1613    }
1614
1615    @ServiceThreadOnly
1616    void setControlEnabled(boolean enabled) {
1617        assertRunOnServiceThread();
1618
1619        int value = toInt(enabled);
1620        mCecController.setOption(OPTION_CEC_ENABLE, value);
1621        if (mMhlController != null) {
1622            mMhlController.setOption(OPTION_MHL_ENABLE, value);
1623        }
1624
1625        synchronized (mLock) {
1626            mHdmiControlEnabled = enabled;
1627        }
1628
1629        if (enabled) {
1630            initializeCec(INITIATED_BY_ENABLE_CEC);
1631        } else {
1632            disableDevices(new PendingActionClearedCallback() {
1633                @Override
1634                public void onCleared(HdmiCecLocalDevice device) {
1635                    assertRunOnServiceThread();
1636                    clearLocalDevices();
1637                }
1638            });
1639        }
1640    }
1641}
1642