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