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