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