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