HdmiControlService.java revision a858d221ff86c497e745222ea15bab141e337636
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 android.annotation.Nullable;
20import android.content.Context;
21import android.hardware.hdmi.HdmiCec;
22import android.hardware.hdmi.HdmiCecDeviceInfo;
23import android.hardware.hdmi.HdmiCecMessage;
24import android.hardware.hdmi.HdmiHotplugEvent;
25import android.hardware.hdmi.HdmiPortInfo;
26import android.hardware.hdmi.IHdmiControlCallback;
27import android.hardware.hdmi.IHdmiControlService;
28import android.hardware.hdmi.IHdmiDeviceEventListener;
29import android.hardware.hdmi.IHdmiHotplugEventListener;
30import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
31import android.media.AudioManager;
32import android.os.Build;
33import android.os.Handler;
34import android.os.HandlerThread;
35import android.os.IBinder;
36import android.os.Looper;
37import android.os.RemoteException;
38import android.util.Slog;
39import android.util.SparseArray;
40import android.util.SparseIntArray;
41
42import com.android.internal.annotations.GuardedBy;
43import com.android.server.SystemService;
44import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
45import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
46
47import java.util.ArrayList;
48import java.util.Collections;
49import java.util.List;
50
51/**
52 * Provides a service for sending and processing HDMI control messages,
53 * HDMI-CEC and MHL control command, and providing the information on both standard.
54 */
55public final class HdmiControlService extends SystemService {
56    private static final String TAG = "HdmiControlService";
57
58    // TODO: Rename the permission to HDMI_CONTROL.
59    private static final String PERMISSION = "android.permission.HDMI_CEC";
60
61    /**
62     * Interface to report send result.
63     */
64    interface SendMessageCallback {
65        /**
66         * Called when {@link HdmiControlService#sendCecCommand} is completed.
67         *
68         * @param error result of send request.
69         * @see {@link #SEND_RESULT_SUCCESS}
70         * @see {@link #SEND_RESULT_NAK}
71         * @see {@link #SEND_RESULT_FAILURE}
72         */
73        void onSendCompleted(int error);
74    }
75
76    /**
77     * Interface to get a list of available logical devices.
78     */
79    interface DevicePollingCallback {
80        /**
81         * Called when device polling is finished.
82         *
83         * @param ackedAddress a list of logical addresses of available devices
84         */
85        void onPollingFinished(List<Integer> ackedAddress);
86    }
87
88    // A thread to handle synchronous IO of CEC and MHL control service.
89    // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
90    // and sparse call it shares a thread to handle IO operations.
91    private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
92
93    // Used to synchronize the access to the service.
94    private final Object mLock = new Object();
95
96    // Type of logical devices hosted in the system. Stored in the unmodifiable list.
97    private final List<Integer> mLocalDevices;
98
99    // List of listeners registered by callers that want to get notified of
100    // hotplug events.
101    @GuardedBy("mLock")
102    private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>();
103
104    // List of records for hotplug event listener to handle the the caller killed in action.
105    @GuardedBy("mLock")
106    private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
107            new ArrayList<>();
108
109    // List of listeners registered by callers that want to get notified of
110    // device status events.
111    @GuardedBy("mLock")
112    private final ArrayList<IHdmiDeviceEventListener> mDeviceEventListeners = new ArrayList<>();
113
114    // List of records for device event listener to handle the the caller killed in action.
115    @GuardedBy("mLock")
116    private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
117            new ArrayList<>();
118
119    // List of listeners registered by callers that want to get notified of
120    // system audio mode changes.
121    private final ArrayList<IHdmiSystemAudioModeChangeListener>
122            mSystemAudioModeChangeListeners = new ArrayList<>();
123    // List of records for system audio mode change to handle the the caller killed in action.
124    private final ArrayList<SystemAudioModeChangeListenerRecord>
125            mSystemAudioModeChangeListenerRecords = new ArrayList<>();
126
127    // Handler used to run a task in service thread.
128    private final Handler mHandler = new Handler();
129
130    @Nullable
131    private HdmiCecController mCecController;
132
133    @Nullable
134    private HdmiMhlController mMhlController;
135
136    // HDMI port information. Stored in the unmodifiable list to keep the static information
137    // from being modified.
138    private List<HdmiPortInfo> mPortInfo;
139
140    public HdmiControlService(Context context) {
141        super(context);
142        mLocalDevices = HdmiUtils.asImmutableList(getContext().getResources().getIntArray(
143                com.android.internal.R.array.config_hdmiCecLogicalDeviceType));
144    }
145
146    @Override
147    public void onStart() {
148        mIoThread.start();
149        mCecController = HdmiCecController.create(this);
150
151        if (mCecController != null) {
152            initializeLocalDevices(mLocalDevices);
153        } else {
154            Slog.i(TAG, "Device does not support HDMI-CEC.");
155        }
156
157        mMhlController = HdmiMhlController.create(this);
158        if (mMhlController == null) {
159            Slog.i(TAG, "Device does not support MHL-control.");
160        }
161        mPortInfo = initPortInfo();
162        publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
163
164        // TODO: Read the preference for SystemAudioMode and initialize mSystemAudioMode and
165        // start to monitor the preference value and invoke SystemAudioActionFromTv if needed.
166    }
167
168    @ServiceThreadOnly
169    private void initializeLocalDevices(final List<Integer> deviceTypes) {
170        assertRunOnServiceThread();
171        // A container for [Logical Address, Local device info].
172        final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>();
173        final SparseIntArray finished = new SparseIntArray();
174        mCecController.clearLogicalAddress();
175        for (int type : deviceTypes) {
176            final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type);
177            localDevice.init();
178            mCecController.allocateLogicalAddress(type,
179                    localDevice.getPreferredAddress(), new AllocateAddressCallback() {
180                @Override
181                public void onAllocated(int deviceType, int logicalAddress) {
182                    if (logicalAddress == HdmiCec.ADDR_UNREGISTERED) {
183                        Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
184                    } else {
185                        HdmiCecDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType);
186                        localDevice.setDeviceInfo(deviceInfo);
187                        mCecController.addLocalDevice(deviceType, localDevice);
188                        mCecController.addLogicalAddress(logicalAddress);
189                        devices.append(logicalAddress, localDevice);
190                    }
191                    finished.append(deviceType, logicalAddress);
192
193                    // Address allocation completed for all devices. Notify each device.
194                    if (deviceTypes.size() == finished.size()) {
195                        notifyAddressAllocated(devices);
196                    }
197                }
198            });
199        }
200    }
201
202    @ServiceThreadOnly
203    private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices) {
204        assertRunOnServiceThread();
205        for (int i = 0; i < devices.size(); ++i) {
206            int address = devices.keyAt(i);
207            HdmiCecLocalDevice device = devices.valueAt(i);
208            device.handleAddressAllocated(address);
209        }
210    }
211
212    // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
213    // keep them in one place.
214    @ServiceThreadOnly
215    private List<HdmiPortInfo> initPortInfo() {
216        assertRunOnServiceThread();
217        HdmiPortInfo[] cecPortInfo = null;
218
219        // CEC HAL provides majority of the info while MHL does only MHL support flag for
220        // each port. Return empty array if CEC HAL didn't provide the info.
221        if (mCecController != null) {
222            cecPortInfo = mCecController.getPortInfos();
223        }
224        if (cecPortInfo == null) {
225            return Collections.emptyList();
226        }
227
228        HdmiPortInfo[] mhlPortInfo = new HdmiPortInfo[0];
229        if (mMhlController != null) {
230            // TODO: Implement plumbing logic to get MHL port information.
231            // mhlPortInfo = mMhlController.getPortInfos();
232        }
233
234        // Use the id (port number) to find the matched info between CEC and MHL to combine them
235        // into one. Leave the field `mhlSupported` to false if matched MHL entry is not found.
236        ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
237        for (int i = 0; i < cecPortInfo.length; ++i) {
238            HdmiPortInfo cec = cecPortInfo[i];
239            int id = cec.getId();
240            boolean mhlInfoFound = false;
241            for (HdmiPortInfo mhl : mhlPortInfo) {
242                if (id == mhl.getId()) {
243                    result.add(new HdmiPortInfo(id, cec.getType(), cec.getAddress(),
244                            cec.isCecSupported(), mhl.isMhlSupported(), cec.isArcSupported()));
245                    mhlInfoFound = true;
246                    break;
247                }
248            }
249            if (!mhlInfoFound) {
250                result.add(cec);
251            }
252        }
253
254        return Collections.unmodifiableList(result);
255    }
256
257    /**
258     * Returns HDMI port information for the given port id.
259     *
260     * @param portId HDMI port id
261     * @return {@link HdmiPortInfo} for the given port
262     */
263    HdmiPortInfo getPortInfo(int portId) {
264        // mPortInfo is an unmodifiable list and the only reference to its inner list.
265        // No lock is necessary.
266        for (HdmiPortInfo info : mPortInfo) {
267            if (portId == info.getId()) {
268                return info;
269            }
270        }
271        return null;
272    }
273
274    /**
275     * Returns the routing path (physical address) of the HDMI port for the given
276     * port id.
277     */
278    int portIdToPath(int portId) {
279        HdmiPortInfo portInfo = getPortInfo(portId);
280        if (portInfo == null) {
281            Slog.e(TAG, "Cannot find the port info: " + portId);
282            return HdmiConstants.INVALID_PHYSICAL_ADDRESS;
283        }
284        return portInfo.getAddress();
285    }
286
287    /**
288     * Returns the id of HDMI port located at the top of the hierarchy of
289     * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
290     * the port id to be returned is the ID associated with the port address
291     * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
292     */
293    int pathToPortId(int path) {
294        int portAddress = path & HdmiConstants.ROUTING_PATH_TOP_MASK;
295        for (HdmiPortInfo info : mPortInfo) {
296            if (portAddress == info.getAddress()) {
297                return info.getId();
298            }
299        }
300        return HdmiConstants.INVALID_PORT_ID;
301    }
302
303    /**
304     * Returns {@link Looper} for IO operation.
305     *
306     * <p>Declared as package-private.
307     */
308    Looper getIoLooper() {
309        return mIoThread.getLooper();
310    }
311
312    /**
313     * Returns {@link Looper} of main thread. Use this {@link Looper} instance
314     * for tasks that are running on main service thread.
315     *
316     * <p>Declared as package-private.
317     */
318    Looper getServiceLooper() {
319        return mHandler.getLooper();
320    }
321
322    /**
323     * Returns physical address of the device.
324     */
325    int getPhysicalAddress() {
326        return mCecController.getPhysicalAddress();
327    }
328
329    /**
330     * Returns vendor id of CEC service.
331     */
332    int getVendorId() {
333        return mCecController.getVendorId();
334    }
335
336    @ServiceThreadOnly
337    HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) {
338        assertRunOnServiceThread();
339        HdmiCecLocalDeviceTv tv = tv();
340        if (tv == null) {
341            return null;
342        }
343        return tv.getDeviceInfo(logicalAddress);
344    }
345
346    /**
347     * Returns version of CEC.
348     */
349    int getCecVersion() {
350        return mCecController.getVersion();
351    }
352
353    /**
354     * Whether a device of the specified physical address is connected to ARC enabled port.
355     */
356    boolean isConnectedToArcPort(int physicalAddress) {
357        for (HdmiPortInfo portInfo : mPortInfo) {
358            if (hasSameTopPort(portInfo.getAddress(), physicalAddress)
359                    && portInfo.isArcSupported()) {
360                return true;
361            }
362        }
363        return false;
364    }
365
366    void runOnServiceThread(Runnable runnable) {
367        mHandler.post(runnable);
368    }
369
370    void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
371        mHandler.postAtFrontOfQueue(runnable);
372    }
373
374    private void assertRunOnServiceThread() {
375        if (Looper.myLooper() != mHandler.getLooper()) {
376            throw new IllegalStateException("Should run on service thread.");
377        }
378    }
379
380    /**
381     * Transmit a CEC command to CEC bus.
382     *
383     * @param command CEC command to send out
384     * @param callback interface used to the result of send command
385     */
386    @ServiceThreadOnly
387    void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
388        assertRunOnServiceThread();
389        mCecController.sendCommand(command, callback);
390    }
391
392    @ServiceThreadOnly
393    void sendCecCommand(HdmiCecMessage command) {
394        assertRunOnServiceThread();
395        mCecController.sendCommand(command, null);
396    }
397
398    @ServiceThreadOnly
399    boolean handleCecCommand(HdmiCecMessage message) {
400        assertRunOnServiceThread();
401        return dispatchMessageToLocalDevice(message);
402    }
403
404    void setAudioReturnChannel(boolean enabled) {
405        mCecController.setAudioReturnChannel(enabled);
406    }
407
408    @ServiceThreadOnly
409    private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
410        assertRunOnServiceThread();
411        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
412            if (device.dispatchMessage(message)
413                    && message.getDestination() != HdmiCec.ADDR_BROADCAST) {
414                return true;
415            }
416        }
417
418        Slog.w(TAG, "Unhandled cec command:" + message);
419        return false;
420    }
421
422    /**
423     * Called when a new hotplug event is issued.
424     *
425     * @param portNo hdmi port number where hot plug event issued.
426     * @param connected whether to be plugged in or not
427     */
428    @ServiceThreadOnly
429    void onHotplug(int portNo, boolean connected) {
430        assertRunOnServiceThread();
431        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
432            device.onHotplug(portNo, connected);
433        }
434        announceHotplugEvent(portNo, connected);
435    }
436
437    /**
438     * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
439     * devices.
440     *
441     * @param callback an interface used to get a list of all remote devices' address
442     * @param pickStrategy strategy how to pick polling candidates
443     * @param retryCount the number of retry used to send polling message to remote devices
444     * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
445     */
446    @ServiceThreadOnly
447    void pollDevices(DevicePollingCallback callback, int pickStrategy, int retryCount) {
448        assertRunOnServiceThread();
449        mCecController.pollDevices(callback, checkPollStrategy(pickStrategy), retryCount);
450    }
451
452    private int checkPollStrategy(int pickStrategy) {
453        int strategy = pickStrategy & HdmiConstants.POLL_STRATEGY_MASK;
454        if (strategy == 0) {
455            throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
456        }
457        int iterationStrategy = pickStrategy & HdmiConstants.POLL_ITERATION_STRATEGY_MASK;
458        if (iterationStrategy == 0) {
459            throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
460        }
461        return strategy | iterationStrategy;
462    }
463
464    List<HdmiCecLocalDevice> getAllLocalDevices() {
465        assertRunOnServiceThread();
466        return mCecController.getLocalDeviceList();
467    }
468
469    Object getServiceLock() {
470        return mLock;
471    }
472
473    void setAudioStatus(boolean mute, int volume) {
474        // TODO: Hook up with AudioManager.
475    }
476
477    void announceSystemAudioModeChange(boolean enabled) {
478        for (IHdmiSystemAudioModeChangeListener listener : mSystemAudioModeChangeListeners) {
479            invokeSystemAudioModeChange(listener, enabled);
480        }
481    }
482
483    private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) {
484        // TODO: find better name instead of model name.
485        String displayName = Build.MODEL;
486        return new HdmiCecDeviceInfo(logicalAddress,
487                getPhysicalAddress(), deviceType, getVendorId(), displayName);
488    }
489
490    // Record class that monitors the event of the caller of being killed. Used to clean up
491    // the listener list and record list accordingly.
492    private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
493        private final IHdmiHotplugEventListener mListener;
494
495        public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
496            mListener = listener;
497        }
498
499        @Override
500        public void binderDied() {
501            synchronized (mLock) {
502                mHotplugEventListenerRecords.remove(this);
503                mHotplugEventListeners.remove(mListener);
504            }
505        }
506    }
507
508    private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
509        private final IHdmiDeviceEventListener mListener;
510
511        public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
512            mListener = listener;
513        }
514
515        @Override
516        public void binderDied() {
517            synchronized (mLock) {
518                mDeviceEventListenerRecords.remove(this);
519                mDeviceEventListeners.remove(mListener);
520            }
521        }
522    }
523
524    private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
525        private IHdmiSystemAudioModeChangeListener mListener;
526
527        public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
528            mListener = listener;
529        }
530
531        @Override
532        public void binderDied() {
533            synchronized (mLock) {
534                mSystemAudioModeChangeListenerRecords.remove(this);
535                mSystemAudioModeChangeListeners.remove(mListener);
536            }
537        }
538    }
539
540    private void enforceAccessPermission() {
541        getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
542    }
543
544    private final class BinderService extends IHdmiControlService.Stub {
545        @Override
546        public int[] getSupportedTypes() {
547            enforceAccessPermission();
548            // mLocalDevices is an unmodifiable list - no lock necesary.
549            int[] localDevices = new int[mLocalDevices.size()];
550            for (int i = 0; i < localDevices.length; ++i) {
551                localDevices[i] = mLocalDevices.get(i);
552            }
553            return localDevices;
554        }
555
556        @Override
557        public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) {
558            enforceAccessPermission();
559            runOnServiceThread(new Runnable() {
560                @Override
561                public void run() {
562                    HdmiCecLocalDeviceTv tv = tv();
563                    if (tv == null) {
564                        Slog.w(TAG, "Local tv device not available");
565                        invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
566                        return;
567                    }
568                    tv.deviceSelect(logicalAddress, callback);
569                }
570            });
571        }
572
573        @Override
574        public void portSelect(final int portId, final IHdmiControlCallback callback) {
575            enforceAccessPermission();
576            runOnServiceThread(new Runnable() {
577                @Override
578                public void run() {
579                    HdmiCecLocalDeviceTv tv = tv();
580                    if (tv == null) {
581                        Slog.w(TAG, "Local tv device not available");
582                        invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
583                        return;
584                    }
585                    tv.portSelect(portId, callback);
586                }
587            });
588        }
589
590        @Override
591        public void sendKeyEvent(final int keyCode, final boolean isPressed) {
592            enforceAccessPermission();
593            runOnServiceThread(new Runnable() {
594                @Override
595                public void run() {
596                    // TODO: sendKeyEvent is for TV device only for now. Allow other
597                    //       local devices of different types to use this as well.
598                    HdmiCecLocalDeviceTv tv = tv();
599                    if (tv == null) {
600                        Slog.w(TAG, "Local tv device not available");
601                        return;
602                    }
603                    tv.sendKeyEvent(keyCode, isPressed);
604                }
605            });
606        }
607
608        @Override
609        public void oneTouchPlay(final IHdmiControlCallback callback) {
610            enforceAccessPermission();
611            runOnServiceThread(new Runnable() {
612                @Override
613                public void run() {
614                    HdmiControlService.this.oneTouchPlay(callback);
615                }
616            });
617        }
618
619        @Override
620        public void queryDisplayStatus(final IHdmiControlCallback callback) {
621            enforceAccessPermission();
622            runOnServiceThread(new Runnable() {
623                @Override
624                public void run() {
625                    HdmiControlService.this.queryDisplayStatus(callback);
626                }
627            });
628        }
629
630        @Override
631        public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
632            enforceAccessPermission();
633            runOnServiceThread(new Runnable() {
634                @Override
635                public void run() {
636                    HdmiControlService.this.addHotplugEventListener(listener);
637                }
638            });
639        }
640
641        @Override
642        public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
643            enforceAccessPermission();
644            runOnServiceThread(new Runnable() {
645                @Override
646                public void run() {
647                    HdmiControlService.this.removeHotplugEventListener(listener);
648                }
649            });
650        }
651
652        @Override
653        public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
654            enforceAccessPermission();
655            runOnServiceThread(new Runnable() {
656                @Override
657                public void run() {
658                    HdmiControlService.this.addDeviceEventListener(listener);
659                }
660            });
661        }
662
663        @Override
664        public List<HdmiPortInfo> getPortInfo() {
665            enforceAccessPermission();
666            return mPortInfo;
667        }
668
669        @Override
670        public boolean canChangeSystemAudioMode() {
671            enforceAccessPermission();
672            HdmiCecLocalDeviceTv tv = tv();
673            if (tv == null) {
674                return false;
675            }
676            return tv.hasSystemAudioDevice();
677        }
678
679        @Override
680        public boolean getSystemAudioMode() {
681            enforceAccessPermission();
682            HdmiCecLocalDeviceTv tv = tv();
683            if (tv == null) {
684                return false;
685            }
686            return tv.getSystemAudioMode();
687        }
688
689        @Override
690        public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
691            enforceAccessPermission();
692            runOnServiceThread(new Runnable() {
693                @Override
694                public void run() {
695                    HdmiCecLocalDeviceTv tv = tv();
696                    if (tv == null) {
697                        Slog.w(TAG, "Local tv device not available");
698                        invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
699                        return;
700                    }
701                    tv.changeSystemAudioMode(enabled, callback);
702                }
703            });
704        }
705
706        @Override
707        public void addSystemAudioModeChangeListener(
708                final IHdmiSystemAudioModeChangeListener listener) {
709            enforceAccessPermission();
710            HdmiControlService.this.addSystemAudioModeChangeListner(listener);
711        }
712
713        @Override
714        public void removeSystemAudioModeChangeListener(
715                final IHdmiSystemAudioModeChangeListener listener) {
716            enforceAccessPermission();
717            HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
718        }
719    }
720
721    @ServiceThreadOnly
722    private void oneTouchPlay(final IHdmiControlCallback callback) {
723        assertRunOnServiceThread();
724        HdmiCecLocalDevicePlayback source = playback();
725        if (source == null) {
726            Slog.w(TAG, "Local playback device not available");
727            invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
728            return;
729        }
730        source.oneTouchPlay(callback);
731    }
732
733    @ServiceThreadOnly
734    private void queryDisplayStatus(final IHdmiControlCallback callback) {
735        assertRunOnServiceThread();
736        HdmiCecLocalDevicePlayback source = playback();
737        if (source == null) {
738            Slog.w(TAG, "Local playback device not available");
739            invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
740            return;
741        }
742        source.queryDisplayStatus(callback);
743    }
744
745    private void addHotplugEventListener(IHdmiHotplugEventListener listener) {
746        HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
747        try {
748            listener.asBinder().linkToDeath(record, 0);
749        } catch (RemoteException e) {
750            Slog.w(TAG, "Listener already died");
751            return;
752        }
753        synchronized (mLock) {
754            mHotplugEventListenerRecords.add(record);
755            mHotplugEventListeners.add(listener);
756        }
757    }
758
759    private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
760        synchronized (mLock) {
761            for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
762                if (record.mListener.asBinder() == listener.asBinder()) {
763                    listener.asBinder().unlinkToDeath(record, 0);
764                    mHotplugEventListenerRecords.remove(record);
765                    break;
766                }
767            }
768            mHotplugEventListeners.remove(listener);
769        }
770    }
771
772    private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
773        DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
774        try {
775            listener.asBinder().linkToDeath(record, 0);
776        } catch (RemoteException e) {
777            Slog.w(TAG, "Listener already died");
778            return;
779        }
780        synchronized (mLock) {
781            mDeviceEventListeners.add(listener);
782            mDeviceEventListenerRecords.add(record);
783        }
784    }
785
786    void invokeDeviceEventListeners(HdmiCecDeviceInfo device, boolean activated) {
787        synchronized (mLock) {
788            for (IHdmiDeviceEventListener listener : mDeviceEventListeners) {
789                try {
790                    listener.onStatusChanged(device, activated);
791                } catch (RemoteException e) {
792                    Slog.e(TAG, "Failed to report device event:" + e);
793                }
794            }
795        }
796    }
797
798    private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
799        SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
800                listener);
801        try {
802            listener.asBinder().linkToDeath(record, 0);
803        } catch (RemoteException e) {
804            Slog.w(TAG, "Listener already died");
805            return;
806        }
807        synchronized (mLock) {
808            mSystemAudioModeChangeListeners.add(listener);
809            mSystemAudioModeChangeListenerRecords.add(record);
810        }
811    }
812
813    private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
814        synchronized (mLock) {
815            for (SystemAudioModeChangeListenerRecord record :
816                    mSystemAudioModeChangeListenerRecords) {
817                if (record.mListener.asBinder() == listener) {
818                    listener.asBinder().unlinkToDeath(record, 0);
819                    mSystemAudioModeChangeListenerRecords.remove(record);
820                    break;
821                }
822            }
823            mSystemAudioModeChangeListeners.remove(listener);
824        }
825    }
826
827    private void invokeCallback(IHdmiControlCallback callback, int result) {
828        try {
829            callback.onComplete(result);
830        } catch (RemoteException e) {
831            Slog.e(TAG, "Invoking callback failed:" + e);
832        }
833    }
834
835    private void invokeSystemAudioModeChange(IHdmiSystemAudioModeChangeListener listener,
836            boolean enabled) {
837        try {
838            listener.onStatusChanged(enabled);
839        } catch (RemoteException e) {
840            Slog.e(TAG, "Invoking callback failed:" + e);
841        }
842    }
843
844    private void announceHotplugEvent(int portId, boolean connected) {
845        HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
846        synchronized (mLock) {
847            for (IHdmiHotplugEventListener listener : mHotplugEventListeners) {
848                invokeHotplugEventListenerLocked(listener, event);
849            }
850        }
851    }
852
853    private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
854            HdmiHotplugEvent event) {
855        try {
856            listener.onReceived(event);
857        } catch (RemoteException e) {
858            Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
859        }
860    }
861
862    private static boolean hasSameTopPort(int path1, int path2) {
863        return (path1 & HdmiConstants.ROUTING_PATH_TOP_MASK)
864                == (path2 & HdmiConstants.ROUTING_PATH_TOP_MASK);
865    }
866
867    private HdmiCecLocalDeviceTv tv() {
868        return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiCec.DEVICE_TV);
869    }
870
871    private HdmiCecLocalDevicePlayback playback() {
872        return (HdmiCecLocalDevicePlayback) mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK);
873    }
874
875    AudioManager getAudioManager() {
876        return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
877    }
878}
879