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