HdmiControlService.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.annotation.Nullable; 20import android.content.Context; 21import android.hardware.hdmi.HdmiCec; 22import android.hardware.hdmi.HdmiCecDeviceInfo; 23import android.hardware.hdmi.HdmiCecMessage; 24import android.os.Handler; 25import android.os.HandlerThread; 26import android.os.Looper; 27import android.util.Slog; 28 29import com.android.server.SystemService; 30 31import java.util.Iterator; 32import java.util.LinkedList; 33import java.util.Locale; 34 35/** 36 * Provides a service for sending and processing HDMI control messages, 37 * HDMI-CEC and MHL control command, and providing the information on both standard. 38 */ 39public final class HdmiControlService extends SystemService { 40 private static final String TAG = "HdmiControlService"; 41 42 // A thread to handle synchronous IO of CEC and MHL control service. 43 // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms) 44 // and sparse call it shares a thread to handle IO operations. 45 private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread"); 46 47 // A collection of FeatureAction. 48 // Note that access to this collection should happen in service thread. 49 private final LinkedList<FeatureAction> mActions = new LinkedList<>(); 50 51 @Nullable 52 private HdmiCecController mCecController; 53 54 @Nullable 55 private HdmiMhlController mMhlController; 56 57 // Whether ARC is "enabled" or not. 58 // TODO: it may need to hold lock if it's accessed from others. 59 private boolean mArcStatusEnabled = false; 60 61 // Handler running on service thread. It's used to run a task in service thread. 62 private Handler mHandler = new Handler(); 63 64 public HdmiControlService(Context context) { 65 super(context); 66 } 67 68 @Override 69 public void onStart() { 70 mCecController = HdmiCecController.create(this); 71 if (mCecController != null) { 72 mCecController.initializeLocalDevices(getContext().getResources() 73 .getIntArray(com.android.internal.R.array.config_hdmiCecLogicalDeviceType)); 74 } else { 75 Slog.i(TAG, "Device does not support HDMI-CEC."); 76 } 77 78 mMhlController = HdmiMhlController.create(this); 79 if (mMhlController == null) { 80 Slog.i(TAG, "Device does not support MHL-control."); 81 } 82 } 83 84 /** 85 * Returns {@link Looper} for IO operation. 86 * 87 * <p>Declared as package-private. 88 */ 89 Looper getIoLooper() { 90 return mIoThread.getLooper(); 91 } 92 93 /** 94 * Returns {@link Looper} of main thread. Use this {@link Looper} instance 95 * for tasks that are running on main service thread. 96 * 97 * <p>Declared as package-private. 98 */ 99 Looper getServiceLooper() { 100 return mHandler.getLooper(); 101 } 102 103 /** 104 * Add and start a new {@link FeatureAction} to the action queue. 105 * 106 * @param action {@link FeatureAction} to add and start 107 */ 108 void addAndStartAction(final FeatureAction action) { 109 // TODO: may need to check the number of stale actions. 110 runOnServiceThread(new Runnable() { 111 @Override 112 public void run() { 113 mActions.add(action); 114 action.start(); 115 } 116 }); 117 } 118 119 /** 120 * Remove the given {@link FeatureAction} object from the action queue. 121 * 122 * @param action {@link FeatureAction} to remove 123 */ 124 void removeAction(final FeatureAction action) { 125 runOnServiceThread(new Runnable() { 126 @Override 127 public void run() { 128 mActions.remove(action); 129 } 130 }); 131 } 132 133 // Remove all actions matched with the given Class type. 134 private <T extends FeatureAction> void removeAction(final Class<T> clazz) { 135 runOnServiceThread(new Runnable() { 136 @Override 137 public void run() { 138 Iterator<FeatureAction> iter = mActions.iterator(); 139 while (iter.hasNext()) { 140 FeatureAction action = iter.next(); 141 if (action.getClass().equals(clazz)) { 142 action.clear(); 143 mActions.remove(action); 144 } 145 } 146 } 147 }); 148 } 149 150 private void runOnServiceThread(Runnable runnable) { 151 mHandler.post(runnable); 152 } 153 154 /** 155 * Change ARC status into the given {@code enabled} status. 156 * 157 * @return {@code true} if ARC was in "Enabled" status 158 */ 159 boolean setArcStatus(boolean enabled) { 160 boolean oldStatus = mArcStatusEnabled; 161 // 1. Enable/disable ARC circuit. 162 // TODO: call set_audio_return_channel of hal interface. 163 164 // 2. Update arc status; 165 mArcStatusEnabled = enabled; 166 return oldStatus; 167 } 168 169 /** 170 * Transmit a CEC command to CEC bus. 171 * 172 * @param command CEC command to send out 173 * @return {@code true} if succeeds to send command 174 */ 175 boolean sendCecCommand(HdmiCecMessage command) { 176 return mCecController.sendCommand(command); 177 } 178 179 /** 180 * Add a new {@link HdmiCecDeviceInfo} to controller. 181 * 182 * @param deviceInfo new device information object to add 183 */ 184 void addDeviceInfo(HdmiCecDeviceInfo deviceInfo) { 185 // TODO: Implement this. 186 } 187 188 boolean handleCecCommand(HdmiCecMessage message) { 189 // Commands that queries system information replies directly instead 190 // of creating FeatureAction because they are state-less. 191 switch (message.getOpcode()) { 192 case HdmiCec.MESSAGE_GET_MENU_LANGUAGE: 193 handleGetMenuLanguage(message); 194 return true; 195 case HdmiCec.MESSAGE_GIVE_OSD_NAME: 196 handleGiveOsdName(message); 197 return true; 198 case HdmiCec.MESSAGE_GIVE_PHYSICAL_ADDRESS: 199 handleGivePhysicalAddress(message); 200 return true; 201 case HdmiCec.MESSAGE_GIVE_DEVICE_VENDOR_ID: 202 handleGiveDeviceVendorId(message); 203 return true; 204 case HdmiCec.MESSAGE_GET_CEC_VERSION: 205 handleGetCecVersion(message); 206 return true; 207 case HdmiCec.MESSAGE_INITIATE_ARC: 208 handleInitiateArc(message); 209 return true; 210 case HdmiCec.MESSAGE_TERMINATE_ARC: 211 handleTerminateArc(message); 212 return true; 213 // TODO: Add remaining system information query such as 214 // <Give Device Power Status> and <Request Active Source> handler. 215 default: 216 Slog.w(TAG, "Unsupported cec command:" + message.toString()); 217 return false; 218 } 219 } 220 221 /** 222 * Called when a new hotplug event is issued. 223 * 224 * @param port hdmi port number where hot plug event issued. 225 * @param connected whether to be plugged in or not 226 */ 227 void onHotplug(int portNo, boolean connected) { 228 // TODO: Start "RequestArcInitiationAction" if ARC port. 229 } 230 231 private void handleInitiateArc(HdmiCecMessage message){ 232 // In case where <Initiate Arc> is started by <Request ARC Initiation> 233 // need to clean up RequestArcInitiationAction. 234 removeAction(RequestArcInitiationAction.class); 235 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 236 message.getDestination(), message.getSource(), true); 237 addAndStartAction(action); 238 } 239 240 private void handleTerminateArc(HdmiCecMessage message) { 241 // In case where <Terminate Arc> is started by <Request ARC Termination> 242 // need to clean up RequestArcInitiationAction. 243 // TODO: check conditions of power status by calling is_connected api 244 // to be added soon. 245 removeAction(RequestArcTerminationAction.class); 246 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 247 message.getDestination(), message.getSource(), false); 248 addAndStartAction(action); 249 } 250 251 private void handleGetCecVersion(HdmiCecMessage message) { 252 int version = mCecController.getVersion(); 253 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(), 254 message.getSource(), 255 version); 256 sendCecCommand(cecMessage); 257 } 258 259 private void handleGiveDeviceVendorId(HdmiCecMessage message) { 260 int vendorId = mCecController.getVendorId(); 261 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand( 262 message.getDestination(), vendorId); 263 sendCecCommand(cecMessage); 264 } 265 266 private void handleGivePhysicalAddress(HdmiCecMessage message) { 267 int physicalAddress = mCecController.getPhysicalAddress(); 268 int deviceType = HdmiCec.getTypeFromAddress(message.getDestination()); 269 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( 270 message.getDestination(), physicalAddress, deviceType); 271 sendCecCommand(cecMessage); 272 } 273 274 private void handleGiveOsdName(HdmiCecMessage message) { 275 // TODO: read device name from settings or property. 276 String name = HdmiCec.getDefaultDeviceName(message.getDestination()); 277 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand( 278 message.getDestination(), message.getSource(), name); 279 if (cecMessage != null) { 280 sendCecCommand(cecMessage); 281 } else { 282 Slog.w(TAG, "Failed to build <Get Osd Name>:" + name); 283 } 284 } 285 286 private void handleGetMenuLanguage(HdmiCecMessage message) { 287 // Only 0 (TV), 14 (specific use) can answer. 288 if (message.getDestination() != HdmiCec.ADDR_TV 289 && message.getDestination() != HdmiCec.ADDR_SPECIFIC_USE) { 290 Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString()); 291 sendCecCommand( 292 HdmiCecMessageBuilder.buildFeatureAbortCommand(message.getDestination(), 293 message.getSource(), HdmiCec.MESSAGE_GET_MENU_LANGUAGE, 294 HdmiCecMessageBuilder.ABORT_UNRECOGNIZED_MODE)); 295 return; 296 } 297 298 HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand( 299 message.getDestination(), 300 Locale.getDefault().getISO3Language()); 301 // TODO: figure out how to handle failed to get language code. 302 if (command != null) { 303 sendCecCommand(command); 304 } else { 305 Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString()); 306 } 307 } 308} 309