HdmiControlService.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.annotation.Nullable; 20import android.content.Context; 21import android.hardware.hdmi.HdmiCec; 22import android.hardware.hdmi.HdmiCecDeviceInfo; 23import android.hardware.hdmi.HdmiCecMessage; 24import android.hardware.hdmi.IHdmiControlCallback; 25import android.hardware.hdmi.IHdmiControlService; 26import android.hardware.hdmi.IHdmiHotplugEventListener; 27import android.os.Handler; 28import android.os.HandlerThread; 29import android.os.IBinder; 30import android.os.Looper; 31import android.os.RemoteException; 32import android.util.Slog; 33 34import com.android.internal.annotations.GuardedBy; 35import com.android.server.SystemService; 36 37import java.util.ArrayList; 38import java.util.Iterator; 39import java.util.LinkedList; 40import java.util.Locale; 41 42/** 43 * Provides a service for sending and processing HDMI control messages, 44 * HDMI-CEC and MHL control command, and providing the information on both standard. 45 */ 46public final class HdmiControlService extends SystemService { 47 private static final String TAG = "HdmiControlService"; 48 49 // TODO: Rename the permission to HDMI_CONTROL. 50 private static final String PERMISSION = "android.permission.HDMI_CEC"; 51 52 static final int SEND_RESULT_SUCCESS = 0; 53 static final int SEND_RESULT_NAK = -1; 54 static final int SEND_RESULT_FAILURE = -2; 55 56 /** 57 * Interface to report send result. 58 */ 59 interface SendMessageCallback { 60 /** 61 * Called when {@link HdmiControlService#sendCecCommand} is completed. 62 * 63 * @param error result of send request. 64 * @see {@link #SEND_RESULT_SUCCESS} 65 * @see {@link #SEND_RESULT_NAK} 66 * @see {@link #SEND_RESULT_FAILURE} 67 */ 68 void onSendCompleted(int error); 69 } 70 71 // A thread to handle synchronous IO of CEC and MHL control service. 72 // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms) 73 // and sparse call it shares a thread to handle IO operations. 74 private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread"); 75 76 // A collection of FeatureAction. 77 // Note that access to this collection should happen in service thread. 78 private final LinkedList<FeatureAction> mActions = new LinkedList<>(); 79 80 // Used to synchronize the access to the service. 81 private final Object mLock = new Object(); 82 83 // Type of logical devices hosted in the system. 84 @GuardedBy("mLock") 85 private final int[] mLocalDevices; 86 87 // List of listeners registered by callers that want to get notified of 88 // hotplug events. 89 private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>(); 90 91 // List of records for hotplug event listener to handle the the caller killed in action. 92 private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords = 93 new ArrayList<>(); 94 95 @Nullable 96 private HdmiCecController mCecController; 97 98 @Nullable 99 private HdmiMhlController mMhlController; 100 101 // Whether ARC is "enabled" or not. 102 // TODO: it may need to hold lock if it's accessed from others. 103 private boolean mArcStatusEnabled = false; 104 105 // Handler running on service thread. It's used to run a task in service thread. 106 private Handler mHandler = new Handler(); 107 108 public HdmiControlService(Context context) { 109 super(context); 110 mLocalDevices = getContext().getResources().getIntArray( 111 com.android.internal.R.array.config_hdmiCecLogicalDeviceType); 112 } 113 114 @Override 115 public void onStart() { 116 mIoThread.start(); 117 mCecController = HdmiCecController.create(this); 118 if (mCecController != null) { 119 mCecController.initializeLocalDevices(mLocalDevices); 120 } else { 121 Slog.i(TAG, "Device does not support HDMI-CEC."); 122 } 123 124 mMhlController = HdmiMhlController.create(this); 125 if (mMhlController == null) { 126 Slog.i(TAG, "Device does not support MHL-control."); 127 } 128 129 // TODO: Publish the BinderService 130 // publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); 131 } 132 133 /** 134 * Returns {@link Looper} for IO operation. 135 * 136 * <p>Declared as package-private. 137 */ 138 Looper getIoLooper() { 139 return mIoThread.getLooper(); 140 } 141 142 /** 143 * Returns {@link Looper} of main thread. Use this {@link Looper} instance 144 * for tasks that are running on main service thread. 145 * 146 * <p>Declared as package-private. 147 */ 148 Looper getServiceLooper() { 149 return mHandler.getLooper(); 150 } 151 152 /** 153 * Add and start a new {@link FeatureAction} to the action queue. 154 * 155 * @param action {@link FeatureAction} to add and start 156 */ 157 void addAndStartAction(final FeatureAction action) { 158 // TODO: may need to check the number of stale actions. 159 runOnServiceThread(new Runnable() { 160 @Override 161 public void run() { 162 mActions.add(action); 163 action.start(); 164 } 165 }); 166 } 167 168 /** 169 * Remove the given {@link FeatureAction} object from the action queue. 170 * 171 * @param action {@link FeatureAction} to remove 172 */ 173 void removeAction(final FeatureAction action) { 174 runOnServiceThread(new Runnable() { 175 @Override 176 public void run() { 177 mActions.remove(action); 178 } 179 }); 180 } 181 182 // Remove all actions matched with the given Class type. 183 private <T extends FeatureAction> void removeAction(final Class<T> clazz) { 184 runOnServiceThread(new Runnable() { 185 @Override 186 public void run() { 187 Iterator<FeatureAction> iter = mActions.iterator(); 188 while (iter.hasNext()) { 189 FeatureAction action = iter.next(); 190 if (action.getClass().equals(clazz)) { 191 action.clear(); 192 mActions.remove(action); 193 } 194 } 195 } 196 }); 197 } 198 199 private void runOnServiceThread(Runnable runnable) { 200 mHandler.post(runnable); 201 } 202 203 /** 204 * Change ARC status into the given {@code enabled} status. 205 * 206 * @return {@code true} if ARC was in "Enabled" status 207 */ 208 boolean setArcStatus(boolean enabled) { 209 boolean oldStatus = mArcStatusEnabled; 210 // 1. Enable/disable ARC circuit. 211 // TODO: call set_audio_return_channel of hal interface. 212 213 // 2. Update arc status; 214 mArcStatusEnabled = enabled; 215 return oldStatus; 216 } 217 218 /** 219 * Transmit a CEC command to CEC bus. 220 * 221 * @param command CEC command to send out 222 * @param callback interface used to the result of send command 223 */ 224 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) { 225 mCecController.sendCommand(command, callback); 226 } 227 228 void sendCecCommand(HdmiCecMessage command) { 229 mCecController.sendCommand(command, null); 230 } 231 232 /** 233 * Add a new {@link HdmiCecDeviceInfo} to controller. 234 * 235 * @param deviceInfo new device information object to add 236 */ 237 void addDeviceInfo(HdmiCecDeviceInfo deviceInfo) { 238 // TODO: Implement this. 239 } 240 241 boolean handleCecCommand(HdmiCecMessage message) { 242 // Commands that queries system information replies directly instead 243 // of creating FeatureAction because they are state-less. 244 switch (message.getOpcode()) { 245 case HdmiCec.MESSAGE_GET_MENU_LANGUAGE: 246 handleGetMenuLanguage(message); 247 return true; 248 case HdmiCec.MESSAGE_GIVE_OSD_NAME: 249 handleGiveOsdName(message); 250 return true; 251 case HdmiCec.MESSAGE_GIVE_PHYSICAL_ADDRESS: 252 handleGivePhysicalAddress(message); 253 return true; 254 case HdmiCec.MESSAGE_GIVE_DEVICE_VENDOR_ID: 255 handleGiveDeviceVendorId(message); 256 return true; 257 case HdmiCec.MESSAGE_GET_CEC_VERSION: 258 handleGetCecVersion(message); 259 return true; 260 case HdmiCec.MESSAGE_INITIATE_ARC: 261 handleInitiateArc(message); 262 return true; 263 case HdmiCec.MESSAGE_TERMINATE_ARC: 264 handleTerminateArc(message); 265 return true; 266 // TODO: Add remaining system information query such as 267 // <Give Device Power Status> and <Request Active Source> handler. 268 default: 269 Slog.w(TAG, "Unsupported cec command:" + message.toString()); 270 return false; 271 } 272 } 273 274 /** 275 * Called when a new hotplug event is issued. 276 * 277 * @param port hdmi port number where hot plug event issued. 278 * @param connected whether to be plugged in or not 279 */ 280 void onHotplug(int portNo, boolean connected) { 281 // TODO: Start "RequestArcInitiationAction" if ARC port. 282 } 283 284 private void handleInitiateArc(HdmiCecMessage message){ 285 // In case where <Initiate Arc> is started by <Request ARC Initiation> 286 // need to clean up RequestArcInitiationAction. 287 removeAction(RequestArcInitiationAction.class); 288 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 289 message.getDestination(), message.getSource(), true); 290 addAndStartAction(action); 291 } 292 293 private void handleTerminateArc(HdmiCecMessage message) { 294 // In case where <Terminate Arc> is started by <Request ARC Termination> 295 // need to clean up RequestArcInitiationAction. 296 // TODO: check conditions of power status by calling is_connected api 297 // to be added soon. 298 removeAction(RequestArcTerminationAction.class); 299 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 300 message.getDestination(), message.getSource(), false); 301 addAndStartAction(action); 302 } 303 304 private void handleGetCecVersion(HdmiCecMessage message) { 305 int version = mCecController.getVersion(); 306 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(), 307 message.getSource(), 308 version); 309 sendCecCommand(cecMessage); 310 } 311 312 private void handleGiveDeviceVendorId(HdmiCecMessage message) { 313 int vendorId = mCecController.getVendorId(); 314 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand( 315 message.getDestination(), vendorId); 316 sendCecCommand(cecMessage); 317 } 318 319 private void handleGivePhysicalAddress(HdmiCecMessage message) { 320 int physicalAddress = mCecController.getPhysicalAddress(); 321 int deviceType = HdmiCec.getTypeFromAddress(message.getDestination()); 322 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( 323 message.getDestination(), physicalAddress, deviceType); 324 sendCecCommand(cecMessage); 325 } 326 327 private void handleGiveOsdName(HdmiCecMessage message) { 328 // TODO: read device name from settings or property. 329 String name = HdmiCec.getDefaultDeviceName(message.getDestination()); 330 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand( 331 message.getDestination(), message.getSource(), name); 332 if (cecMessage != null) { 333 sendCecCommand(cecMessage); 334 } else { 335 Slog.w(TAG, "Failed to build <Get Osd Name>:" + name); 336 } 337 } 338 339 private void handleGetMenuLanguage(HdmiCecMessage message) { 340 // Only 0 (TV), 14 (specific use) can answer. 341 if (message.getDestination() != HdmiCec.ADDR_TV 342 && message.getDestination() != HdmiCec.ADDR_SPECIFIC_USE) { 343 Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString()); 344 sendCecCommand( 345 HdmiCecMessageBuilder.buildFeatureAbortCommand(message.getDestination(), 346 message.getSource(), HdmiCec.MESSAGE_GET_MENU_LANGUAGE, 347 HdmiCecMessageBuilder.ABORT_UNRECOGNIZED_MODE)); 348 return; 349 } 350 351 HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand( 352 message.getDestination(), 353 Locale.getDefault().getISO3Language()); 354 // TODO: figure out how to handle failed to get language code. 355 if (command != null) { 356 sendCecCommand(command); 357 } else { 358 Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString()); 359 } 360 } 361 362 // Record class that monitors the event of the caller of being killed. Used to clean up 363 // the listener list and record list accordingly. 364 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { 365 private final IHdmiHotplugEventListener mListener; 366 367 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) { 368 mListener = listener; 369 } 370 371 @Override 372 public void binderDied() { 373 synchronized (mLock) { 374 mHotplugEventListenerRecords.remove(this); 375 mHotplugEventListeners.remove(mListener); 376 } 377 } 378 } 379 380 private void enforceAccessPermission() { 381 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); 382 } 383 384 private final class BinderService extends IHdmiControlService.Stub { 385 @Override 386 public int[] getSupportedTypes() { 387 enforceAccessPermission(); 388 synchronized (mLock) { 389 return mLocalDevices; 390 } 391 } 392 393 @Override 394 public void oneTouchPlay(IHdmiControlCallback callback) { 395 enforceAccessPermission(); 396 // TODO: Post a message for HdmiControlService#oneTouchPlay() 397 } 398 399 @Override 400 public void queryDisplayStatus(IHdmiControlCallback callback) { 401 enforceAccessPermission(); 402 // TODO: Post a message for HdmiControlService#queryDisplayStatus() 403 } 404 405 @Override 406 public void addHotplugEventListener(IHdmiHotplugEventListener listener) { 407 enforceAccessPermission(); 408 // TODO: Post a message for HdmiControlService#addHotplugEventListener() 409 } 410 411 @Override 412 public void removeHotplugEventListener(IHdmiHotplugEventListener listener) { 413 enforceAccessPermission(); 414 // TODO: Post a message for HdmiControlService#removeHotplugEventListener() 415 } 416 } 417 418 private void oneTouchPlay(IHdmiControlCallback callback) { 419 // TODO: Create a new action 420 } 421 422 private void queryDisplayStatus(IHdmiControlCallback callback) { 423 // TODO: Create a new action 424 } 425 426 private void addHotplugEventListener(IHdmiHotplugEventListener listener) { 427 HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); 428 try { 429 listener.asBinder().linkToDeath(record, 0); 430 } catch (RemoteException e) { 431 Slog.w(TAG, "Listener already died"); 432 return; 433 } 434 synchronized (mLock) { 435 mHotplugEventListenerRecords.add(record); 436 mHotplugEventListeners.add(listener); 437 } 438 } 439 440 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) { 441 synchronized (mLock) { 442 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 443 if (record.mListener.asBinder() == listener.asBinder()) { 444 listener.asBinder().unlinkToDeath(record, 0); 445 mHotplugEventListenerRecords.remove(record); 446 break; 447 } 448 } 449 mHotplugEventListeners.remove(listener); 450 } 451 } 452} 453