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