blob: 0f3fc215aef7ae8a6cf395c6c6decb6a7ebf3879 [file] [log] [blame]
Jungshik Jang0792d372014-04-23 17:57:26 +09001/*
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;
Jungshik Janga1fa91f2014-05-08 20:56:41 +090021import android.hardware.hdmi.HdmiCec;
Jinsuk Kimc70d2292014-04-30 15:43:16 +090022import android.hardware.hdmi.HdmiCecDeviceInfo;
23import android.hardware.hdmi.HdmiCecMessage;
Jungshik Jangd643f762014-05-22 19:28:09 +090024import android.hardware.hdmi.IHdmiControlCallback;
25import android.hardware.hdmi.IHdmiControlService;
26import android.hardware.hdmi.IHdmiHotplugEventListener;
Jungshik Jang67ea5212014-05-15 14:05:24 +090027import android.os.Handler;
Jungshik Jang0792d372014-04-23 17:57:26 +090028import android.os.HandlerThread;
Jinsuk Kim78d695d2014-05-13 16:36:15 +090029import android.os.IBinder;
Jungshik Jange9c77c82014-04-24 20:30:09 +090030import android.os.Looper;
Jinsuk Kim78d695d2014-05-13 16:36:15 +090031import android.os.RemoteException;
Jungshik Jang0792d372014-04-23 17:57:26 +090032import android.util.Slog;
Jungshik Jang8b308d92014-05-29 21:52:28 +090033import android.util.SparseIntArray;
Jinsuk Kim78d695d2014-05-13 16:36:15 +090034
35import com.android.internal.annotations.GuardedBy;
Jungshik Jang0792d372014-04-23 17:57:26 +090036import com.android.server.SystemService;
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +090037import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
Jungshik Jang8b308d92014-05-29 21:52:28 +090038import com.android.server.hdmi.HdmiCecLocalDevice.AddressAllocationCallback;
Jungshik Jang0792d372014-04-23 17:57:26 +090039
Jinsuk Kim78d695d2014-05-13 16:36:15 +090040import java.util.ArrayList;
Jungshik Jang67ea5212014-05-15 14:05:24 +090041import java.util.Iterator;
42import java.util.LinkedList;
Jungshik Jang02bb4262014-05-23 16:48:31 +090043import java.util.List;
Jungshik Janga1fa91f2014-05-08 20:56:41 +090044import java.util.Locale;
45
Jungshik Jang0792d372014-04-23 17:57:26 +090046/**
47 * Provides a service for sending and processing HDMI control messages,
48 * HDMI-CEC and MHL control command, and providing the information on both standard.
49 */
50public final class HdmiControlService extends SystemService {
51 private static final String TAG = "HdmiControlService";
52
Jinsuk Kim78d695d2014-05-13 16:36:15 +090053 // TODO: Rename the permission to HDMI_CONTROL.
54 private static final String PERMISSION = "android.permission.HDMI_CEC";
55
Yuncheol Heoece603b2014-05-23 20:10:19 +090056 static final int SEND_RESULT_SUCCESS = 0;
57 static final int SEND_RESULT_NAK = -1;
58 static final int SEND_RESULT_FAILURE = -2;
59
Jungshik Jangd643f762014-05-22 19:28:09 +090060 /**
61 * Interface to report send result.
62 */
63 interface SendMessageCallback {
64 /**
65 * Called when {@link HdmiControlService#sendCecCommand} is completed.
66 *
Yuncheol Heoece603b2014-05-23 20:10:19 +090067 * @param error result of send request.
68 * @see {@link #SEND_RESULT_SUCCESS}
69 * @see {@link #SEND_RESULT_NAK}
70 * @see {@link #SEND_RESULT_FAILURE}
Jungshik Jangd643f762014-05-22 19:28:09 +090071 */
Jungshik Jangd643f762014-05-22 19:28:09 +090072 void onSendCompleted(int error);
73 }
74
Jungshik Jang02bb4262014-05-23 16:48:31 +090075 /**
76 * Interface to get a list of available logical devices.
77 */
78 interface DevicePollingCallback {
79 /**
80 * Called when device polling is finished.
81 *
82 * @param ackedAddress a list of logical addresses of available devices
83 */
84 void onPollingFinished(List<Integer> ackedAddress);
85 }
86
Jungshik Jang0792d372014-04-23 17:57:26 +090087 // A thread to handle synchronous IO of CEC and MHL control service.
88 // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
89 // and sparse call it shares a thread to handle IO operations.
90 private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
91
Jungshik Jang67ea5212014-05-15 14:05:24 +090092 // A collection of FeatureAction.
93 // Note that access to this collection should happen in service thread.
94 private final LinkedList<FeatureAction> mActions = new LinkedList<>();
95
Jinsuk Kim78d695d2014-05-13 16:36:15 +090096 // Used to synchronize the access to the service.
97 private final Object mLock = new Object();
98
99 // Type of logical devices hosted in the system.
100 @GuardedBy("mLock")
101 private final int[] mLocalDevices;
102
103 // List of listeners registered by callers that want to get notified of
104 // hotplug events.
105 private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>();
106
107 // List of records for hotplug event listener to handle the the caller killed in action.
108 private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
109 new ArrayList<>();
110
Jungshik Jang0792d372014-04-23 17:57:26 +0900111 @Nullable
112 private HdmiCecController mCecController;
113
114 @Nullable
115 private HdmiMhlController mMhlController;
116
Jungshik Jang67ea5212014-05-15 14:05:24 +0900117 // Whether ARC is "enabled" or not.
118 // TODO: it may need to hold lock if it's accessed from others.
119 private boolean mArcStatusEnabled = false;
120
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900121 // Whether SystemAudioMode is "On" or not.
122 private boolean mSystemAudioMode;
123
Jungshik Jang67ea5212014-05-15 14:05:24 +0900124 // Handler running on service thread. It's used to run a task in service thread.
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900125 private final Handler mHandler = new Handler();
Jungshik Jang67ea5212014-05-15 14:05:24 +0900126
Jungshik Jang0792d372014-04-23 17:57:26 +0900127 public HdmiControlService(Context context) {
128 super(context);
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900129 mLocalDevices = getContext().getResources().getIntArray(
130 com.android.internal.R.array.config_hdmiCecLogicalDeviceType);
Jungshik Jang0792d372014-04-23 17:57:26 +0900131 }
132
133 @Override
134 public void onStart() {
Jungshik Jang2f51aec2014-05-20 14:37:38 +0900135 mIoThread.start();
Jungshik Jange9c77c82014-04-24 20:30:09 +0900136 mCecController = HdmiCecController.create(this);
Jinsuk Kima8a5e502014-05-15 16:51:49 +0900137 if (mCecController != null) {
Jungshik Jang8b308d92014-05-29 21:52:28 +0900138 mCecController.initializeLocalDevices(mLocalDevices, new AddressAllocationCallback() {
139 private final SparseIntArray mAllocated = new SparseIntArray();
140
141 @Override
142 public void onAddressAllocated(int deviceType, int logicalAddress) {
143 mAllocated.append(deviceType, logicalAddress);
144 // TODO: get HdmiLCecLocalDevice and call onAddressAllocated here.
145
146 // Once all logical allocation is done, launch device discovery
147 // action if one of local device is TV.
148 int tvAddress = mAllocated.get(HdmiCec.DEVICE_TV, -1);
149 if (mLocalDevices.length == mAllocated.size() && tvAddress != -1) {
150 launchDeviceDiscovery(tvAddress);
151 }
152 }
153 });
Jinsuk Kima8a5e502014-05-15 16:51:49 +0900154 } else {
Jungshik Jang0792d372014-04-23 17:57:26 +0900155 Slog.i(TAG, "Device does not support HDMI-CEC.");
156 }
157
Jungshik Jange9c77c82014-04-24 20:30:09 +0900158 mMhlController = HdmiMhlController.create(this);
Jungshik Jang0792d372014-04-23 17:57:26 +0900159 if (mMhlController == null) {
160 Slog.i(TAG, "Device does not support MHL-control.");
161 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900162
Jinsuk Kim8692fc62014-05-29 07:39:22 +0900163 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900164
165 // TODO: Read the preference for SystemAudioMode and initialize mSystemAudioMode and
166 // start to monitor the preference value and invoke SystemAudioActionFromTv if needed.
Jungshik Jang0792d372014-04-23 17:57:26 +0900167 }
Jungshik Jange9c77c82014-04-24 20:30:09 +0900168
169 /**
170 * Returns {@link Looper} for IO operation.
171 *
172 * <p>Declared as package-private.
173 */
174 Looper getIoLooper() {
175 return mIoThread.getLooper();
176 }
177
178 /**
179 * Returns {@link Looper} of main thread. Use this {@link Looper} instance
180 * for tasks that are running on main service thread.
181 *
182 * <p>Declared as package-private.
183 */
184 Looper getServiceLooper() {
Jungshik Jang67ea5212014-05-15 14:05:24 +0900185 return mHandler.getLooper();
Jungshik Jange9c77c82014-04-24 20:30:09 +0900186 }
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900187
188 /**
Jungshik Jang67ea5212014-05-15 14:05:24 +0900189 * Add and start a new {@link FeatureAction} to the action queue.
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900190 *
Jungshik Jang67ea5212014-05-15 14:05:24 +0900191 * @param action {@link FeatureAction} to add and start
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900192 */
Jungshik Jang67ea5212014-05-15 14:05:24 +0900193 void addAndStartAction(final FeatureAction action) {
194 // TODO: may need to check the number of stale actions.
195 runOnServiceThread(new Runnable() {
196 @Override
197 public void run() {
198 mActions.add(action);
199 action.start();
200 }
201 });
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900202 }
203
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +0900204 // See if we have an action of a given type in progress.
205 private <T extends FeatureAction> boolean hasAction(final Class<T> clazz) {
206 for (FeatureAction action : mActions) {
207 if (action.getClass().equals(clazz)) {
208 return true;
209 }
210 }
211 return false;
212 }
213
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900214 /**
215 * Remove the given {@link FeatureAction} object from the action queue.
216 *
Jungshik Jang67ea5212014-05-15 14:05:24 +0900217 * @param action {@link FeatureAction} to remove
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900218 */
Jungshik Jang67ea5212014-05-15 14:05:24 +0900219 void removeAction(final FeatureAction action) {
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900220 assertRunOnServiceThread();
221 mActions.remove(action);
Jungshik Jang67ea5212014-05-15 14:05:24 +0900222 }
223
224 // Remove all actions matched with the given Class type.
225 private <T extends FeatureAction> void removeAction(final Class<T> clazz) {
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900226 removeActionExcept(clazz, null);
227 }
228
229 // Remove all actions matched with the given Class type besides |exception|.
230 <T extends FeatureAction> void removeActionExcept(final Class<T> clazz,
231 final FeatureAction exception) {
232 assertRunOnServiceThread();
233 Iterator<FeatureAction> iter = mActions.iterator();
234 while (iter.hasNext()) {
235 FeatureAction action = iter.next();
236 if (action != exception && action.getClass().equals(clazz)) {
237 action.clear();
238 mActions.remove(action);
Jungshik Jang67ea5212014-05-15 14:05:24 +0900239 }
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900240 }
Jungshik Jang67ea5212014-05-15 14:05:24 +0900241 }
242
243 private void runOnServiceThread(Runnable runnable) {
244 mHandler.post(runnable);
245 }
246
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900247 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
248 mHandler.postAtFrontOfQueue(runnable);
249 }
250
251 private void assertRunOnServiceThread() {
252 if (Looper.myLooper() != mHandler.getLooper()) {
253 throw new IllegalStateException("Should run on service thread.");
254 }
255 }
256
Jungshik Jang67ea5212014-05-15 14:05:24 +0900257 /**
258 * Change ARC status into the given {@code enabled} status.
259 *
260 * @return {@code true} if ARC was in "Enabled" status
261 */
262 boolean setArcStatus(boolean enabled) {
263 boolean oldStatus = mArcStatusEnabled;
264 // 1. Enable/disable ARC circuit.
265 // TODO: call set_audio_return_channel of hal interface.
266
267 // 2. Update arc status;
268 mArcStatusEnabled = enabled;
269 return oldStatus;
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900270 }
271
272 /**
273 * Transmit a CEC command to CEC bus.
274 *
275 * @param command CEC command to send out
Jungshik Jangd643f762014-05-22 19:28:09 +0900276 * @param callback interface used to the result of send command
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900277 */
Jungshik Jangd643f762014-05-22 19:28:09 +0900278 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
279 mCecController.sendCommand(command, callback);
280 }
281
282 void sendCecCommand(HdmiCecMessage command) {
283 mCecController.sendCommand(command, null);
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900284 }
285
286 /**
287 * Add a new {@link HdmiCecDeviceInfo} to controller.
288 *
289 * @param deviceInfo new device information object to add
290 */
291 void addDeviceInfo(HdmiCecDeviceInfo deviceInfo) {
292 // TODO: Implement this.
293 }
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900294
295 boolean handleCecCommand(HdmiCecMessage message) {
296 // Commands that queries system information replies directly instead
297 // of creating FeatureAction because they are state-less.
298 switch (message.getOpcode()) {
299 case HdmiCec.MESSAGE_GET_MENU_LANGUAGE:
300 handleGetMenuLanguage(message);
301 return true;
Jungshik Jangbe9cd8e2014-05-15 14:29:01 +0900302 case HdmiCec.MESSAGE_GIVE_OSD_NAME:
303 handleGiveOsdName(message);
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900304 return true;
305 case HdmiCec.MESSAGE_GIVE_PHYSICAL_ADDRESS:
306 handleGivePhysicalAddress(message);
307 return true;
308 case HdmiCec.MESSAGE_GIVE_DEVICE_VENDOR_ID:
309 handleGiveDeviceVendorId(message);
310 return true;
311 case HdmiCec.MESSAGE_GET_CEC_VERSION:
312 handleGetCecVersion(message);
313 return true;
Jungshik Jang67ea5212014-05-15 14:05:24 +0900314 case HdmiCec.MESSAGE_INITIATE_ARC:
315 handleInitiateArc(message);
316 return true;
317 case HdmiCec.MESSAGE_TERMINATE_ARC:
318 handleTerminateArc(message);
319 return true;
Jungshik Jang8b308d92014-05-29 21:52:28 +0900320 case HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS:
321 handleReportPhysicalAddress(message);
322 return true;
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900323 case HdmiCec.MESSAGE_SET_SYSTEM_AUDIO_MODE:
324 handleSetSystemAudioMode(message);
325 return true;
326 case HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_STATUS:
327 handleSystemAudioModeStatus(message);
328 return true;
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900329 default:
Jinsuk Kimc3923ea2014-05-28 19:11:44 +0900330 return dispatchMessageToAction(message);
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900331 }
332 }
333
Jungshik Jang67ea5212014-05-15 14:05:24 +0900334 /**
335 * Called when a new hotplug event is issued.
336 *
Jungshik Jang8b308d92014-05-29 21:52:28 +0900337 * @param portNo hdmi port number where hot plug event issued.
Jungshik Jang67ea5212014-05-15 14:05:24 +0900338 * @param connected whether to be plugged in or not
339 */
340 void onHotplug(int portNo, boolean connected) {
341 // TODO: Start "RequestArcInitiationAction" if ARC port.
342 }
343
Jungshik Jang02bb4262014-05-23 16:48:31 +0900344 /**
345 * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
346 * devices.
347 *
348 * @param callback an interface used to get a list of all remote devices' address
349 * @param retryCount the number of retry used to send polling message to remote devices
350 */
351 void pollDevices(DevicePollingCallback callback, int retryCount) {
352 mCecController.pollDevices(callback, retryCount);
353 }
354
Jungshik Jang8b308d92014-05-29 21:52:28 +0900355 private void handleReportPhysicalAddress(HdmiCecMessage message) {
356 // At first, try to consume it.
357 if (dispatchMessageToAction(message)) {
358 return;
359 }
360
361 // Ignore if [Device Discovery Action] is on going ignore message.
362 if (hasAction(DeviceDiscoveryAction.class)) {
363 Slog.i(TAG, "Ignore unrecognizable <Report Physical Address> "
364 + "because Device Discovery Action is on-going:" + message);
365 return;
366 }
367
368 // TODO: start new device action.
369 }
370
Jungshik Jang67ea5212014-05-15 14:05:24 +0900371 private void handleInitiateArc(HdmiCecMessage message){
372 // In case where <Initiate Arc> is started by <Request ARC Initiation>
373 // need to clean up RequestArcInitiationAction.
374 removeAction(RequestArcInitiationAction.class);
375 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
376 message.getDestination(), message.getSource(), true);
377 addAndStartAction(action);
378 }
379
380 private void handleTerminateArc(HdmiCecMessage message) {
381 // In case where <Terminate Arc> is started by <Request ARC Termination>
382 // need to clean up RequestArcInitiationAction.
383 // TODO: check conditions of power status by calling is_connected api
384 // to be added soon.
385 removeAction(RequestArcTerminationAction.class);
386 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
387 message.getDestination(), message.getSource(), false);
388 addAndStartAction(action);
389 }
390
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900391 private void handleGetCecVersion(HdmiCecMessage message) {
392 int version = mCecController.getVersion();
393 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(),
394 message.getSource(),
395 version);
396 sendCecCommand(cecMessage);
397 }
398
399 private void handleGiveDeviceVendorId(HdmiCecMessage message) {
400 int vendorId = mCecController.getVendorId();
401 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
402 message.getDestination(), vendorId);
403 sendCecCommand(cecMessage);
404 }
405
406 private void handleGivePhysicalAddress(HdmiCecMessage message) {
407 int physicalAddress = mCecController.getPhysicalAddress();
408 int deviceType = HdmiCec.getTypeFromAddress(message.getDestination());
409 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
410 message.getDestination(), physicalAddress, deviceType);
411 sendCecCommand(cecMessage);
412 }
413
Jungshik Jangbe9cd8e2014-05-15 14:29:01 +0900414 private void handleGiveOsdName(HdmiCecMessage message) {
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900415 // TODO: read device name from settings or property.
416 String name = HdmiCec.getDefaultDeviceName(message.getDestination());
417 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand(
418 message.getDestination(), message.getSource(), name);
419 if (cecMessage != null) {
420 sendCecCommand(cecMessage);
421 } else {
422 Slog.w(TAG, "Failed to build <Get Osd Name>:" + name);
423 }
424 }
425
426 private void handleGetMenuLanguage(HdmiCecMessage message) {
427 // Only 0 (TV), 14 (specific use) can answer.
428 if (message.getDestination() != HdmiCec.ADDR_TV
429 && message.getDestination() != HdmiCec.ADDR_SPECIFIC_USE) {
430 Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString());
431 sendCecCommand(
432 HdmiCecMessageBuilder.buildFeatureAbortCommand(message.getDestination(),
433 message.getSource(), HdmiCec.MESSAGE_GET_MENU_LANGUAGE,
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900434 HdmiConstants.ABORT_UNRECOGNIZED_MODE));
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900435 return;
436 }
437
438 HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand(
439 message.getDestination(),
440 Locale.getDefault().getISO3Language());
441 // TODO: figure out how to handle failed to get language code.
442 if (command != null) {
443 sendCecCommand(command);
444 } else {
445 Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString());
446 }
447 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900448
Jinsuk Kimc3923ea2014-05-28 19:11:44 +0900449 private boolean dispatchMessageToAction(HdmiCecMessage message) {
450 for (FeatureAction action : mActions) {
451 if (action.processCommand(message)) {
452 return true;
453 }
454 }
455 Slog.w(TAG, "Unsupported cec command:" + message);
456 return false;
457 }
458
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900459 private void handleSetSystemAudioMode(HdmiCecMessage message) {
460 if (dispatchMessageToAction(message) || !isMessageForSystemAudio(message)) {
461 return;
462 }
463 SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this,
464 message.getDestination(), message.getSource(),
465 HdmiUtils.parseCommandParamSystemAudioStatus(message));
466 addAndStartAction(action);
467 }
468
469 private void handleSystemAudioModeStatus(HdmiCecMessage message) {
470 if (!isMessageForSystemAudio(message)) {
471 return;
472 }
473 setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message));
474 }
475
476 private boolean isMessageForSystemAudio(HdmiCecMessage message) {
477 if (message.getSource() != HdmiCec.ADDR_AUDIO_SYSTEM
478 || message.getDestination() != HdmiCec.ADDR_TV
479 || getAvrDeviceInfo() == null) {
480 Slog.w(TAG, "Skip abnormal CecMessage: " + message);
481 return false;
482 }
483 return true;
484 }
485
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900486 // Record class that monitors the event of the caller of being killed. Used to clean up
487 // the listener list and record list accordingly.
488 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
489 private final IHdmiHotplugEventListener mListener;
490
491 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
492 mListener = listener;
493 }
494
495 @Override
496 public void binderDied() {
497 synchronized (mLock) {
498 mHotplugEventListenerRecords.remove(this);
499 mHotplugEventListeners.remove(mListener);
500 }
501 }
502 }
503
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900504 void addCecDevice(HdmiCecDeviceInfo info) {
505 mCecController.addDeviceInfo(info);
506 }
507
508 // Launch device discovery sequence.
509 // It starts with clearing the existing device info list.
510 // Note that it assumes that logical address of all local devices is already allocated.
Jungshik Jang8b308d92014-05-29 21:52:28 +0900511 private void launchDeviceDiscovery(int sourceAddress) {
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900512 // At first, clear all existing device infos.
513 mCecController.clearDeviceInfoList();
514
515 // TODO: check whether TV is one of local devices.
Jungshik Jang8b308d92014-05-29 21:52:28 +0900516 DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, sourceAddress,
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900517 new DeviceDiscoveryCallback() {
518 @Override
519 public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) {
520 for (HdmiCecDeviceInfo info : deviceInfos) {
521 mCecController.addDeviceInfo(info);
522 }
523
524 // Add device info of all local devices.
525 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
526 mCecController.addDeviceInfo(device.getDeviceInfo());
527 }
528
529 // TODO: start hot-plug detection sequence here.
530 // addAndStartAction(new HotplugDetectionAction());
531 }
532 });
533 addAndStartAction(action);
534 }
535
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900536 private void enforceAccessPermission() {
537 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
538 }
539
540 private final class BinderService extends IHdmiControlService.Stub {
541 @Override
542 public int[] getSupportedTypes() {
543 enforceAccessPermission();
544 synchronized (mLock) {
545 return mLocalDevices;
546 }
547 }
548
549 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +0900550 public void oneTouchPlay(final IHdmiControlCallback callback) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900551 enforceAccessPermission();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +0900552 runOnServiceThread(new Runnable() {
553 @Override
554 public void run() {
555 HdmiControlService.this.oneTouchPlay(callback);
556 }
557 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900558 }
559
560 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +0900561 public void queryDisplayStatus(final IHdmiControlCallback callback) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900562 enforceAccessPermission();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +0900563 runOnServiceThread(new Runnable() {
564 @Override
565 public void run() {
566 HdmiControlService.this.queryDisplayStatus(callback);
567 }
568 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900569 }
570
571 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +0900572 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900573 enforceAccessPermission();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +0900574 runOnServiceThread(new Runnable() {
575 @Override
576 public void run() {
577 HdmiControlService.this.addHotplugEventListener(listener);
578 }
579 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900580 }
581
582 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +0900583 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900584 enforceAccessPermission();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +0900585 runOnServiceThread(new Runnable() {
586 @Override
587 public void run() {
588 HdmiControlService.this.removeHotplugEventListener(listener);
589 }
590 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900591 }
592 }
593
594 private void oneTouchPlay(IHdmiControlCallback callback) {
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +0900595 if (hasAction(OneTouchPlayAction.class)) {
596 Slog.w(TAG, "oneTouchPlay already in progress");
597 invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS);
598 return;
599 }
600 HdmiCecLocalDevice source = mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK);
601 if (source == null) {
602 Slog.w(TAG, "Local playback device not available");
603 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
604 return;
605 }
606 // TODO: Consider the case of multiple TV sets. For now we always direct the command
607 // to the primary one.
608 OneTouchPlayAction action = OneTouchPlayAction.create(this,
609 source.getDeviceInfo().getLogicalAddress(),
610 source.getDeviceInfo().getPhysicalAddress(), HdmiCec.ADDR_TV, callback);
611 if (action == null) {
612 Slog.w(TAG, "Cannot initiate oneTouchPlay");
613 invokeCallback(callback, HdmiCec.RESULT_EXCEPTION);
614 return;
615 }
616 addAndStartAction(action);
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900617 }
618
619 private void queryDisplayStatus(IHdmiControlCallback callback) {
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +0900620 if (hasAction(DevicePowerStatusAction.class)) {
621 Slog.w(TAG, "queryDisplayStatus already in progress");
622 invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS);
623 return;
624 }
625 HdmiCecLocalDevice source = mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK);
626 if (source == null) {
627 Slog.w(TAG, "Local playback device not available");
628 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
629 return;
630 }
631 DevicePowerStatusAction action = DevicePowerStatusAction.create(this,
632 source.getDeviceInfo().getLogicalAddress(), HdmiCec.ADDR_TV, callback);
633 if (action == null) {
634 Slog.w(TAG, "Cannot initiate queryDisplayStatus");
635 invokeCallback(callback, HdmiCec.RESULT_EXCEPTION);
636 return;
637 }
638 addAndStartAction(action);
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900639 }
640
641 private void addHotplugEventListener(IHdmiHotplugEventListener listener) {
642 HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
643 try {
644 listener.asBinder().linkToDeath(record, 0);
645 } catch (RemoteException e) {
646 Slog.w(TAG, "Listener already died");
647 return;
648 }
649 synchronized (mLock) {
650 mHotplugEventListenerRecords.add(record);
651 mHotplugEventListeners.add(listener);
652 }
653 }
654
655 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
656 synchronized (mLock) {
657 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
658 if (record.mListener.asBinder() == listener.asBinder()) {
659 listener.asBinder().unlinkToDeath(record, 0);
660 mHotplugEventListenerRecords.remove(record);
661 break;
662 }
663 }
664 mHotplugEventListeners.remove(listener);
665 }
666 }
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +0900667
668 private void invokeCallback(IHdmiControlCallback callback, int result) {
669 try {
670 callback.onComplete(result);
671 } catch (RemoteException e) {
672 Slog.e(TAG, "Invoking callback failed:" + e);
673 }
674 }
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900675
676 HdmiCecDeviceInfo getAvrDeviceInfo() {
677 return mCecController.getDeviceInfo(HdmiCec.ADDR_AUDIO_SYSTEM);
678 }
679
680 void setSystemAudioMode(boolean newMode) {
681 assertRunOnServiceThread();
682 if (newMode != mSystemAudioMode) {
683 // TODO: Need to set the preference for SystemAudioMode.
684 // TODO: Need to handle the notification of changing the mode and
685 // to identify the notification should be handled in the service or TvSettings.
686 mSystemAudioMode = newMode;
687 }
688 }
689
690 boolean getSystemAudioMode() {
691 assertRunOnServiceThread();
692 return mSystemAudioMode;
693 }
694
695 void setAudioStatus(boolean mute, int volume) {
696 // TODO: Hook up with AudioManager.
697 }
698
699 boolean isInPresetInstallationMode() {
700 // TODO: Implement this.
701 return false;
702 }
Jungshik Jang0792d372014-04-23 17:57:26 +0900703}