HdmiCecController.java revision 2918e9e133de8066ab497a5f8dac1c310c792767
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.HdmiCec;
20import android.hardware.hdmi.HdmiCecDeviceInfo;
21import android.hardware.hdmi.HdmiCecMessage;
22import android.os.Handler;
23import android.util.SparseArray;
24
25import libcore.util.EmptyArray;
26
27import java.util.ArrayList;
28import java.util.Arrays;
29import java.util.List;
30
31/**
32 * Manages HDMI-CEC command and behaviors. It converts user's command into CEC command
33 * and pass it to CEC HAL so that it sends message to other device. For incoming
34 * message it translates the message and delegates it to proper module.
35 *
36 * <p>It can be created only by {@link HdmiCecController#create}
37 *
38 * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
39 */
40final class HdmiCecController {
41    private static final String TAG = "HdmiCecController";
42
43    private static final byte[] EMPTY_BODY = EmptyArray.BYTE;
44
45    // A message to pass cec send command to IO looper.
46    private static final int MSG_SEND_CEC_COMMAND = 1;
47    // A message to delegate logical allocation to IO looper.
48    private static final int MSG_ALLOCATE_LOGICAL_ADDRESS = 2;
49
50    // Message types to handle incoming message in main service looper.
51    private final static int MSG_RECEIVE_CEC_COMMAND = 1;
52    // A message to report allocated logical address to main control looper.
53    private final static int MSG_REPORT_LOGICAL_ADDRESS = 2;
54
55    private static final int NUM_LOGICAL_ADDRESS = 16;
56
57    // TODO: define other constants for errors.
58    private static final int ERROR_SUCCESS = 0;
59
60    // Handler instance to process synchronous I/O (mainly send) message.
61    private Handler mIoHandler;
62
63    // Handler instance to process various messages coming from other CEC
64    // device or issued by internal state change.
65    private Handler mControlHandler;
66
67    // Stores the pointer to the native implementation of the service that
68    // interacts with HAL.
69    private long mNativePtr;
70
71    private HdmiControlService mService;
72
73    // Map-like container of all cec devices. A logical address of device is
74    // used as key of container.
75    private final SparseArray<HdmiCecDeviceInfo> mDeviceInfos =
76            new SparseArray<HdmiCecDeviceInfo>();
77
78    // Stores the local CEC devices in the system.
79    private final ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
80
81    // Private constructor.  Use HdmiCecController.create().
82    private HdmiCecController() {
83    }
84
85    /**
86     * A factory method to get {@link HdmiCecController}. If it fails to initialize
87     * inner device or has no device it will return {@code null}.
88     *
89     * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
90     * @param service {@link HdmiControlService} instance used to create internal handler
91     *                and to pass callback for incoming message or event.
92     * @return {@link HdmiCecController} if device is initialized successfully. Otherwise,
93     *         returns {@code null}.
94     */
95    static HdmiCecController create(HdmiControlService service) {
96        HdmiCecController controller = new HdmiCecController();
97        long nativePtr = nativeInit(controller);
98        if (nativePtr == 0L) {
99            controller = null;
100            return null;
101        }
102
103        controller.init(service, nativePtr);
104        return controller;
105    }
106
107    private void init(HdmiControlService service, long nativePtr) {
108        mService = service;
109        mIoHandler = new Handler(service.getServiceLooper());
110        mControlHandler = new Handler(service.getServiceLooper());
111        mNativePtr = nativePtr;
112    }
113
114    /**
115     * Perform initialization for each hosted device.
116     *
117     * @param deviceTypes array of device types
118     */
119    void initializeLocalDevices(int[] deviceTypes) {
120        for (int type : deviceTypes) {
121            HdmiCecLocalDevice device = HdmiCecLocalDevice.create(this, type);
122            if (device == null) {
123                continue;
124            }
125            // TODO: Consider restoring the local device addresses from persistent storage
126            //       to allocate the same addresses again if possible.
127            device.setPreferredAddress(HdmiCec.ADDR_UNREGISTERED);
128            mLocalDevices.add(device);
129            device.init();
130        }
131    }
132
133    /**
134     * Interface to report allocated logical address.
135     */
136    interface AllocateLogicalAddressCallback {
137        /**
138         * Called when a new logical address is allocated.
139         *
140         * @param deviceType requested device type to allocate logical address
141         * @param logicalAddress allocated logical address. If it is
142         *                       {@link HdmiCec#ADDR_UNREGISTERED}, it means that
143         *                       it failed to allocate logical address for the given device type
144         */
145        void onAllocated(int deviceType, int logicalAddress);
146    }
147
148    /**
149     * Allocate a new logical address of the given device type. Allocated
150     * address will be reported through {@link AllocateLogicalAddressCallback}.
151     *
152     * <p> Declared as package-private, accessed by {@link HdmiControlService} only.
153     *
154     * @param deviceType type of device to used to determine logical address
155     * @param preferredAddress a logical address preferred to be allocated.
156     *                         If sets {@link HdmiCec#ADDR_UNREGISTERED}, scans
157     *                         the smallest logical address matched with the given device type.
158     *                         Otherwise, scan address will start from {@code preferredAddress}
159     * @param callback callback interface to report allocated logical address to caller
160     */
161    void allocateLogicalAddress(final int deviceType, final int preferredAddress,
162            final AllocateLogicalAddressCallback callback) {
163        runOnIoThread(new Runnable() {
164            @Override
165            public void run() {
166                handleAllocateLogicalAddress(deviceType, preferredAddress, callback);
167            }
168        });
169    }
170
171    private void handleAllocateLogicalAddress(final int deviceType, int preferredAddress,
172            final AllocateLogicalAddressCallback callback) {
173        int startAddress = preferredAddress;
174        // If preferred address is "unregistered", start address will be the smallest
175        // address matched with the given device type.
176        if (preferredAddress == HdmiCec.ADDR_UNREGISTERED) {
177            for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
178                if (deviceType == HdmiCec.getTypeFromAddress(i)) {
179                    startAddress = i;
180                    break;
181                }
182            }
183        }
184
185        int logicalAddress = HdmiCec.ADDR_UNREGISTERED;
186        // Iterates all possible addresses which has the same device type.
187        for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
188            int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS;
189            if (curAddress != HdmiCec.ADDR_UNREGISTERED
190                    && deviceType == HdmiCec.getTypeFromAddress(i)) {
191                // <Polling Message> is a message which has empty body and
192                // uses same address for both source and destination address.
193                // If sending <Polling Message> failed (NAK), it becomes
194                // new logical address for the device because no device uses
195                // it as logical address of the device.
196                int error = nativeSendCecCommand(mNativePtr, curAddress, curAddress,
197                        EMPTY_BODY);
198                if (error != ERROR_SUCCESS) {
199                    logicalAddress = curAddress;
200                    break;
201                }
202            }
203        }
204
205        final int assignedAddress = logicalAddress;
206        if (callback != null) {
207            runOnServiceThread(new Runnable() {
208                    @Override
209                public void run() {
210                    callback.onAllocated(deviceType, assignedAddress);
211                }
212            });
213        }
214    }
215
216    private static byte[] buildBody(int opcode, byte[] params) {
217        byte[] body = new byte[params.length + 1];
218        body[0] = (byte) opcode;
219        System.arraycopy(params, 0, body, 1, params.length);
220        return body;
221    }
222
223    /**
224     * Add a new {@link HdmiCecDeviceInfo}. It returns old device info which has the same
225     * logical address as new device info's.
226     *
227     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
228     *
229     * @param deviceInfo a new {@link HdmiCecDeviceInfo} to be added.
230     * @return {@code null} if it is new device. Otherwise, returns old {@HdmiCecDeviceInfo}
231     *         that has the same logical address as new one has.
232     */
233    HdmiCecDeviceInfo addDeviceInfo(HdmiCecDeviceInfo deviceInfo) {
234        HdmiCecDeviceInfo oldDeviceInfo = getDeviceInfo(deviceInfo.getLogicalAddress());
235        if (oldDeviceInfo != null) {
236            removeDeviceInfo(deviceInfo.getLogicalAddress());
237        }
238        mDeviceInfos.append(deviceInfo.getLogicalAddress(), deviceInfo);
239        return oldDeviceInfo;
240    }
241
242    /**
243     * Remove a device info corresponding to the given {@code logicalAddress}.
244     * It returns removed {@link HdmiCecDeviceInfo} if exists.
245     *
246     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
247     *
248     * @param logicalAddress logical address of device to be removed
249     * @return removed {@link HdmiCecDeviceInfo} it exists. Otherwise, returns {@code null}
250     */
251    HdmiCecDeviceInfo removeDeviceInfo(int logicalAddress) {
252        HdmiCecDeviceInfo deviceInfo = mDeviceInfos.get(logicalAddress);
253        if (deviceInfo != null) {
254            mDeviceInfos.remove(logicalAddress);
255        }
256        return deviceInfo;
257    }
258
259    /**
260     * Return a list of all {@HdmiCecDeviceInfo}.
261     *
262     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
263     */
264    List<HdmiCecDeviceInfo> getDeviceInfoList() {
265        List<HdmiCecDeviceInfo> deviceInfoList = new ArrayList<HdmiCecDeviceInfo>(
266                mDeviceInfos.size());
267        for (int i = 0; i < mDeviceInfos.size(); ++i) {
268            deviceInfoList.add(mDeviceInfos.valueAt(i));
269        }
270        return deviceInfoList;
271    }
272
273    /**
274     * Return a {@link HdmiCecDeviceInfo} corresponding to the given {@code logicalAddress}.
275     *
276     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
277     *
278     * @param logicalAddress logical address to be retrieved
279     * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}.
280     *         Returns null if no logical address matched
281     */
282    HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) {
283        return mDeviceInfos.get(logicalAddress);
284    }
285
286    /**
287     * Add a new logical address to the device. Device's HW should be notified
288     * when a new logical address is assigned to a device, so that it can accept
289     * a command having available destinations.
290     *
291     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
292     *
293     * @param newLogicalAddress a logical address to be added
294     * @return 0 on success. Otherwise, returns negative value
295     */
296    int addLogicalAddress(int newLogicalAddress) {
297        if (HdmiCec.isValidAddress(newLogicalAddress)) {
298            return nativeAddLogicalAddress(mNativePtr, newLogicalAddress);
299        } else {
300            return -1;
301        }
302    }
303
304    /**
305     * Clear all logical addresses registered in the device.
306     *
307     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
308     */
309    void clearLogicalAddress() {
310        // TODO: consider to backup logical address so that new logical address
311        // allocation can use it as preferred address.
312        for (HdmiCecLocalDevice device : mLocalDevices) {
313            device.clearAddress();
314        }
315        nativeClearLogicalAddress(mNativePtr);
316    }
317
318    /**
319     * Return the physical address of the device.
320     *
321     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
322     *
323     * @return CEC physical address of the device. The range of success address
324     *         is between 0x0000 and 0xFFFF. If failed it returns -1
325     */
326    int getPhysicalAddress() {
327        return nativeGetPhysicalAddress(mNativePtr);
328    }
329
330    /**
331     * Return CEC version of the device.
332     *
333     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
334     */
335    int getVersion() {
336        return nativeGetVersion(mNativePtr);
337    }
338
339    /**
340     * Return vendor id of the device.
341     *
342     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
343     */
344    int getVendorId() {
345        return nativeGetVendorId(mNativePtr);
346    }
347
348    private void runOnIoThread(Runnable runnable) {
349        mIoHandler.post(runnable);
350    }
351
352    private void runOnServiceThread(Runnable runnable) {
353        mControlHandler.post(runnable);
354    }
355
356    private boolean isAcceptableAddress(int address) {
357        // Can access command targeting devices available in local device or broadcast command.
358        if (address == HdmiCec.ADDR_BROADCAST) {
359            return true;
360        }
361        for (HdmiCecLocalDevice device : mLocalDevices) {
362            if (device.isAddressOf(address)) {
363                return true;
364            }
365        }
366        return false;
367    }
368
369    private void onReceiveCommand(HdmiCecMessage message) {
370        if (isAcceptableAddress(message.getDestination()) &&
371                mService.handleCecCommand(message)) {
372            return;
373        }
374
375        // TODO: Use device's source address for broadcast message.
376        int sourceAddress = message.getDestination() != HdmiCec.ADDR_BROADCAST ?
377                message.getDestination() : 0;
378        // Reply <Feature Abort> to initiator (source) for all requests.
379        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand
380                (sourceAddress, message.getSource(), message.getOpcode(),
381                        HdmiCecMessageBuilder.ABORT_REFUSED);
382        sendCommand(cecMessage, null);
383    }
384
385    void sendCommand(HdmiCecMessage cecMessage) {
386        sendCommand(cecMessage, null);
387    }
388
389    void sendCommand(final HdmiCecMessage cecMessage,
390            final HdmiControlService.SendMessageCallback callback) {
391        runOnIoThread(new Runnable() {
392            @Override
393            public void run() {
394                byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
395                final int error = nativeSendCecCommand(mNativePtr, cecMessage.getSource(),
396                        cecMessage.getDestination(), body);
397                if (callback != null) {
398                    runOnServiceThread(new Runnable() {
399                        @Override
400                        public void run() {
401                            callback.onSendCompleted(error);
402                        }
403                    });
404                }
405            }
406        });
407    }
408
409    /**
410     * Called by native when incoming CEC message arrived.
411     */
412    private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) {
413        byte opcode = body[0];
414        byte params[] = Arrays.copyOfRange(body, 1, body.length);
415        final HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, dstAddress, opcode, params);
416
417        // Delegate message to main handler so that it handles in main thread.
418        runOnServiceThread(new Runnable() {
419            @Override
420            public void run() {
421                onReceiveCommand(cecMessage);
422            }
423        });
424    }
425
426    /**
427     * Called by native when a hotplug event issues.
428     */
429    private void handleHotplug(boolean connected) {
430        // TODO: once add port number to cec HAL interface, pass port number
431        // to the service.
432        mService.onHotplug(0, connected);
433    }
434
435    private static native long nativeInit(HdmiCecController handler);
436    private static native int nativeSendCecCommand(long controllerPtr, int srcAddress,
437            int dstAddress, byte[] body);
438    private static native int nativeAddLogicalAddress(long controllerPtr, int logicalAddress);
439    private static native void nativeClearLogicalAddress(long controllerPtr);
440    private static native int nativeGetPhysicalAddress(long controllerPtr);
441    private static native int nativeGetVersion(long controllerPtr);
442    private static native int nativeGetVendorId(long controllerPtr);
443}
444