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