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