HdmiCecController.java revision 4fc1d105fc279bf7df6c876e160672866bdad8e7
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.hardware.hdmi.HdmiPortInfo;
20import android.os.Handler;
21import android.os.Looper;
22import android.os.MessageQueue;
23import android.util.Slog;
24import android.util.SparseArray;
25
26import com.android.internal.util.Predicate;
27import com.android.server.hdmi.HdmiAnnotations.IoThreadOnly;
28import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
29import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
30
31import libcore.util.EmptyArray;
32
33import java.util.ArrayList;
34import java.util.List;
35
36/**
37 * Manages HDMI-CEC command and behaviors. It converts user's command into CEC command
38 * and pass it to CEC HAL so that it sends message to other device. For incoming
39 * message it translates the message and delegates it to proper module.
40 *
41 * <p>It should be careful to access member variables on IO thread because
42 * it can be accessed from system thread as well.
43 *
44 * <p>It can be created only by {@link HdmiCecController#create}
45 *
46 * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
47 */
48final class HdmiCecController {
49    private static final String TAG = "HdmiCecController";
50
51    /**
52     * Interface to report allocated logical address.
53     */
54    interface AllocateAddressCallback {
55        /**
56         * Called when a new logical address is allocated.
57         *
58         * @param deviceType requested device type to allocate logical address
59         * @param logicalAddress allocated logical address. If it is
60         *                       {@link Constants#ADDR_UNREGISTERED}, it means that
61         *                       it failed to allocate logical address for the given device type
62         */
63        void onAllocated(int deviceType, int logicalAddress);
64    }
65
66    private static final byte[] EMPTY_BODY = EmptyArray.BYTE;
67
68    // A message to pass cec send command to IO looper.
69    private static final int MSG_SEND_CEC_COMMAND = 1;
70    // A message to delegate logical allocation to IO looper.
71    private static final int MSG_ALLOCATE_LOGICAL_ADDRESS = 2;
72
73    // Message types to handle incoming message in main service looper.
74    private final static int MSG_RECEIVE_CEC_COMMAND = 1;
75    // A message to report allocated logical address to main control looper.
76    private final static int MSG_REPORT_LOGICAL_ADDRESS = 2;
77
78    private static final int NUM_LOGICAL_ADDRESS = 16;
79
80    private static final int RETRY_COUNT_FOR_LOGICAL_ADDRESS_ALLOCATION = 3;
81
82    // Predicate for whether the given logical address is remote device's one or not.
83    private final Predicate<Integer> mRemoteDeviceAddressPredicate = new Predicate<Integer>() {
84        @Override
85        public boolean apply(Integer address) {
86            return !isAllocatedLocalDeviceAddress(address);
87        }
88    };
89
90    // Predicate whether the given logical address is system audio's one or not
91    private final Predicate<Integer> mSystemAudioAddressPredicate = new Predicate<Integer>() {
92        @Override
93        public boolean apply(Integer address) {
94            return HdmiUtils.getTypeFromAddress(address) == Constants.ADDR_AUDIO_SYSTEM;
95        }
96    };
97
98    // Handler instance to process synchronous I/O (mainly send) message.
99    private Handler mIoHandler;
100
101    // Handler instance to process various messages coming from other CEC
102    // device or issued by internal state change.
103    private Handler mControlHandler;
104
105    // Stores the pointer to the native implementation of the service that
106    // interacts with HAL.
107    private volatile long mNativePtr;
108
109    private HdmiControlService mService;
110
111    // Stores the local CEC devices in the system. Device type is used for key.
112    private final SparseArray<HdmiCecLocalDevice> mLocalDevices = new SparseArray<>();
113
114    // Private constructor.  Use HdmiCecController.create().
115    private HdmiCecController() {
116    }
117
118    /**
119     * A factory method to get {@link HdmiCecController}. If it fails to initialize
120     * inner device or has no device it will return {@code null}.
121     *
122     * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
123     * @param service {@link HdmiControlService} instance used to create internal handler
124     *                and to pass callback for incoming message or event.
125     * @return {@link HdmiCecController} if device is initialized successfully. Otherwise,
126     *         returns {@code null}.
127     */
128    static HdmiCecController create(HdmiControlService service) {
129        HdmiCecController controller = new HdmiCecController();
130        long nativePtr = nativeInit(controller, service.getServiceLooper().getQueue());
131        if (nativePtr == 0L) {
132            controller = null;
133            return null;
134        }
135
136        controller.init(service, nativePtr);
137        return controller;
138    }
139
140    private void init(HdmiControlService service, long nativePtr) {
141        mService = service;
142        mIoHandler = new Handler(service.getServiceLooper());
143        mControlHandler = new Handler(service.getServiceLooper());
144        mNativePtr = nativePtr;
145    }
146
147    @ServiceThreadOnly
148    void addLocalDevice(int deviceType, HdmiCecLocalDevice device) {
149        assertRunOnServiceThread();
150        mLocalDevices.put(deviceType, device);
151    }
152
153    /**
154     * Allocate a new logical address of the given device type. Allocated
155     * address will be reported through {@link AllocateAddressCallback}.
156     *
157     * <p> Declared as package-private, accessed by {@link HdmiControlService} only.
158     *
159     * @param deviceType type of device to used to determine logical address
160     * @param preferredAddress a logical address preferred to be allocated.
161     *                         If sets {@link Constants#ADDR_UNREGISTERED}, scans
162     *                         the smallest logical address matched with the given device type.
163     *                         Otherwise, scan address will start from {@code preferredAddress}
164     * @param callback callback interface to report allocated logical address to caller
165     */
166    @ServiceThreadOnly
167    void allocateLogicalAddress(final int deviceType, final int preferredAddress,
168            final AllocateAddressCallback callback) {
169        assertRunOnServiceThread();
170
171        runOnIoThread(new Runnable() {
172            @Override
173            public void run() {
174                handleAllocateLogicalAddress(deviceType, preferredAddress, callback);
175            }
176        });
177    }
178
179    @IoThreadOnly
180    private void handleAllocateLogicalAddress(final int deviceType, int preferredAddress,
181            final AllocateAddressCallback callback) {
182        assertRunOnIoThread();
183        int startAddress = preferredAddress;
184        // If preferred address is "unregistered", start address will be the smallest
185        // address matched with the given device type.
186        if (preferredAddress == Constants.ADDR_UNREGISTERED) {
187            for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
188                if (deviceType == HdmiUtils.getTypeFromAddress(i)) {
189                    startAddress = i;
190                    break;
191                }
192            }
193        }
194
195        int logicalAddress = Constants.ADDR_UNREGISTERED;
196        // Iterates all possible addresses which has the same device type.
197        for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
198            int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS;
199            if (curAddress != Constants.ADDR_UNREGISTERED
200                    && deviceType == HdmiUtils.getTypeFromAddress(curAddress)) {
201                if (!sendPollMessage(curAddress, curAddress,
202                        RETRY_COUNT_FOR_LOGICAL_ADDRESS_ALLOCATION)) {
203                    logicalAddress = curAddress;
204                    break;
205                }
206            }
207        }
208
209        final int assignedAddress = logicalAddress;
210        if (callback != null) {
211            runOnServiceThread(new Runnable() {
212                    @Override
213                public void run() {
214                    callback.onAllocated(deviceType, assignedAddress);
215                }
216            });
217        }
218    }
219
220    private static byte[] buildBody(int opcode, byte[] params) {
221        byte[] body = new byte[params.length + 1];
222        body[0] = (byte) opcode;
223        System.arraycopy(params, 0, body, 1, params.length);
224        return body;
225    }
226
227
228    HdmiPortInfo[] getPortInfos() {
229        return nativeGetPortInfos(mNativePtr);
230    }
231
232    /**
233     * Return the locally hosted logical device of a given type.
234     *
235     * @param deviceType logical device type
236     * @return {@link HdmiCecLocalDevice} instance if the instance of the type is available;
237     *          otherwise null.
238     */
239    HdmiCecLocalDevice getLocalDevice(int deviceType) {
240        return mLocalDevices.get(deviceType);
241    }
242
243    /**
244     * Add a new logical address to the device. Device's HW should be notified
245     * when a new logical address is assigned to a device, so that it can accept
246     * a command having available destinations.
247     *
248     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
249     *
250     * @param newLogicalAddress a logical address to be added
251     * @return 0 on success. Otherwise, returns negative value
252     */
253    @ServiceThreadOnly
254    int addLogicalAddress(int newLogicalAddress) {
255        assertRunOnServiceThread();
256        if (HdmiUtils.isValidAddress(newLogicalAddress)) {
257            return nativeAddLogicalAddress(mNativePtr, newLogicalAddress);
258        } else {
259            return -1;
260        }
261    }
262
263    /**
264     * Clear all logical addresses registered in the device.
265     *
266     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
267     */
268    @ServiceThreadOnly
269    void clearLogicalAddress() {
270        assertRunOnServiceThread();
271        // TODO: consider to backup logical address so that new logical address
272        // allocation can use it as preferred address.
273        for (int i = 0; i < mLocalDevices.size(); ++i) {
274            mLocalDevices.valueAt(i).clearAddress();
275        }
276        nativeClearLogicalAddress(mNativePtr);
277    }
278
279    @ServiceThreadOnly
280    void clearLocalDevices() {
281        assertRunOnServiceThread();
282        mLocalDevices.clear();
283    }
284
285    /**
286     * Return the physical address of the device.
287     *
288     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
289     *
290     * @return CEC physical address of the device. The range of success address
291     *         is between 0x0000 and 0xFFFF. If failed it returns -1
292     */
293    @ServiceThreadOnly
294    int getPhysicalAddress() {
295        assertRunOnServiceThread();
296        return nativeGetPhysicalAddress(mNativePtr);
297    }
298
299    /**
300     * Return CEC version of the device.
301     *
302     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
303     */
304    @ServiceThreadOnly
305    int getVersion() {
306        assertRunOnServiceThread();
307        return nativeGetVersion(mNativePtr);
308    }
309
310    /**
311     * Return vendor id of the device.
312     *
313     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
314     */
315    @ServiceThreadOnly
316    int getVendorId() {
317        assertRunOnServiceThread();
318        return nativeGetVendorId(mNativePtr);
319    }
320
321    /**
322     * Set an option to CEC HAL.
323     *
324     * @param flag key of option
325     * @param value value of option
326     */
327    @ServiceThreadOnly
328    void setOption(int flag, int value) {
329        assertRunOnServiceThread();
330        nativeSetOption(mNativePtr, flag, value);
331    }
332
333    /**
334     * Configure ARC circuit in the hardware logic to start or stop the feature.
335     *
336     * @param enabled whether to enable/disable ARC
337     */
338    @ServiceThreadOnly
339    void setAudioReturnChannel(boolean enabled) {
340        assertRunOnServiceThread();
341        nativeSetAudioReturnChannel(mNativePtr, enabled);
342    }
343
344    /**
345     * Return the connection status of the specified port
346     *
347     * @param port port number to check connection status
348     * @return true if connected; otherwise, return false
349     */
350    @ServiceThreadOnly
351    boolean isConnected(int port) {
352        assertRunOnServiceThread();
353        return nativeIsConnected(mNativePtr, port);
354    }
355
356    /**
357     * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
358     * devices.
359     *
360     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
361     *
362     * @param callback an interface used to get a list of all remote devices' address
363     * @param sourceAddress a logical address of source device where sends polling message
364     * @param pickStrategy strategy how to pick polling candidates
365     * @param retryCount the number of retry used to send polling message to remote devices
366     */
367    @ServiceThreadOnly
368    void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
369            int retryCount) {
370        assertRunOnServiceThread();
371
372        // Extract polling candidates. No need to poll against local devices.
373        List<Integer> pollingCandidates = pickPollCandidates(pickStrategy);
374        runDevicePolling(sourceAddress, pollingCandidates, retryCount, callback);
375    }
376
377    /**
378     * Return a list of all {@link HdmiCecLocalDevice}s.
379     *
380     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
381     */
382    @ServiceThreadOnly
383    List<HdmiCecLocalDevice> getLocalDeviceList() {
384        assertRunOnServiceThread();
385        return HdmiUtils.sparseArrayToList(mLocalDevices);
386    }
387
388    private List<Integer> pickPollCandidates(int pickStrategy) {
389        int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
390        Predicate<Integer> pickPredicate = null;
391        switch (strategy) {
392            case Constants.POLL_STRATEGY_SYSTEM_AUDIO:
393                pickPredicate = mSystemAudioAddressPredicate;
394                break;
395            case Constants.POLL_STRATEGY_REMOTES_DEVICES:
396            default:  // The default is POLL_STRATEGY_REMOTES_DEVICES.
397                pickPredicate = mRemoteDeviceAddressPredicate;
398                break;
399        }
400
401        int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
402        ArrayList<Integer> pollingCandidates = new ArrayList<>();
403        switch (iterationStrategy) {
404            case Constants.POLL_ITERATION_IN_ORDER:
405                for (int i = Constants.ADDR_TV; i <= Constants.ADDR_SPECIFIC_USE; ++i) {
406                    if (pickPredicate.apply(i)) {
407                        pollingCandidates.add(i);
408                    }
409                }
410                break;
411            case Constants.POLL_ITERATION_REVERSE_ORDER:
412            default:  // The default is reverse order.
413                for (int i = Constants.ADDR_SPECIFIC_USE; i >= Constants.ADDR_TV; --i) {
414                    if (pickPredicate.apply(i)) {
415                        pollingCandidates.add(i);
416                    }
417                }
418                break;
419        }
420        return pollingCandidates;
421    }
422
423    @ServiceThreadOnly
424    private boolean isAllocatedLocalDeviceAddress(int address) {
425        assertRunOnServiceThread();
426        for (int i = 0; i < mLocalDevices.size(); ++i) {
427            if (mLocalDevices.valueAt(i).isAddressOf(address)) {
428                return true;
429            }
430        }
431        return false;
432    }
433
434    @ServiceThreadOnly
435    private void runDevicePolling(final int sourceAddress,
436            final List<Integer> candidates, final int retryCount,
437            final DevicePollingCallback callback) {
438        assertRunOnServiceThread();
439        runOnIoThread(new Runnable() {
440            @Override
441            public void run() {
442                final ArrayList<Integer> allocated = new ArrayList<>();
443                for (Integer address : candidates) {
444                    if (sendPollMessage(sourceAddress, address, retryCount)) {
445                        allocated.add(address);
446                    }
447                }
448                if (callback != null) {
449                    runOnServiceThread(new Runnable() {
450                        @Override
451                        public void run() {
452                            callback.onPollingFinished(allocated);
453                        }
454                    });
455                }
456            }
457        });
458    }
459
460    @IoThreadOnly
461    private boolean sendPollMessage(int sourceAddress, int destinationAddress, int retryCount) {
462        assertRunOnIoThread();
463        for (int i = 0; i < retryCount; ++i) {
464            // <Polling Message> is a message which has empty body.
465            // If sending <Polling Message> failed (NAK), it becomes
466            // new logical address for the device because no device uses
467            // it as logical address of the device.
468            if (nativeSendCecCommand(mNativePtr, sourceAddress, destinationAddress, EMPTY_BODY)
469                    == Constants.SEND_RESULT_SUCCESS) {
470                return true;
471            }
472        }
473        return false;
474    }
475
476    private void assertRunOnIoThread() {
477        if (Looper.myLooper() != mIoHandler.getLooper()) {
478            throw new IllegalStateException("Should run on io thread.");
479        }
480    }
481
482    private void assertRunOnServiceThread() {
483        if (Looper.myLooper() != mControlHandler.getLooper()) {
484            throw new IllegalStateException("Should run on service thread.");
485        }
486    }
487
488    // Run a Runnable on IO thread.
489    // It should be careful to access member variables on IO thread because
490    // it can be accessed from system thread as well.
491    private void runOnIoThread(Runnable runnable) {
492        mIoHandler.post(runnable);
493    }
494
495    private void runOnServiceThread(Runnable runnable) {
496        mControlHandler.post(runnable);
497    }
498
499    private boolean isAcceptableAddress(int address) {
500        // Can access command targeting devices available in local device or broadcast command.
501        if (address == Constants.ADDR_BROADCAST) {
502            return true;
503        }
504        return isAllocatedLocalDeviceAddress(address);
505    }
506
507    @ServiceThreadOnly
508    private void onReceiveCommand(HdmiCecMessage message) {
509        assertRunOnServiceThread();
510        if (isAcceptableAddress(message.getDestination())
511                && mService.handleCecCommand(message)) {
512            return;
513        }
514
515        if (message.getDestination() != Constants.ADDR_BROADCAST) {
516            int sourceAddress = message.getDestination();
517            // Reply <Feature Abort> to initiator (source) for all requests.
518            HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand(
519                    sourceAddress, message.getSource(), message.getOpcode(),
520                    Constants.ABORT_REFUSED);
521            sendCommand(cecMessage);
522        }
523    }
524
525    @ServiceThreadOnly
526    void sendCommand(HdmiCecMessage cecMessage) {
527        assertRunOnServiceThread();
528        sendCommand(cecMessage, null);
529    }
530
531    @ServiceThreadOnly
532    void sendCommand(final HdmiCecMessage cecMessage,
533            final HdmiControlService.SendMessageCallback callback) {
534        assertRunOnServiceThread();
535        runOnIoThread(new Runnable() {
536            @Override
537            public void run() {
538                byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
539                final int error = nativeSendCecCommand(mNativePtr, cecMessage.getSource(),
540                        cecMessage.getDestination(), body);
541                if (error != Constants.SEND_RESULT_SUCCESS) {
542                    Slog.w(TAG, "Failed to send " + cecMessage);
543                }
544                if (callback != null) {
545                    runOnServiceThread(new Runnable() {
546                        @Override
547                        public void run() {
548                            callback.onSendCompleted(error);
549                        }
550                    });
551                }
552            }
553        });
554    }
555
556    /**
557     * Called by native when incoming CEC message arrived.
558     */
559    @ServiceThreadOnly
560    private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) {
561        assertRunOnServiceThread();
562        onReceiveCommand(HdmiCecMessageBuilder.of(srcAddress, dstAddress, body));
563    }
564
565    /**
566     * Called by native when a hotplug event issues.
567     */
568    @ServiceThreadOnly
569    private void handleHotplug(int port, boolean connected) {
570        assertRunOnServiceThread();
571        mService.onHotplug(port, connected);
572    }
573
574    private static native long nativeInit(HdmiCecController handler, MessageQueue messageQueue);
575    private static native int nativeSendCecCommand(long controllerPtr, int srcAddress,
576            int dstAddress, byte[] body);
577    private static native int nativeAddLogicalAddress(long controllerPtr, int logicalAddress);
578    private static native void nativeClearLogicalAddress(long controllerPtr);
579    private static native int nativeGetPhysicalAddress(long controllerPtr);
580    private static native int nativeGetVersion(long controllerPtr);
581    private static native int nativeGetVendorId(long controllerPtr);
582    private static native HdmiPortInfo[] nativeGetPortInfos(long controllerPtr);
583    private static native void nativeSetOption(long controllerPtr, int flag, int value);
584    private static native void nativeSetAudioReturnChannel(long controllerPtr, boolean flag);
585    private static native boolean nativeIsConnected(long controllerPtr, int port);
586}
587