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