HdmiCecController.java revision 7d9a843af83dbc75b1d170c743603198ede5a10f
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 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 */ 41class HdmiCecController { 42 private static final String TAG = "HdmiCecController"; 43 44 // A message to pass cec send command to IO looper. 45 private static final int MSG_SEND_CEC_COMMAND = 1; 46 47 // Message types to handle incoming message in main service looper. 48 private final static int MSG_RECEIVE_CEC_COMMAND = 1; 49 50 // TODO: move these values to HdmiCec.java once make it internal constant class. 51 // CEC's ABORT reason values. 52 private static final int ABORT_UNRECOGNIZED_MODE = 0; 53 private static final int ABORT_NOT_IN_CORRECT_MODE = 1; 54 private static final int ABORT_CANNOT_PROVIDE_SOURCE = 2; 55 private static final int ABORT_INVALID_OPERAND = 3; 56 private static final int ABORT_REFUSED = 4; 57 private static final int ABORT_UNABLE_TO_DETERMINE = 5; 58 59 // Handler instance to process synchronous I/O (mainly send) message. 60 private Handler mIoHandler; 61 62 // Handler instance to process various messages coming from other CEC 63 // device or issued by internal state change. 64 private Handler mControlHandler; 65 66 // Stores the pointer to the native implementation of the service that 67 // interacts with HAL. 68 private long mNativePtr; 69 70 // Map-like container of all cec devices. A logical address of device is 71 // used as key of container. 72 private final SparseArray<HdmiCecDeviceInfo> mDeviceInfos = 73 new SparseArray<HdmiCecDeviceInfo>(); 74 75 // Private constructor. Use HdmiCecController.create(). 76 private HdmiCecController() { 77 } 78 79 /** 80 * A factory method to get {@link HdmiCecController}. If it fails to initialize 81 * inner device or has no device it will return {@code null}. 82 * 83 * <p>Declared as package-private, accessed by {@link HdmiControlService} only. 84 * @param service {@link HdmiControlService} instance used to create internal handler 85 * and to pass callback for incoming message or event. 86 * @return {@link HdmiCecController} if device is initialized successfully. Otherwise, 87 * returns {@code null}. 88 */ 89 static HdmiCecController create(HdmiControlService service) { 90 HdmiCecController handler = new HdmiCecController(); 91 long nativePtr = nativeInit(handler); 92 if (nativePtr == 0L) { 93 handler = null; 94 return null; 95 } 96 97 handler.init(service, nativePtr); 98 return handler; 99 } 100 101 private static byte[] buildBody(int opcode, byte[] params) { 102 byte[] body = new byte[params.length + 1]; 103 body[0] = (byte) opcode; 104 System.arraycopy(params, 0, body, 1, params.length); 105 return body; 106 } 107 108 private final class IoHandler extends Handler { 109 private IoHandler(Looper looper) { 110 super(looper); 111 } 112 113 @Override 114 public void handleMessage(Message msg) { 115 switch (msg.what) { 116 case MSG_SEND_CEC_COMMAND: 117 HdmiCecMessage cecMessage = (HdmiCecMessage) msg.obj; 118 byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams()); 119 nativeSendCecCommand(mNativePtr, cecMessage.getSource(), 120 cecMessage.getDestination(), body); 121 break; 122 default: 123 Slog.w(TAG, "Unsupported CEC Io request:" + msg.what); 124 break; 125 } 126 } 127 } 128 129 private final class ControlHandler extends Handler { 130 private ControlHandler(Looper looper) { 131 super(looper); 132 } 133 134 @Override 135 public void handleMessage(Message msg) { 136 switch (msg.what) { 137 case MSG_RECEIVE_CEC_COMMAND: 138 // TODO: delegate it to HdmiControl service. 139 onReceiveCommand((HdmiCecMessage) msg.obj); 140 break; 141 default: 142 Slog.i(TAG, "Unsupported message type:" + msg.what); 143 break; 144 } 145 } 146 } 147 148 /** 149 * Add a new {@link HdmiCecDeviceInfo}. It returns old device info which has the same 150 * logical address as new device info's. 151 * 152 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 153 * 154 * @param deviceInfo a new {@link HdmiCecDeviceInfo} to be added. 155 * @return {@code null} if it is new device. Otherwise, returns old {@HdmiCecDeviceInfo} 156 * that has the same logical address as new one has. 157 */ 158 HdmiCecDeviceInfo addDeviceInfo(HdmiCecDeviceInfo deviceInfo) { 159 HdmiCecDeviceInfo oldDeviceInfo = getDeviceInfo(deviceInfo.getLogicalAddress()); 160 if (oldDeviceInfo != null) { 161 removeDeviceInfo(deviceInfo.getLogicalAddress()); 162 } 163 mDeviceInfos.append(deviceInfo.getLogicalAddress(), deviceInfo); 164 return oldDeviceInfo; 165 } 166 167 /** 168 * Remove a device info corresponding to the given {@code logicalAddress}. 169 * It returns removed {@link HdmiCecDeviceInfo} if exists. 170 * 171 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 172 * 173 * @param logicalAddress logical address of device to be removed 174 * @return removed {@link HdmiCecDeviceInfo} it exists. Otherwise, returns {@code null} 175 */ 176 HdmiCecDeviceInfo removeDeviceInfo(int logicalAddress) { 177 HdmiCecDeviceInfo deviceInfo = mDeviceInfos.get(logicalAddress); 178 if (deviceInfo != null) { 179 mDeviceInfos.remove(logicalAddress); 180 } 181 return deviceInfo; 182 } 183 184 /** 185 * Return a list of all {@HdmiCecDeviceInfo}. 186 * 187 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 188 */ 189 List<HdmiCecDeviceInfo> getDeviceInfoList() { 190 List<HdmiCecDeviceInfo> deviceInfoList = new ArrayList<HdmiCecDeviceInfo>( 191 mDeviceInfos.size()); 192 for (int i = 0; i < mDeviceInfos.size(); ++i) { 193 deviceInfoList.add(mDeviceInfos.valueAt(i)); 194 } 195 return deviceInfoList; 196 } 197 198 /** 199 * Return a {@link HdmiCecDeviceInfo} corresponding to the given {@code logicalAddress}. 200 * 201 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 202 * 203 * @param logicalAddress logical address to be retrieved 204 * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}. 205 * Returns null if no logical address matched 206 */ 207 HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) { 208 return mDeviceInfos.get(logicalAddress); 209 } 210 211 private void init(HdmiControlService service, long nativePtr) { 212 mIoHandler = new IoHandler(service.getServiceLooper()); 213 mControlHandler = new ControlHandler(service.getServiceLooper()); 214 mNativePtr = nativePtr; 215 } 216 217 private void onReceiveCommand(HdmiCecMessage message) { 218 // TODO: Handle message according to opcode type. 219 220 // TODO: Use device's source address for broadcast message. 221 int sourceAddress = message.getDestination() != HdmiCec.ADDR_BROADCAST ? 222 message.getDestination() : 0; 223 // Reply <Feature Abort> to initiator (source) for all requests. 224 sendFeatureAbort(sourceAddress, message.getSource(), message.getOpcode(), 225 ABORT_REFUSED); 226 } 227 228 private void sendFeatureAbort(int srcAddress, int destAddress, int originalOpcode, 229 int reason) { 230 byte[] params = new byte[2]; 231 params[0] = (byte) originalOpcode; 232 params[1] = (byte) reason; 233 234 HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, destAddress, 235 HdmiCec.MESSAGE_FEATURE_ABORT, params); 236 Message message = mIoHandler.obtainMessage(MSG_SEND_CEC_COMMAND, cecMessage); 237 mIoHandler.sendMessage(message); 238 } 239 240 /** 241 * Called by native when incoming CEC message arrived. 242 */ 243 private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) { 244 byte opcode = body[0]; 245 byte params[] = Arrays.copyOfRange(body, 1, body.length); 246 HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, dstAddress, opcode, params); 247 248 // Delegate message to main handler so that it handles in main thread. 249 Message message = mControlHandler.obtainMessage( 250 MSG_RECEIVE_CEC_COMMAND, cecMessage); 251 mControlHandler.sendMessage(message); 252 } 253 254 /** 255 * Called by native when a hotplug event issues. 256 */ 257 private void handleHotplug(boolean connected) { 258 // TODO: Delegate event to main message handler. 259 } 260 261 private static native long nativeInit(HdmiCecController handler); 262 private static native int nativeSendCecCommand(long contollerPtr, int srcAddress, 263 int dstAddress, byte[] body); 264} 265