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