blob: 2cbc1b9cdd77472e3471e610ddd5a2391a46ffb5 [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
Jinsuk Kimed086452014-08-18 15:01:53 +090019import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE;
20import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE;
Jinsuk Kim50084862014-08-07 13:11:40 +090021import static com.android.server.hdmi.Constants.DISABLED;
22import static com.android.server.hdmi.Constants.ENABLED;
Jinsuk Kim50084862014-08-07 13:11:40 +090023import static com.android.server.hdmi.Constants.OPTION_CEC_AUTO_WAKEUP;
24import static com.android.server.hdmi.Constants.OPTION_CEC_ENABLE;
25import static com.android.server.hdmi.Constants.OPTION_CEC_SERVICE_CONTROL;
Jinsuk Kim5b8cb002015-01-19 07:30:12 +090026import static com.android.server.hdmi.Constants.OPTION_CEC_SET_LANGUAGE;
Jinsuk Kim50084862014-08-07 13:11:40 +090027import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE;
28import static com.android.server.hdmi.Constants.OPTION_MHL_INPUT_SWITCHING;
29import static com.android.server.hdmi.Constants.OPTION_MHL_POWER_CHARGE;
Jinsuk Kim5b8cb002015-01-19 07:30:12 +090030import static com.android.server.hdmi.Constants.OPTION_MHL_SERVICE_CONTROL;
Jinsuk Kim50084862014-08-07 13:11:40 +090031
Jungshik Jang0792d372014-04-23 17:57:26 +090032import android.annotation.Nullable;
Yuncheol Heo38db6292014-07-01 14:15:14 +090033import android.content.BroadcastReceiver;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +090034import android.content.ContentResolver;
Jungshik Jang0792d372014-04-23 17:57:26 +090035import android.content.Context;
Yuncheol Heo38db6292014-07-01 14:15:14 +090036import android.content.Intent;
37import android.content.IntentFilter;
Jinsuk Kim50084862014-08-07 13:11:40 +090038import android.database.ContentObserver;
Jinsuk Kimc0c20d02014-07-04 14:34:31 +090039import android.hardware.hdmi.HdmiControlManager;
Yuncheol Heo7d9acc72014-08-12 15:30:49 +090040import android.hardware.hdmi.HdmiDeviceInfo;
Jungshik Jang60cffce2014-06-12 18:03:04 +090041import android.hardware.hdmi.HdmiHotplugEvent;
Jinsuk Kim0340bbc2014-06-05 11:07:47 +090042import android.hardware.hdmi.HdmiPortInfo;
Jungshik Jangd643f762014-05-22 19:28:09 +090043import android.hardware.hdmi.IHdmiControlCallback;
44import android.hardware.hdmi.IHdmiControlService;
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +090045import android.hardware.hdmi.IHdmiDeviceEventListener;
Jungshik Jangd643f762014-05-22 19:28:09 +090046import android.hardware.hdmi.IHdmiHotplugEventListener;
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +090047import android.hardware.hdmi.IHdmiInputChangeListener;
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +090048import android.hardware.hdmi.IHdmiMhlVendorCommandListener;
Jungshik Jang12e5dce2014-07-24 15:27:44 +090049import android.hardware.hdmi.IHdmiRecordListener;
Jungshik Jangea67c182014-06-19 22:19:20 +090050import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
Jinsuk Kim119160a2014-07-07 18:48:10 +090051import android.hardware.hdmi.IHdmiVendorCommandListener;
Jungshik Janga858d222014-06-23 17:17:47 +090052import android.media.AudioManager;
Jinsuk Kim7fa3a662014-11-07 15:20:24 +090053import android.media.tv.TvInputManager;
54import android.media.tv.TvInputManager.TvInputCallback;
Jinsuk Kim50084862014-08-07 13:11:40 +090055import android.net.Uri;
Jungshik Jang42c98002014-06-12 13:17:44 +090056import android.os.Build;
Jungshik Jang67ea5212014-05-15 14:05:24 +090057import android.os.Handler;
Jungshik Jang0792d372014-04-23 17:57:26 +090058import android.os.HandlerThread;
Jinsuk Kim78d695d2014-05-13 16:36:15 +090059import android.os.IBinder;
Jungshik Jange9c77c82014-04-24 20:30:09 +090060import android.os.Looper;
Yuncheol Heo38db6292014-07-01 14:15:14 +090061import android.os.PowerManager;
Jinsuk Kim78d695d2014-05-13 16:36:15 +090062import android.os.RemoteException;
Yuncheol Heo38db6292014-07-01 14:15:14 +090063import android.os.SystemClock;
Yuncheol Heo7d9acc72014-08-12 15:30:49 +090064import android.os.SystemProperties;
Jinsuk Kim50084862014-08-07 13:11:40 +090065import android.os.UserHandle;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +090066import android.provider.Settings.Global;
Yuncheol Heo7d9acc72014-08-12 15:30:49 +090067import android.text.TextUtils;
Jinsuk Kim2b152012014-07-25 08:22:26 +090068import android.util.ArraySet;
Jungshik Jang0792d372014-04-23 17:57:26 +090069import android.util.Slog;
Jungshik Jang3ee65722014-06-03 16:22:30 +090070import android.util.SparseArray;
Jungshik Jang8b308d92014-05-29 21:52:28 +090071import android.util.SparseIntArray;
Jinsuk Kim78d695d2014-05-13 16:36:15 +090072
Jinsuk Kim4893c7e2014-06-19 14:13:22 +090073import com.android.internal.annotations.GuardedBy;
Terry Heo959d2db2014-08-28 16:45:41 +090074import com.android.internal.util.IndentingPrintWriter;
Jungshik Jang0792d372014-04-23 17:57:26 +090075import com.android.server.SystemService;
Jungshik Janga5b74142014-06-23 18:03:10 +090076import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
Jungshik Jang3ee65722014-06-03 16:22:30 +090077import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
Jinsuk Kim7e742062014-07-30 13:19:13 +090078import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
Jungshik Jang4fc1d102014-07-09 19:24:50 +090079import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback;
Jungshik Jang0792d372014-04-23 17:57:26 +090080
Jungshik Jangb6591b82014-07-23 16:10:23 +090081import libcore.util.EmptyArray;
82
Terry Heo959d2db2014-08-28 16:45:41 +090083import java.io.FileDescriptor;
84import java.io.PrintWriter;
Jinsuk Kim78d695d2014-05-13 16:36:15 +090085import java.util.ArrayList;
Jinsuk Kimf4eb72d2014-07-25 13:02:51 +090086import java.util.Arrays;
Jinsuk Kim0340bbc2014-06-05 11:07:47 +090087import java.util.Collections;
Jungshik Jang02bb4262014-05-23 16:48:31 +090088import java.util.List;
Terry Heo1ca0a432014-08-18 10:30:32 +090089import java.util.Locale;
Jungshik Janga1fa91f2014-05-08 20:56:41 +090090
Jungshik Jang0792d372014-04-23 17:57:26 +090091/**
92 * Provides a service for sending and processing HDMI control messages,
93 * HDMI-CEC and MHL control command, and providing the information on both standard.
94 */
95public final class HdmiControlService extends SystemService {
96 private static final String TAG = "HdmiControlService";
Jinsuk Kim5fe3a6c2014-10-29 07:02:06 +090097 private final Locale HONG_KONG = new Locale("zh", "HK");
98 private final Locale MACAU = new Locale("zh", "MO");
Jungshik Jang0792d372014-04-23 17:57:26 +090099
Jinsuk Kimc7eba0f2014-07-07 14:18:02 +0900100 static final String PERMISSION = "android.permission.HDMI_CEC";
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900101
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900102 // The reason code to initiate intializeCec().
103 static final int INITIATED_BY_ENABLE_CEC = 0;
104 static final int INITIATED_BY_BOOT_UP = 1;
105 static final int INITIATED_BY_SCREEN_ON = 2;
106 static final int INITIATED_BY_WAKE_UP_MESSAGE = 3;
Yuncheol Heob5021862014-09-02 10:36:04 +0900107 static final int INITIATED_BY_HOTPLUG = 4;
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900108
Jungshik Jangd643f762014-05-22 19:28:09 +0900109 /**
110 * Interface to report send result.
111 */
112 interface SendMessageCallback {
113 /**
114 * Called when {@link HdmiControlService#sendCecCommand} is completed.
115 *
Yuncheol Heoece603b2014-05-23 20:10:19 +0900116 * @param error result of send request.
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900117 * <ul>
118 * <li>{@link Constants#SEND_RESULT_SUCCESS}
119 * <li>{@link Constants#SEND_RESULT_NAK}
120 * <li>{@link Constants#SEND_RESULT_FAILURE}
121 * </ul>
Jungshik Jangd643f762014-05-22 19:28:09 +0900122 */
Jungshik Jangd643f762014-05-22 19:28:09 +0900123 void onSendCompleted(int error);
124 }
125
Jungshik Jang02bb4262014-05-23 16:48:31 +0900126 /**
127 * Interface to get a list of available logical devices.
128 */
129 interface DevicePollingCallback {
130 /**
131 * Called when device polling is finished.
132 *
133 * @param ackedAddress a list of logical addresses of available devices
134 */
135 void onPollingFinished(List<Integer> ackedAddress);
136 }
137
Terry Heo1ca0a432014-08-18 10:30:32 +0900138 private class HdmiControlBroadcastReceiver extends BroadcastReceiver {
Jungshik Jangf67113f2014-08-22 16:27:19 +0900139 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +0900140 @Override
141 public void onReceive(Context context, Intent intent) {
Jungshik Jangf67113f2014-08-22 16:27:19 +0900142 assertRunOnServiceThread();
Yuncheol Heo38db6292014-07-01 14:15:14 +0900143 switch (intent.getAction()) {
144 case Intent.ACTION_SCREEN_OFF:
145 if (isPowerOnOrTransient()) {
146 onStandby();
147 }
148 break;
149 case Intent.ACTION_SCREEN_ON:
150 if (isPowerStandbyOrTransient()) {
151 onWakeUp();
152 }
153 break;
Terry Heo1ca0a432014-08-18 10:30:32 +0900154 case Intent.ACTION_CONFIGURATION_CHANGED:
Jinsuk Kim5fe3a6c2014-10-29 07:02:06 +0900155 String language = getMenuLanguage();
Terry Heo1ca0a432014-08-18 10:30:32 +0900156 if (!mLanguage.equals(language)) {
157 onLanguageChanged(language);
158 }
159 break;
Yuncheol Heo38db6292014-07-01 14:15:14 +0900160 }
161 }
Jinsuk Kim5fe3a6c2014-10-29 07:02:06 +0900162
163 private String getMenuLanguage() {
164 Locale locale = Locale.getDefault();
165 if (locale.equals(Locale.TAIWAN) || locale.equals(HONG_KONG) || locale.equals(MACAU)) {
166 // Android always returns "zho" for all Chinese variants.
167 // Use "bibliographic" code defined in CEC639-2 for traditional
168 // Chinese used in Taiwan/Hong Kong/Macau.
169 return "chi";
170 } else {
171 return locale.getISO3Language();
172 }
173 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900174 }
175
Jungshik Jang0792d372014-04-23 17:57:26 +0900176 // A thread to handle synchronous IO of CEC and MHL control service.
177 // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
178 // and sparse call it shares a thread to handle IO operations.
179 private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
180
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900181 // Used to synchronize the access to the service.
182 private final Object mLock = new Object();
183
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900184 // Type of logical devices hosted in the system. Stored in the unmodifiable list.
185 private final List<Integer> mLocalDevices;
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900186
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900187 // List of records for hotplug event listener to handle the the caller killed in action.
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900188 @GuardedBy("mLock")
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900189 private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
190 new ArrayList<>();
191
Jungshik Jangf4249322014-08-21 14:17:05 +0900192 // List of records for device event listener to handle the caller killed in action.
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900193 @GuardedBy("mLock")
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +0900194 private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
195 new ArrayList<>();
196
Jungshik Jangf4249322014-08-21 14:17:05 +0900197 // List of records for vendor command listener to handle the caller killed in action.
Jinsuk Kim119160a2014-07-07 18:48:10 +0900198 @GuardedBy("mLock")
199 private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords =
200 new ArrayList<>();
201
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +0900202 @GuardedBy("mLock")
203 private InputChangeListenerRecord mInputChangeListenerRecord;
204
Jungshik Jangb6591b82014-07-23 16:10:23 +0900205 @GuardedBy("mLock")
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900206 private HdmiRecordListenerRecord mRecordListenerRecord;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900207
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900208 // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
209 // handling will be disabled and no request will be handled.
210 @GuardedBy("mLock")
211 private boolean mHdmiControlEnabled;
212
Jinsuk Kim4d43d932014-07-03 16:43:58 +0900213 // Set to true while the service is in normal mode. While set to false, no input change is
214 // allowed. Used for situations where input change can confuse users such as channel auto-scan,
215 // system upgrade, etc., a.k.a. "prohibit mode".
216 @GuardedBy("mLock")
217 private boolean mProhibitMode;
218
Jungshik Jangea67c182014-06-19 22:19:20 +0900219 // List of records for system audio mode change to handle the the caller killed in action.
220 private final ArrayList<SystemAudioModeChangeListenerRecord>
221 mSystemAudioModeChangeListenerRecords = new ArrayList<>();
222
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900223 // Handler used to run a task in service thread.
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900224 private final Handler mHandler = new Handler();
225
Jinsuk Kim50084862014-08-07 13:11:40 +0900226 private final SettingsObserver mSettingsObserver;
227
Jungshik Jangf4249322014-08-21 14:17:05 +0900228 private final HdmiControlBroadcastReceiver
229 mHdmiControlBroadcastReceiver = new HdmiControlBroadcastReceiver();
230
Jungshik Jang0792d372014-04-23 17:57:26 +0900231 @Nullable
232 private HdmiCecController mCecController;
233
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900234 // HDMI port information. Stored in the unmodifiable list to keep the static information
235 // from being modified.
236 private List<HdmiPortInfo> mPortInfo;
237
Jinsuk Kim2b152012014-07-25 08:22:26 +0900238 // Map from path(physical address) to port ID.
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900239 private UnmodifiableSparseIntArray mPortIdMap;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900240
241 // Map from port ID to HdmiPortInfo.
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900242 private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900243
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900244 // Map from port ID to HdmiDeviceInfo.
245 private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap;
246
Yuncheol Heo75a77e72014-07-09 18:27:53 +0900247 private HdmiCecMessageValidator mMessageValidator;
248
Yuncheol Heo38db6292014-07-01 14:15:14 +0900249 @ServiceThreadOnly
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900250 private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +0900251
252 @ServiceThreadOnly
Terry Heo1ca0a432014-08-18 10:30:32 +0900253 private String mLanguage = Locale.getDefault().getISO3Language();
254
255 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +0900256 private boolean mStandbyMessageReceived = false;
257
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900258 @ServiceThreadOnly
259 private boolean mWakeUpMessageReceived = false;
260
Jungshik Jang867b4e02014-08-12 13:41:30 +0900261 @ServiceThreadOnly
262 private int mActivePortId = Constants.INVALID_PORT_ID;
263
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900264 // Set to true while the input change by MHL is allowed.
265 @GuardedBy("mLock")
266 private boolean mMhlInputChangeEnabled;
267
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +0900268 // List of records for MHL Vendor command listener to handle the caller killed in action.
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900269 @GuardedBy("mLock")
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +0900270 private final ArrayList<HdmiMhlVendorCommandListenerRecord>
271 mMhlVendorCommandListenerRecords = new ArrayList<>();
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900272
273 @GuardedBy("mLock")
274 private List<HdmiDeviceInfo> mMhlDevices;
275
276 @Nullable
Jinsuk Kim78104122014-08-26 19:32:34 +0900277 private HdmiMhlControllerStub mMhlController;
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900278
Jinsuk Kim7fa3a662014-11-07 15:20:24 +0900279 @Nullable
280 private TvInputManager mTvInputManager;
281
Jinsuk Kime26d8332015-01-09 08:55:41 +0900282 @Nullable
283 private PowerManager mPowerManager;
284
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900285 // Last input port before switching to the MHL port. Should switch back to this port
286 // when the mobile device sends the request one touch play with off.
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900287 // Gets invalidated if we go to other port/input.
288 @ServiceThreadOnly
289 private int mLastInputMhl = Constants.INVALID_PORT_ID;
290
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900291 // Set to true if the logical address allocation is completed.
292 private boolean mAddressAllocated = false;
293
294 // Buffer for processing the incoming cec messages while allocating logical addresses.
295 private final class CecMessageBuffer {
296 private List<HdmiCecMessage> mBuffer = new ArrayList<>();
297
298 public void bufferMessage(HdmiCecMessage message) {
299 switch (message.getOpcode()) {
300 case Constants.MESSAGE_ACTIVE_SOURCE:
301 bufferActiveSource(message);
302 break;
303 case Constants.MESSAGE_IMAGE_VIEW_ON:
304 case Constants.MESSAGE_TEXT_VIEW_ON:
305 bufferImageOrTextViewOn(message);
306 break;
307 // Add here if new message that needs to buffer
308 default:
309 // Do not need to buffer messages other than above
310 break;
311 }
312 }
313
314 public void processMessages() {
315 for (final HdmiCecMessage message : mBuffer) {
316 runOnServiceThread(new Runnable() {
317 @Override
318 public void run() {
319 handleCecCommand(message);
320 }
321 });
322 }
323 mBuffer.clear();
324 }
325
326 private void bufferActiveSource(HdmiCecMessage message) {
327 if (!replaceMessageIfBuffered(message, Constants.MESSAGE_ACTIVE_SOURCE)) {
328 mBuffer.add(message);
329 }
330 }
331
332 private void bufferImageOrTextViewOn(HdmiCecMessage message) {
333 if (!replaceMessageIfBuffered(message, Constants.MESSAGE_IMAGE_VIEW_ON) &&
334 !replaceMessageIfBuffered(message, Constants.MESSAGE_TEXT_VIEW_ON)) {
335 mBuffer.add(message);
336 }
337 }
338
339 // Returns true if the message is replaced
340 private boolean replaceMessageIfBuffered(HdmiCecMessage message, int opcode) {
341 for (int i = 0; i < mBuffer.size(); i++) {
342 HdmiCecMessage bufferedMessage = mBuffer.get(i);
343 if (bufferedMessage.getOpcode() == opcode) {
344 mBuffer.set(i, message);
345 return true;
346 }
347 }
348 return false;
349 }
350 }
351
352 private CecMessageBuffer mCecMessageBuffer = new CecMessageBuffer();
353
Jungshik Jang0792d372014-04-23 17:57:26 +0900354 public HdmiControlService(Context context) {
355 super(context);
Yuncheol Heo7d9acc72014-08-12 15:30:49 +0900356 mLocalDevices = getIntList(SystemProperties.get(Constants.PROPERTY_DEVICE_TYPE));
Jinsuk Kim50084862014-08-07 13:11:40 +0900357 mSettingsObserver = new SettingsObserver(mHandler);
Jungshik Jang0792d372014-04-23 17:57:26 +0900358 }
359
Yuncheol Heo7d9acc72014-08-12 15:30:49 +0900360 private static List<Integer> getIntList(String string) {
361 ArrayList<Integer> list = new ArrayList<>();
362 TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
363 splitter.setString(string);
364 for (String item : splitter) {
365 try {
366 list.add(Integer.parseInt(item));
367 } catch (NumberFormatException e) {
368 Slog.w(TAG, "Can't parseInt: " + item);
369 }
370 }
371 return Collections.unmodifiableList(list);
372 }
373
Jungshik Jang0792d372014-04-23 17:57:26 +0900374 @Override
375 public void onStart() {
Jungshik Jang2f51aec2014-05-20 14:37:38 +0900376 mIoThread.start();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900377 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900378 mProhibitMode = false;
379 mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
Yuncheol Heo08a1be82014-08-12 20:58:41 +0900380 mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900381
Jungshik Janga9f10622014-07-11 15:36:39 +0900382 mCecController = HdmiCecController.create(this);
Jinsuk Kima8a5e502014-05-15 16:51:49 +0900383 if (mCecController != null) {
Yuncheol Heo347a6042014-07-07 21:12:43 +0900384 // TODO: Remove this as soon as OEM's HAL implementation is corrected.
Jinsuk Kim50084862014-08-07 13:11:40 +0900385 mCecController.setOption(OPTION_CEC_ENABLE, ENABLED);
Yuncheol Heo347a6042014-07-07 21:12:43 +0900386
Jungshik Janga9f10622014-07-11 15:36:39 +0900387 // TODO: load value for mHdmiControlEnabled from preference.
388 if (mHdmiControlEnabled) {
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900389 initializeCec(INITIATED_BY_BOOT_UP);
Jungshik Janga9f10622014-07-11 15:36:39 +0900390 }
Jinsuk Kima8a5e502014-05-15 16:51:49 +0900391 } else {
Jungshik Jang0792d372014-04-23 17:57:26 +0900392 Slog.i(TAG, "Device does not support HDMI-CEC.");
Jinsuk Kim08f1ab02014-10-13 10:38:16 +0900393 return;
Jungshik Jang0792d372014-04-23 17:57:26 +0900394 }
395
Jinsuk Kim78104122014-08-26 19:32:34 +0900396 mMhlController = HdmiMhlControllerStub.create(this);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900397 if (!mMhlController.isReady()) {
Jungshik Jang0792d372014-04-23 17:57:26 +0900398 Slog.i(TAG, "Device does not support MHL-control.");
399 }
Jinsuk Kimed086452014-08-18 15:01:53 +0900400 mMhlDevices = Collections.emptyList();
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900401
402 initPortInfo();
Yuncheol Heo75a77e72014-07-09 18:27:53 +0900403 mMessageValidator = new HdmiCecMessageValidator(this);
Jinsuk Kim8692fc62014-05-29 07:39:22 +0900404 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900405
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900406 if (mCecController != null) {
Yuncheol Heo0608b932014-10-13 16:39:18 +0900407 // Register broadcast receiver for power state change.
Yuncheol Heo38db6292014-07-01 14:15:14 +0900408 IntentFilter filter = new IntentFilter();
409 filter.addAction(Intent.ACTION_SCREEN_OFF);
410 filter.addAction(Intent.ACTION_SCREEN_ON);
Terry Heo1ca0a432014-08-18 10:30:32 +0900411 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
412 getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter);
Yuncheol Heo0608b932014-10-13 16:39:18 +0900413
414 // Register ContentObserver to monitor the settings change.
415 registerContentObserver();
Yuncheol Heo38db6292014-07-01 14:15:14 +0900416 }
Jinsuk Kim5b8cb002015-01-19 07:30:12 +0900417 mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED);
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900418 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900419
Jinsuk Kim7fa3a662014-11-07 15:20:24 +0900420 @Override
421 public void onBootPhase(int phase) {
422 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
423 mTvInputManager = (TvInputManager) getContext().getSystemService(
424 Context.TV_INPUT_SERVICE);
Jinsuk Kime26d8332015-01-09 08:55:41 +0900425 mPowerManager = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
Jinsuk Kim7fa3a662014-11-07 15:20:24 +0900426 }
427 }
428
429 TvInputManager getTvInputManager() {
430 return mTvInputManager;
431 }
432
433 void registerTvInputCallback(TvInputCallback callback) {
434 if (mTvInputManager == null) return;
435 mTvInputManager.registerCallback(callback, mHandler);
436 }
437
438 void unregisterTvInputCallback(TvInputCallback callback) {
439 if (mTvInputManager == null) return;
440 mTvInputManager.unregisterCallback(callback);
441 }
442
Jinsuk Kime26d8332015-01-09 08:55:41 +0900443 PowerManager getPowerManager() {
444 return mPowerManager;
445 }
446
Yuncheol Heo25c20292014-07-31 17:59:39 +0900447 /**
448 * Called when the initialization of local devices is complete.
449 */
Yuncheol Heo0608b932014-10-13 16:39:18 +0900450 private void onInitializeCecComplete(int initiatedBy) {
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900451 if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
452 mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
453 }
454 mWakeUpMessageReceived = false;
455
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900456 if (isTvDeviceEnabled()) {
Jinsuk Kim50084862014-08-07 13:11:40 +0900457 mCecController.setOption(OPTION_CEC_AUTO_WAKEUP, toInt(tv().getAutoWakeup()));
Yuncheol Heo0608b932014-10-13 16:39:18 +0900458 }
459 int reason = -1;
460 switch (initiatedBy) {
461 case INITIATED_BY_BOOT_UP:
462 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_START;
463 break;
464 case INITIATED_BY_ENABLE_CEC:
465 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING;
466 break;
467 case INITIATED_BY_SCREEN_ON:
468 case INITIATED_BY_WAKE_UP_MESSAGE:
469 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_WAKEUP;
470 break;
471 }
472 if (reason != -1) {
473 invokeVendorCommandListenersOnControlStateChanged(true, reason);
Yuncheol Heo25c20292014-07-31 17:59:39 +0900474 }
475 }
476
Jinsuk Kim50084862014-08-07 13:11:40 +0900477 private void registerContentObserver() {
478 ContentResolver resolver = getContext().getContentResolver();
479 String[] settings = new String[] {
480 Global.HDMI_CONTROL_ENABLED,
481 Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
482 Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
483 Global.MHL_INPUT_SWITCHING_ENABLED,
484 Global.MHL_POWER_CHARGE_ENABLED
485 };
Jungshik Jang5691b2f2014-08-18 16:50:12 +0900486 for (String s : settings) {
Jinsuk Kim50084862014-08-07 13:11:40 +0900487 resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
488 UserHandle.USER_ALL);
489 }
490 }
491
492 private class SettingsObserver extends ContentObserver {
493 public SettingsObserver(Handler handler) {
494 super(handler);
495 }
496
Jungshik Jangf67113f2014-08-22 16:27:19 +0900497 // onChange is set up to run in service thread.
Jinsuk Kim50084862014-08-07 13:11:40 +0900498 @Override
499 public void onChange(boolean selfChange, Uri uri) {
500 String option = uri.getLastPathSegment();
501 boolean enabled = readBooleanSetting(option, true);
502 switch (option) {
503 case Global.HDMI_CONTROL_ENABLED:
504 setControlEnabled(enabled);
505 break;
506 case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900507 if (isTvDeviceEnabled()) {
508 tv().setAutoWakeup(enabled);
509 }
Jungshik Jang350e68d2014-08-19 18:56:21 +0900510 setCecOption(OPTION_CEC_AUTO_WAKEUP, toInt(enabled));
Jinsuk Kim50084862014-08-07 13:11:40 +0900511 break;
512 case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900513 if (isTvDeviceEnabled()) {
514 tv().setAutoDeviceOff(enabled);
515 }
Jinsuk Kim50084862014-08-07 13:11:40 +0900516 // No need to propagate to HAL.
517 break;
518 case Global.MHL_INPUT_SWITCHING_ENABLED:
Yuncheol Heo08a1be82014-08-12 20:58:41 +0900519 setMhlInputChangeEnabled(enabled);
Jinsuk Kim50084862014-08-07 13:11:40 +0900520 break;
521 case Global.MHL_POWER_CHARGE_ENABLED:
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900522 mMhlController.setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled));
Jinsuk Kim50084862014-08-07 13:11:40 +0900523 break;
524 }
525 }
526 }
527
528 private static int toInt(boolean enabled) {
529 return enabled ? ENABLED : DISABLED;
530 }
531
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900532 boolean readBooleanSetting(String key, boolean defVal) {
533 ContentResolver cr = getContext().getContentResolver();
Jinsuk Kim50084862014-08-07 13:11:40 +0900534 return Global.getInt(cr, key, toInt(defVal)) == ENABLED;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900535 }
536
537 void writeBooleanSetting(String key, boolean value) {
538 ContentResolver cr = getContext().getContentResolver();
Jinsuk Kim50084862014-08-07 13:11:40 +0900539 Global.putInt(cr, key, toInt(value));
540 }
541
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900542 private void initializeCec(int initiatedBy) {
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900543 mAddressAllocated = false;
Jinsuk Kim50084862014-08-07 13:11:40 +0900544 mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, ENABLED);
Jinsuk Kim5b8cb002015-01-19 07:30:12 +0900545 mCecController.setOption(OPTION_CEC_SET_LANGUAGE, HdmiUtils.languageToInt(mLanguage));
Yuncheol Heob5021862014-09-02 10:36:04 +0900546 initializeLocalDevices(initiatedBy);
Jungshik Janga9f10622014-07-11 15:36:39 +0900547 }
548
Jungshik Janga5b74142014-06-23 18:03:10 +0900549 @ServiceThreadOnly
Yuncheol Heob5021862014-09-02 10:36:04 +0900550 private void initializeLocalDevices(final int initiatedBy) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900551 assertRunOnServiceThread();
Yuncheol Heob5021862014-09-02 10:36:04 +0900552 // A container for [Device type, Local device info].
553 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
Yuncheol Heob5021862014-09-02 10:36:04 +0900554 for (int type : mLocalDevices) {
Jinsuk Kim6f87b4e2014-10-10 14:40:29 +0900555 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
556 if (localDevice == null) {
557 localDevice = HdmiCecLocalDevice.create(this, type);
558 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900559 localDevice.init();
Yuncheol Heob5021862014-09-02 10:36:04 +0900560 localDevices.add(localDevice);
561 }
Jinsuk Kim6f87b4e2014-10-10 14:40:29 +0900562 // It's now safe to flush existing local devices from mCecController since they were
563 // already moved to 'localDevices'.
564 clearLocalDevices();
Yuncheol Heob5021862014-09-02 10:36:04 +0900565 allocateLogicalAddress(localDevices, initiatedBy);
566 }
567
568 @ServiceThreadOnly
569 private void allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices,
570 final int initiatedBy) {
571 assertRunOnServiceThread();
Yuncheol Heo89ec14e2014-09-16 15:53:59 +0900572 mCecController.clearLogicalAddress();
Yuncheol Heob5021862014-09-02 10:36:04 +0900573 final ArrayList<HdmiCecLocalDevice> allocatedDevices = new ArrayList<>();
574 final int[] finished = new int[1];
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900575 mAddressAllocated = allocatingDevices.isEmpty();
576
Yuncheol Heob5021862014-09-02 10:36:04 +0900577 for (final HdmiCecLocalDevice localDevice : allocatingDevices) {
578 mCecController.allocateLogicalAddress(localDevice.getType(),
Jungshik Jang3ee65722014-06-03 16:22:30 +0900579 localDevice.getPreferredAddress(), new AllocateAddressCallback() {
580 @Override
581 public void onAllocated(int deviceType, int logicalAddress) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900582 if (logicalAddress == Constants.ADDR_UNREGISTERED) {
Jungshik Jang3ee65722014-06-03 16:22:30 +0900583 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
584 } else {
Jungshik Jang410ca9c2014-08-07 18:04:14 +0900585 // Set POWER_STATUS_ON to all local devices because they share lifetime
586 // with system.
587 HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
588 HdmiControlManager.POWER_STATUS_ON);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900589 localDevice.setDeviceInfo(deviceInfo);
590 mCecController.addLocalDevice(deviceType, localDevice);
591 mCecController.addLogicalAddress(logicalAddress);
Yuncheol Heob5021862014-09-02 10:36:04 +0900592 allocatedDevices.add(localDevice);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900593 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900594
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900595 // Address allocation completed for all devices. Notify each device.
Yuncheol Heob5021862014-09-02 10:36:04 +0900596 if (allocatingDevices.size() == ++finished[0]) {
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900597 mAddressAllocated = true;
Yuncheol Heob5021862014-09-02 10:36:04 +0900598 if (initiatedBy != INITIATED_BY_HOTPLUG) {
599 // In case of the hotplug we don't call onInitializeCecComplete()
600 // since we reallocate the logical address only.
Yuncheol Heo0608b932014-10-13 16:39:18 +0900601 onInitializeCecComplete(initiatedBy);
Yuncheol Heob5021862014-09-02 10:36:04 +0900602 }
603 notifyAddressAllocated(allocatedDevices, initiatedBy);
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900604 mCecMessageBuffer.processMessages();
Jungshik Jang3ee65722014-06-03 16:22:30 +0900605 }
606 }
607 });
608 }
609 }
610
Jungshik Janga5b74142014-06-23 18:03:10 +0900611 @ServiceThreadOnly
Yuncheol Heob5021862014-09-02 10:36:04 +0900612 private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900613 assertRunOnServiceThread();
Yuncheol Heob5021862014-09-02 10:36:04 +0900614 for (HdmiCecLocalDevice device : devices) {
615 int address = device.getDeviceInfo().getLogicalAddress();
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900616 device.handleAddressAllocated(address, initiatedBy);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900617 }
618 }
619
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900620 // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
621 // keep them in one place.
Jungshik Janga5b74142014-06-23 18:03:10 +0900622 @ServiceThreadOnly
Jinsuk Kim2b152012014-07-25 08:22:26 +0900623 private void initPortInfo() {
Jungshik Janga5b74142014-06-23 18:03:10 +0900624 assertRunOnServiceThread();
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900625 HdmiPortInfo[] cecPortInfo = null;
626
627 // CEC HAL provides majority of the info while MHL does only MHL support flag for
628 // each port. Return empty array if CEC HAL didn't provide the info.
629 if (mCecController != null) {
630 cecPortInfo = mCecController.getPortInfos();
631 }
632 if (cecPortInfo == null) {
Jinsuk Kim2b152012014-07-25 08:22:26 +0900633 return;
634 }
635
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900636 SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
637 SparseIntArray portIdMap = new SparseIntArray();
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900638 SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>();
Jinsuk Kim2b152012014-07-25 08:22:26 +0900639 for (HdmiPortInfo info : cecPortInfo) {
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900640 portIdMap.put(info.getAddress(), info.getId());
641 portInfoMap.put(info.getId(), info);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900642 portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900643 }
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900644 mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
645 mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900646 mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900647
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900648 HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
649 ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
650 for (HdmiPortInfo info : mhlPortInfo) {
651 if (info.isMhlSupported()) {
652 mhlSupportedPorts.add(info.getId());
653 }
654 }
655
656 // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use
657 // cec port info if we do not have have port that supports MHL.
658 if (mhlSupportedPorts.isEmpty()) {
Jinsuk Kimf4eb72d2014-07-25 13:02:51 +0900659 mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo));
660 return;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900661 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900662 ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
663 for (HdmiPortInfo info : cecPortInfo) {
664 if (mhlSupportedPorts.contains(info.getId())) {
665 result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
666 info.isCecSupported(), true, info.isArcSupported()));
667 } else {
668 result.add(info);
669 }
670 }
671 mPortInfo = Collections.unmodifiableList(result);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900672 }
673
Jungshik Jang2738e2d2014-08-19 09:30:05 +0900674 List<HdmiPortInfo> getPortInfo() {
675 return mPortInfo;
676 }
677
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900678 /**
679 * Returns HDMI port information for the given port id.
680 *
681 * @param portId HDMI port id
682 * @return {@link HdmiPortInfo} for the given port
683 */
684 HdmiPortInfo getPortInfo(int portId) {
Jinsuk Kim2b152012014-07-25 08:22:26 +0900685 return mPortInfoMap.get(portId, null);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900686 }
687
Jungshik Jange9c77c82014-04-24 20:30:09 +0900688 /**
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900689 * Returns the routing path (physical address) of the HDMI port for the given
690 * port id.
691 */
692 int portIdToPath(int portId) {
693 HdmiPortInfo portInfo = getPortInfo(portId);
694 if (portInfo == null) {
695 Slog.e(TAG, "Cannot find the port info: " + portId);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900696 return Constants.INVALID_PHYSICAL_ADDRESS;
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900697 }
698 return portInfo.getAddress();
699 }
700
701 /**
702 * Returns the id of HDMI port located at the top of the hierarchy of
703 * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
704 * the port id to be returned is the ID associated with the port address
705 * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
706 */
707 int pathToPortId(int path) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900708 int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900709 return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900710 }
711
Jinsuk Kim09ffc842014-07-11 17:04:32 +0900712 boolean isValidPortId(int portId) {
Jinsuk Kim2b152012014-07-25 08:22:26 +0900713 return getPortInfo(portId) != null;
Jinsuk Kim09ffc842014-07-11 17:04:32 +0900714 }
715
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900716 /**
Jungshik Jange9c77c82014-04-24 20:30:09 +0900717 * Returns {@link Looper} for IO operation.
718 *
719 * <p>Declared as package-private.
720 */
721 Looper getIoLooper() {
722 return mIoThread.getLooper();
723 }
724
725 /**
726 * Returns {@link Looper} of main thread. Use this {@link Looper} instance
727 * for tasks that are running on main service thread.
728 *
729 * <p>Declared as package-private.
730 */
731 Looper getServiceLooper() {
Jungshik Jang67ea5212014-05-15 14:05:24 +0900732 return mHandler.getLooper();
Jungshik Jange9c77c82014-04-24 20:30:09 +0900733 }
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900734
735 /**
Jungshik Jang3ee65722014-06-03 16:22:30 +0900736 * Returns physical address of the device.
737 */
738 int getPhysicalAddress() {
739 return mCecController.getPhysicalAddress();
740 }
741
742 /**
743 * Returns vendor id of CEC service.
744 */
745 int getVendorId() {
746 return mCecController.getVendorId();
747 }
748
Jungshik Janga5b74142014-06-23 18:03:10 +0900749 @ServiceThreadOnly
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900750 HdmiDeviceInfo getDeviceInfo(int logicalAddress) {
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900751 assertRunOnServiceThread();
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900752 return tv() == null ? null : tv().getCecDeviceInfo(logicalAddress);
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900753 }
754
Jinsuk Kim6ad7cbd2015-01-06 11:30:56 +0900755 @ServiceThreadOnly
756 HdmiDeviceInfo getDeviceInfoByPort(int port) {
757 assertRunOnServiceThread();
758 HdmiMhlLocalDeviceStub info = mMhlController.getLocalDevice(port);
759 if (info != null) {
760 return info.getInfo();
761 }
762 return null;
763 }
764
Jungshik Jang3ee65722014-06-03 16:22:30 +0900765 /**
Jungshik Jang092b4452014-06-11 15:19:17 +0900766 * Returns version of CEC.
767 */
768 int getCecVersion() {
769 return mCecController.getVersion();
770 }
771
772 /**
Jungshik Jang60cffce2014-06-12 18:03:04 +0900773 * Whether a device of the specified physical address is connected to ARC enabled port.
774 */
775 boolean isConnectedToArcPort(int physicalAddress) {
Jungshik Jang339227d2014-08-25 15:37:20 +0900776 int portId = pathToPortId(physicalAddress);
Jinsuk Kim2b152012014-07-25 08:22:26 +0900777 if (portId != Constants.INVALID_PORT_ID) {
778 return mPortInfoMap.get(portId).isArcSupported();
Jungshik Jang60cffce2014-06-12 18:03:04 +0900779 }
780 return false;
781 }
782
Jinsuk Kim7b0cf642015-04-14 09:43:45 +0900783 @ServiceThreadOnly
784 boolean isConnected(int portId) {
785 assertRunOnServiceThread();
786 return mCecController.isConnected(portId);
787 }
788
Jungshik Jang79c58a42014-06-16 16:45:36 +0900789 void runOnServiceThread(Runnable runnable) {
Jungshik Jang67ea5212014-05-15 14:05:24 +0900790 mHandler.post(runnable);
791 }
792
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900793 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
794 mHandler.postAtFrontOfQueue(runnable);
795 }
796
797 private void assertRunOnServiceThread() {
798 if (Looper.myLooper() != mHandler.getLooper()) {
799 throw new IllegalStateException("Should run on service thread.");
800 }
801 }
802
Jungshik Jang67ea5212014-05-15 14:05:24 +0900803 /**
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900804 * Transmit a CEC command to CEC bus.
805 *
806 * @param command CEC command to send out
Jungshik Jangd643f762014-05-22 19:28:09 +0900807 * @param callback interface used to the result of send command
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900808 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900809 @ServiceThreadOnly
Jungshik Jangd643f762014-05-22 19:28:09 +0900810 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900811 assertRunOnServiceThread();
Yuncheol Heo4c212892014-09-12 14:32:46 +0900812 if (mMessageValidator.isValid(command) == HdmiCecMessageValidator.OK) {
Jungshik Jang5f75cbd2014-08-07 12:02:29 +0900813 mCecController.sendCommand(command, callback);
814 } else {
Jungshik Jang2e8f1b62014-09-03 08:28:02 +0900815 HdmiLogger.error("Invalid message type:" + command);
Jungshik Jang5f75cbd2014-08-07 12:02:29 +0900816 if (callback != null) {
817 callback.onSendCompleted(Constants.SEND_RESULT_FAILURE);
818 }
819 }
Jungshik Jangd643f762014-05-22 19:28:09 +0900820 }
821
Jungshik Janga5b74142014-06-23 18:03:10 +0900822 @ServiceThreadOnly
Jungshik Jangd643f762014-05-22 19:28:09 +0900823 void sendCecCommand(HdmiCecMessage command) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900824 assertRunOnServiceThread();
Jungshik Jang5f75cbd2014-08-07 12:02:29 +0900825 sendCecCommand(command, null);
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900826 }
827
Yuncheol Heo6aae6522014-08-05 14:48:37 +0900828 /**
829 * Send <Feature Abort> command on the given CEC message if possible.
830 * If the aborted message is invalid, then it wont send the message.
831 * @param command original command to be aborted
832 * @param reason reason of feature abort
833 */
834 @ServiceThreadOnly
835 void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) {
836 assertRunOnServiceThread();
837 mCecController.maySendFeatureAbortCommand(command, reason);
838 }
839
Jungshik Janga5b74142014-06-23 18:03:10 +0900840 @ServiceThreadOnly
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900841 boolean handleCecCommand(HdmiCecMessage message) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900842 assertRunOnServiceThread();
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900843 if (!mAddressAllocated) {
844 mCecMessageBuffer.bufferMessage(message);
845 return true;
846 }
Yuncheol Heo4c212892014-09-12 14:32:46 +0900847 int errorCode = mMessageValidator.isValid(message);
848 if (errorCode != HdmiCecMessageValidator.OK) {
Yuncheol Heoa95f1a92014-11-06 08:25:39 +0900849 // We'll not response on the messages with the invalid source or destination
850 // or with parameter length shorter than specified in the standard.
Yuncheol Heo4c212892014-09-12 14:32:46 +0900851 if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) {
852 maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
853 }
854 return true;
Yuncheol Heo75a77e72014-07-09 18:27:53 +0900855 }
Jungshik Jang092b4452014-06-11 15:19:17 +0900856 return dispatchMessageToLocalDevice(message);
857 }
858
Jinsuk Kim1481a422014-12-17 16:15:05 +0900859 void setAudioReturnChannel(int portId, boolean enabled) {
860 mCecController.setAudioReturnChannel(portId, enabled);
Jungshik Jang60cffce2014-06-12 18:03:04 +0900861 }
862
Jungshik Janga5b74142014-06-23 18:03:10 +0900863 @ServiceThreadOnly
Jungshik Jang092b4452014-06-11 15:19:17 +0900864 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900865 assertRunOnServiceThread();
Jungshik Jang092b4452014-06-11 15:19:17 +0900866 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900867 if (device.dispatchMessage(message)
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900868 && message.getDestination() != Constants.ADDR_BROADCAST) {
Jungshik Jang092b4452014-06-11 15:19:17 +0900869 return true;
870 }
871 }
Jungshik Jang60cffce2014-06-12 18:03:04 +0900872
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900873 if (message.getDestination() != Constants.ADDR_BROADCAST) {
Jungshik Jang2e8f1b62014-09-03 08:28:02 +0900874 HdmiLogger.warning("Unhandled cec command:" + message);
Jungshik Jang3a959fc2014-07-03 09:34:05 +0900875 }
Jungshik Jang092b4452014-06-11 15:19:17 +0900876 return false;
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900877 }
878
Jungshik Jang67ea5212014-05-15 14:05:24 +0900879 /**
880 * Called when a new hotplug event is issued.
881 *
Jinsuk Kimed086452014-08-18 15:01:53 +0900882 * @param portId hdmi port number where hot plug event issued.
Jungshik Jang67ea5212014-05-15 14:05:24 +0900883 * @param connected whether to be plugged in or not
884 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900885 @ServiceThreadOnly
Jinsuk Kimed086452014-08-18 15:01:53 +0900886 void onHotplug(int portId, boolean connected) {
Jungshik Jang60cffce2014-06-12 18:03:04 +0900887 assertRunOnServiceThread();
Yuncheol Heob5021862014-09-02 10:36:04 +0900888
Yuncheol Heob8d62e72014-09-22 19:53:41 +0900889 if (connected && !isTvDevice()) {
890 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
891 for (int type : mLocalDevices) {
892 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
893 if (localDevice == null) {
894 localDevice = HdmiCecLocalDevice.create(this, type);
895 localDevice.init();
896 }
897 localDevices.add(localDevice);
Yuncheol Heob5021862014-09-02 10:36:04 +0900898 }
Yuncheol Heob8d62e72014-09-22 19:53:41 +0900899 allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
Yuncheol Heob5021862014-09-02 10:36:04 +0900900 }
Yuncheol Heob5021862014-09-02 10:36:04 +0900901
Jungshik Jang79c58a42014-06-16 16:45:36 +0900902 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jinsuk Kimed086452014-08-18 15:01:53 +0900903 device.onHotplug(portId, connected);
Jungshik Jang60cffce2014-06-12 18:03:04 +0900904 }
Jinsuk Kimed086452014-08-18 15:01:53 +0900905 announceHotplugEvent(portId, connected);
Jungshik Jang67ea5212014-05-15 14:05:24 +0900906 }
907
Jungshik Jang02bb4262014-05-23 16:48:31 +0900908 /**
909 * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
910 * devices.
911 *
912 * @param callback an interface used to get a list of all remote devices' address
Jungshik Jang1de51422014-07-03 11:14:26 +0900913 * @param sourceAddress a logical address of source device where sends polling message
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900914 * @param pickStrategy strategy how to pick polling candidates
Jungshik Jang02bb4262014-05-23 16:48:31 +0900915 * @param retryCount the number of retry used to send polling message to remote devices
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900916 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
Jungshik Jang02bb4262014-05-23 16:48:31 +0900917 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900918 @ServiceThreadOnly
Jungshik Jang1de51422014-07-03 11:14:26 +0900919 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
920 int retryCount) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900921 assertRunOnServiceThread();
Jungshik Jang1de51422014-07-03 11:14:26 +0900922 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
923 retryCount);
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900924 }
925
926 private int checkPollStrategy(int pickStrategy) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900927 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900928 if (strategy == 0) {
929 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
930 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900931 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900932 if (iterationStrategy == 0) {
933 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
934 }
935 return strategy | iterationStrategy;
Jungshik Jang02bb4262014-05-23 16:48:31 +0900936 }
937
Jungshik Jang60cffce2014-06-12 18:03:04 +0900938 List<HdmiCecLocalDevice> getAllLocalDevices() {
939 assertRunOnServiceThread();
940 return mCecController.getLocalDeviceList();
941 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900942
Jungshik Jang79c58a42014-06-16 16:45:36 +0900943 Object getServiceLock() {
944 return mLock;
945 }
946
947 void setAudioStatus(boolean mute, int volume) {
Jungshik Jangb69aafbf2014-07-11 16:29:06 +0900948 AudioManager audioManager = getAudioManager();
949 boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
950 if (mute) {
951 if (!muted) {
952 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
953 }
954 } else {
955 if (muted) {
956 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
957 }
958 // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
959 // volume change notification back to hdmi control service.
960 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
Jungshik Jang1a6be6e2014-09-16 11:04:54 +0900961 AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
Jungshik Jangb69aafbf2014-07-11 16:29:06 +0900962 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900963 }
964
Jungshik Jangea67c182014-06-19 22:19:20 +0900965 void announceSystemAudioModeChange(boolean enabled) {
Jungshik Jangf4249322014-08-21 14:17:05 +0900966 synchronized (mLock) {
967 for (SystemAudioModeChangeListenerRecord record :
968 mSystemAudioModeChangeListenerRecords) {
969 invokeSystemAudioModeChangeLocked(record.mListener, enabled);
970 }
Jungshik Jangea67c182014-06-19 22:19:20 +0900971 }
972 }
973
Jungshik Jang410ca9c2014-08-07 18:04:14 +0900974 private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
Jungshik Jang42c98002014-06-12 13:17:44 +0900975 // TODO: find better name instead of model name.
976 String displayName = Build.MODEL;
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900977 return new HdmiDeviceInfo(logicalAddress,
Jinsuk Kim2b152012014-07-25 08:22:26 +0900978 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
979 getVendorId(), displayName);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900980 }
981
Jungshik Jang7df52862014-08-11 14:35:27 +0900982 @ServiceThreadOnly
Jungshik Jang7df52862014-08-11 14:35:27 +0900983 void handleMhlHotplugEvent(int portId, boolean connected) {
984 assertRunOnServiceThread();
Jinsuk Kim93eed0c2014-10-14 11:52:22 +0900985 // Hotplug event is used to add/remove MHL devices as TV input.
Jungshik Jang7df52862014-08-11 14:35:27 +0900986 if (connected) {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +0900987 HdmiMhlLocalDeviceStub newDevice = new HdmiMhlLocalDeviceStub(this, portId);
988 HdmiMhlLocalDeviceStub oldDevice = mMhlController.addLocalDevice(newDevice);
Jungshik Jang7df52862014-08-11 14:35:27 +0900989 if (oldDevice != null) {
990 oldDevice.onDeviceRemoved();
991 Slog.i(TAG, "Old device of port " + portId + " is removed");
992 }
Jinsuk Kim93eed0c2014-10-14 11:52:22 +0900993 invokeDeviceEventListeners(newDevice.getInfo(), DEVICE_EVENT_ADD_DEVICE);
994 updateSafeMhlInput();
Jungshik Jang7df52862014-08-11 14:35:27 +0900995 } else {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +0900996 HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +0900997 if (device != null) {
998 device.onDeviceRemoved();
Jinsuk Kim93eed0c2014-10-14 11:52:22 +0900999 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE);
1000 updateSafeMhlInput();
Jungshik Jang7df52862014-08-11 14:35:27 +09001001 } else {
1002 Slog.w(TAG, "No device to remove:[portId=" + portId);
1003 }
1004 }
Jinsuk Kimed086452014-08-18 15:01:53 +09001005 announceHotplugEvent(portId, connected);
Jungshik Jang7df52862014-08-11 14:35:27 +09001006 }
1007
1008 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001009 void handleMhlBusModeChanged(int portId, int busmode) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001010 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001011 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001012 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001013 device.setBusMode(busmode);
Jungshik Jang7df52862014-08-11 14:35:27 +09001014 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001015 Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId +
1016 ", busmode:" + busmode + "]");
Jungshik Jang7df52862014-08-11 14:35:27 +09001017 }
1018 }
1019
1020 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001021 void handleMhlBusOvercurrent(int portId, boolean on) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001022 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001023 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001024 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001025 device.onBusOvercurrentDetected(on);
Jungshik Jang7df52862014-08-11 14:35:27 +09001026 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001027 Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]");
Jungshik Jang7df52862014-08-11 14:35:27 +09001028 }
1029 }
1030
1031 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001032 void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001033 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001034 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jinsuk Kimed086452014-08-18 15:01:53 +09001035
Jungshik Jang7df52862014-08-11 14:35:27 +09001036 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001037 device.setDeviceStatusChange(adopterId, deviceId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001038 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001039 Slog.w(TAG, "No mhl device exists for device status event[portId:"
Jungshik Jang7df52862014-08-11 14:35:27 +09001040 + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]");
1041 }
1042 }
1043
Jinsuk Kimed086452014-08-18 15:01:53 +09001044 @ServiceThreadOnly
1045 private void updateSafeMhlInput() {
1046 assertRunOnServiceThread();
1047 List<HdmiDeviceInfo> inputs = Collections.emptyList();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001048 SparseArray<HdmiMhlLocalDeviceStub> devices = mMhlController.getAllLocalDevices();
Jinsuk Kimed086452014-08-18 15:01:53 +09001049 for (int i = 0; i < devices.size(); ++i) {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001050 HdmiMhlLocalDeviceStub device = devices.valueAt(i);
Jinsuk Kimed086452014-08-18 15:01:53 +09001051 HdmiDeviceInfo info = device.getInfo();
1052 if (info != null) {
1053 if (inputs.isEmpty()) {
1054 inputs = new ArrayList<>();
1055 }
1056 inputs.add(device.getInfo());
1057 }
1058 }
1059 synchronized (mLock) {
1060 mMhlDevices = inputs;
1061 }
1062 }
1063
1064 private List<HdmiDeviceInfo> getMhlDevicesLocked() {
1065 return mMhlDevices;
1066 }
1067
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001068 private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient {
1069 private final IHdmiMhlVendorCommandListener mListener;
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001070
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001071 public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) {
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001072 mListener = listener;
1073 }
1074
1075 @Override
1076 public void binderDied() {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001077 mMhlVendorCommandListenerRecords.remove(this);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001078 }
1079 }
1080
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001081 // Record class that monitors the event of the caller of being killed. Used to clean up
1082 // the listener list and record list accordingly.
1083 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
1084 private final IHdmiHotplugEventListener mListener;
1085
1086 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
1087 mListener = listener;
1088 }
1089
1090 @Override
1091 public void binderDied() {
1092 synchronized (mLock) {
1093 mHotplugEventListenerRecords.remove(this);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001094 }
1095 }
Jinsuk Kim3cd30512014-12-04 11:05:09 +09001096
1097 @Override
1098 public boolean equals(Object obj) {
1099 if (!(obj instanceof HotplugEventListenerRecord)) return false;
1100 if (obj == this) return true;
1101 HotplugEventListenerRecord other = (HotplugEventListenerRecord) obj;
1102 return other.mListener == this.mListener;
1103 }
1104
1105 @Override
1106 public int hashCode() {
1107 return mListener.hashCode();
1108 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001109 }
1110
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001111 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
1112 private final IHdmiDeviceEventListener mListener;
1113
1114 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
1115 mListener = listener;
1116 }
1117
1118 @Override
Jungshik Jangea67c182014-06-19 22:19:20 +09001119 public void binderDied() {
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001120 synchronized (mLock) {
1121 mDeviceEventListenerRecords.remove(this);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001122 }
1123 }
1124 }
1125
Jungshik Jangea67c182014-06-19 22:19:20 +09001126 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
Yuncheol Heo38db6292014-07-01 14:15:14 +09001127 private final IHdmiSystemAudioModeChangeListener mListener;
Jungshik Jangea67c182014-06-19 22:19:20 +09001128
1129 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
1130 mListener = listener;
1131 }
1132
1133 @Override
1134 public void binderDied() {
1135 synchronized (mLock) {
1136 mSystemAudioModeChangeListenerRecords.remove(this);
Jungshik Jangea67c182014-06-19 22:19:20 +09001137 }
1138 }
1139 }
1140
Jinsuk Kim119160a2014-07-07 18:48:10 +09001141 class VendorCommandListenerRecord implements IBinder.DeathRecipient {
1142 private final IHdmiVendorCommandListener mListener;
1143 private final int mDeviceType;
1144
1145 public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
1146 mListener = listener;
1147 mDeviceType = deviceType;
1148 }
1149
1150 @Override
1151 public void binderDied() {
1152 synchronized (mLock) {
1153 mVendorCommandListenerRecords.remove(this);
1154 }
1155 }
1156 }
1157
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001158 private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
Jungshik Jangf4249322014-08-21 14:17:05 +09001159 private final IHdmiRecordListener mListener;
1160
1161 public HdmiRecordListenerRecord(IHdmiRecordListener listener) {
1162 mListener = listener;
1163 }
1164
Jungshik Jangb6591b82014-07-23 16:10:23 +09001165 @Override
1166 public void binderDied() {
1167 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001168 mRecordListenerRecord = null;
Jungshik Jangb6591b82014-07-23 16:10:23 +09001169 }
1170 }
1171 }
1172
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001173 private void enforceAccessPermission() {
1174 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
1175 }
1176
1177 private final class BinderService extends IHdmiControlService.Stub {
1178 @Override
1179 public int[] getSupportedTypes() {
1180 enforceAccessPermission();
Jinsuk Kim0340bbc2014-06-05 11:07:47 +09001181 // mLocalDevices is an unmodifiable list - no lock necesary.
1182 int[] localDevices = new int[mLocalDevices.size()];
1183 for (int i = 0; i < localDevices.length; ++i) {
1184 localDevices[i] = mLocalDevices.get(i);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001185 }
Jinsuk Kim0340bbc2014-06-05 11:07:47 +09001186 return localDevices;
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001187 }
1188
1189 @Override
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001190 public HdmiDeviceInfo getActiveSource() {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001191 enforceAccessPermission();
Jinsuk Kim7e742062014-07-30 13:19:13 +09001192 HdmiCecLocalDeviceTv tv = tv();
1193 if (tv == null) {
1194 Slog.w(TAG, "Local tv device not available");
1195 return null;
1196 }
1197 ActiveSource activeSource = tv.getActiveSource();
1198 if (activeSource.isValid()) {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001199 return new HdmiDeviceInfo(activeSource.logicalAddress,
1200 activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID,
1201 HdmiDeviceInfo.DEVICE_INACTIVE, 0, "");
Jinsuk Kim7e742062014-07-30 13:19:13 +09001202 }
1203 int activePath = tv.getActivePath();
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001204 if (activePath != HdmiDeviceInfo.PATH_INVALID) {
Jinsuk Kim7640d982015-01-28 16:44:07 +09001205 HdmiDeviceInfo info = tv.getSafeDeviceInfoByPath(activePath);
Jinsuk Kimd47abef2015-01-17 07:38:24 +09001206 return (info != null) ? info : new HdmiDeviceInfo(activePath, tv.getActivePortId());
Jinsuk Kim7e742062014-07-30 13:19:13 +09001207 }
1208 return null;
1209 }
1210
1211 @Override
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001212 public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) {
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001213 enforceAccessPermission();
1214 runOnServiceThread(new Runnable() {
1215 @Override
1216 public void run() {
Jinsuk Kim72b7d732014-07-24 09:15:35 +09001217 if (callback == null) {
1218 Slog.e(TAG, "Callback cannot be null");
1219 return;
1220 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09001221 HdmiCecLocalDeviceTv tv = tv();
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001222 if (tv == null) {
Jinsuk Kima062a932014-06-18 10:00:39 +09001223 Slog.w(TAG, "Local tv device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001224 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001225 return;
1226 }
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001227 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001228 if (device != null) {
1229 if (device.getPortId() == tv.getActivePortId()) {
1230 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
Jinsuk Kim87f22a22014-08-20 10:40:12 +09001231 return;
1232 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001233 // Upon selecting MHL device, we send RAP[Content On] to wake up
1234 // the connected mobile device, start routing control to switch ports.
1235 // callback is handled by MHL action.
1236 device.turnOn(callback);
Yuncheol Heo7c5d31e2014-09-03 16:28:54 +09001237 tv.doManualPortSwitching(device.getPortId(), null);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001238 return;
Jinsuk Kim87f22a22014-08-20 10:40:12 +09001239 }
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001240 tv.deviceSelect(deviceId, callback);
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001241 }
1242 });
1243 }
1244
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001245 @Override
Jinsuk Kima062a932014-06-18 10:00:39 +09001246 public void portSelect(final int portId, final IHdmiControlCallback callback) {
1247 enforceAccessPermission();
1248 runOnServiceThread(new Runnable() {
1249 @Override
1250 public void run() {
Jinsuk Kim72b7d732014-07-24 09:15:35 +09001251 if (callback == null) {
1252 Slog.e(TAG, "Callback cannot be null");
1253 return;
1254 }
Jinsuk Kima062a932014-06-18 10:00:39 +09001255 HdmiCecLocalDeviceTv tv = tv();
1256 if (tv == null) {
1257 Slog.w(TAG, "Local tv device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001258 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kima062a932014-06-18 10:00:39 +09001259 return;
1260 }
Jinsuk Kim83335712014-06-24 07:57:00 +09001261 tv.doManualPortSwitching(portId, callback);
Jinsuk Kima062a932014-06-18 10:00:39 +09001262 }
1263 });
1264 }
1265
1266 @Override
Jinsuk Kimc068bb52014-07-07 16:59:20 +09001267 public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
Jinsuk Kima062a932014-06-18 10:00:39 +09001268 enforceAccessPermission();
1269 runOnServiceThread(new Runnable() {
1270 @Override
1271 public void run() {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001272 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001273 if (device != null) {
1274 device.sendKeyEvent(keyCode, isPressed);
1275 return;
Jinsuk Kima062a932014-06-18 10:00:39 +09001276 }
Jungshik Jang4612a6e2014-08-12 22:01:23 +09001277 if (mCecController != null) {
1278 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
1279 if (localDevice == null) {
1280 Slog.w(TAG, "Local device not available");
1281 return;
1282 }
1283 localDevice.sendKeyEvent(keyCode, isPressed);
1284 }
Jinsuk Kima062a932014-06-18 10:00:39 +09001285 }
1286 });
1287 }
1288
1289 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001290 public void oneTouchPlay(final IHdmiControlCallback callback) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001291 enforceAccessPermission();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001292 runOnServiceThread(new Runnable() {
1293 @Override
1294 public void run() {
1295 HdmiControlService.this.oneTouchPlay(callback);
1296 }
1297 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001298 }
1299
1300 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001301 public void queryDisplayStatus(final IHdmiControlCallback callback) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001302 enforceAccessPermission();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001303 runOnServiceThread(new Runnable() {
1304 @Override
1305 public void run() {
1306 HdmiControlService.this.queryDisplayStatus(callback);
1307 }
1308 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001309 }
1310
1311 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001312 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001313 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001314 HdmiControlService.this.addHotplugEventListener(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001315 }
1316
1317 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001318 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001319 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001320 HdmiControlService.this.removeHotplugEventListener(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001321 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001322
1323 @Override
1324 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
1325 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001326 HdmiControlService.this.addDeviceEventListener(listener);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001327 }
1328
1329 @Override
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001330 public List<HdmiPortInfo> getPortInfo() {
1331 enforceAccessPermission();
Jungshik Jang2738e2d2014-08-19 09:30:05 +09001332 return HdmiControlService.this.getPortInfo();
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001333 }
Jungshik Jangea67c182014-06-19 22:19:20 +09001334
1335 @Override
1336 public boolean canChangeSystemAudioMode() {
1337 enforceAccessPermission();
1338 HdmiCecLocalDeviceTv tv = tv();
1339 if (tv == null) {
1340 return false;
1341 }
Jungshik Jange9cf1582014-06-23 17:28:58 +09001342 return tv.hasSystemAudioDevice();
Jungshik Jangea67c182014-06-19 22:19:20 +09001343 }
1344
1345 @Override
1346 public boolean getSystemAudioMode() {
1347 enforceAccessPermission();
1348 HdmiCecLocalDeviceTv tv = tv();
1349 if (tv == null) {
1350 return false;
1351 }
Jungshik Jang377dcbd2014-07-15 15:49:02 +09001352 return tv.isSystemAudioActivated();
Jungshik Jangea67c182014-06-19 22:19:20 +09001353 }
1354
1355 @Override
1356 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
1357 enforceAccessPermission();
1358 runOnServiceThread(new Runnable() {
1359 @Override
1360 public void run() {
1361 HdmiCecLocalDeviceTv tv = tv();
1362 if (tv == null) {
1363 Slog.w(TAG, "Local tv device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001364 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jungshik Jangea67c182014-06-19 22:19:20 +09001365 return;
1366 }
1367 tv.changeSystemAudioMode(enabled, callback);
1368 }
1369 });
1370 }
1371
1372 @Override
1373 public void addSystemAudioModeChangeListener(
1374 final IHdmiSystemAudioModeChangeListener listener) {
1375 enforceAccessPermission();
1376 HdmiControlService.this.addSystemAudioModeChangeListner(listener);
1377 }
1378
1379 @Override
1380 public void removeSystemAudioModeChangeListener(
1381 final IHdmiSystemAudioModeChangeListener listener) {
1382 enforceAccessPermission();
1383 HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
1384 }
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001385
1386 @Override
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001387 public void setInputChangeListener(final IHdmiInputChangeListener listener) {
1388 enforceAccessPermission();
1389 HdmiControlService.this.setInputChangeListener(listener);
1390 }
1391
1392 @Override
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001393 public List<HdmiDeviceInfo> getInputDevices() {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001394 enforceAccessPermission();
1395 // No need to hold the lock for obtaining TV device as the local device instance
1396 // is preserved while the HDMI control is enabled.
1397 HdmiCecLocalDeviceTv tv = tv();
Jinsuk Kimed086452014-08-18 15:01:53 +09001398 synchronized (mLock) {
1399 List<HdmiDeviceInfo> cecDevices = (tv == null)
1400 ? Collections.<HdmiDeviceInfo>emptyList()
1401 : tv.getSafeExternalInputsLocked();
1402 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked());
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001403 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001404 }
1405
Jinsuk Kimbdf27fb2014-10-20 10:00:04 +09001406 // Returns all the CEC devices on the bus including system audio, switch,
1407 // even those of reserved type.
1408 @Override
1409 public List<HdmiDeviceInfo> getDeviceList() {
1410 enforceAccessPermission();
1411 HdmiCecLocalDeviceTv tv = tv();
1412 synchronized (mLock) {
1413 return (tv == null)
1414 ? Collections.<HdmiDeviceInfo>emptyList()
1415 : tv.getSafeCecDevicesLocked();
1416 }
1417 }
1418
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001419 @Override
Jungshik Jang41d97462014-06-30 22:26:29 +09001420 public void setSystemAudioVolume(final int oldIndex, final int newIndex,
1421 final int maxIndex) {
1422 enforceAccessPermission();
1423 runOnServiceThread(new Runnable() {
1424 @Override
1425 public void run() {
1426 HdmiCecLocalDeviceTv tv = tv();
1427 if (tv == null) {
1428 Slog.w(TAG, "Local tv device not available");
1429 return;
1430 }
1431 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
1432 }
1433 });
1434 }
1435
1436 @Override
1437 public void setSystemAudioMute(final boolean mute) {
1438 enforceAccessPermission();
1439 runOnServiceThread(new Runnable() {
1440 @Override
1441 public void run() {
1442 HdmiCecLocalDeviceTv tv = tv();
1443 if (tv == null) {
1444 Slog.w(TAG, "Local tv device not available");
1445 return;
1446 }
1447 tv.changeMute(mute);
1448 }
1449 });
1450 }
1451
1452 @Override
Jungshik Janga13da0d2014-06-30 16:26:06 +09001453 public void setArcMode(final boolean enabled) {
1454 enforceAccessPermission();
1455 runOnServiceThread(new Runnable() {
1456 @Override
1457 public void run() {
1458 HdmiCecLocalDeviceTv tv = tv();
1459 if (tv == null) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09001460 Slog.w(TAG, "Local tv device not available to change arc mode.");
Jungshik Janga13da0d2014-06-30 16:26:06 +09001461 return;
1462 }
1463 }
1464 });
1465 }
Jinsuk Kim160a6e52014-07-02 06:16:36 +09001466
1467 @Override
Jinsuk Kim4d43d932014-07-03 16:43:58 +09001468 public void setProhibitMode(final boolean enabled) {
1469 enforceAccessPermission();
1470 if (!isTvDevice()) {
1471 return;
1472 }
1473 HdmiControlService.this.setProhibitMode(enabled);
1474 }
Jinsuk Kim119160a2014-07-07 18:48:10 +09001475
1476 @Override
1477 public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
1478 final int deviceType) {
1479 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001480 HdmiControlService.this.addVendorCommandListener(listener, deviceType);
Jinsuk Kim119160a2014-07-07 18:48:10 +09001481 }
1482
1483 @Override
1484 public void sendVendorCommand(final int deviceType, final int targetAddress,
1485 final byte[] params, final boolean hasVendorId) {
1486 enforceAccessPermission();
1487 runOnServiceThread(new Runnable() {
1488 @Override
1489 public void run() {
1490 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1491 if (device == null) {
1492 Slog.w(TAG, "Local device not available");
1493 return;
1494 }
1495 if (hasVendorId) {
1496 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1497 device.getDeviceInfo().getLogicalAddress(), targetAddress,
1498 getVendorId(), params));
1499 } else {
1500 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1501 device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1502 }
1503 }
1504 });
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001505 }
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09001506
1507 @Override
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09001508 public void sendStandby(final int deviceType, final int deviceId) {
1509 enforceAccessPermission();
1510 runOnServiceThread(new Runnable() {
1511 @Override
1512 public void run() {
Jinsuk Kim61c94d12015-01-15 07:00:28 +09001513 HdmiMhlLocalDeviceStub mhlDevice = mMhlController.getLocalDeviceById(deviceId);
1514 if (mhlDevice != null) {
1515 mhlDevice.sendStandby();
1516 return;
1517 }
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09001518 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1519 if (device == null) {
1520 Slog.w(TAG, "Local device not available");
1521 return;
1522 }
1523 device.sendStandby(deviceId);
1524 }
1525 });
1526 }
1527
1528 @Override
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001529 public void setHdmiRecordListener(IHdmiRecordListener listener) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001530 enforceAccessPermission();
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001531 HdmiControlService.this.setHdmiRecordListener(listener);
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09001532 }
1533
1534 @Override
Jungshik Jangb6591b82014-07-23 16:10:23 +09001535 public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001536 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001537 runOnServiceThread(new Runnable() {
1538 @Override
1539 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001540 if (!isTvDeviceEnabled()) {
1541 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001542 return;
1543 }
1544 tv().startOneTouchRecord(recorderAddress, recordSource);
1545 }
1546 });
Jungshik Jangbffb0632014-07-22 16:56:52 +09001547 }
1548
1549 @Override
Jungshik Jangb6591b82014-07-23 16:10:23 +09001550 public void stopOneTouchRecord(final int recorderAddress) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001551 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001552 runOnServiceThread(new Runnable() {
1553 @Override
1554 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001555 if (!isTvDeviceEnabled()) {
1556 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001557 return;
1558 }
1559 tv().stopOneTouchRecord(recorderAddress);
1560 }
1561 });
1562 }
1563
1564 @Override
1565 public void startTimerRecording(final int recorderAddress, final int sourceType,
1566 final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001567 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001568 runOnServiceThread(new Runnable() {
1569 @Override
1570 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001571 if (!isTvDeviceEnabled()) {
1572 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001573 return;
1574 }
1575 tv().startTimerRecording(recorderAddress, sourceType, recordSource);
1576 }
1577 });
1578 }
1579
1580 @Override
1581 public void clearTimerRecording(final int recorderAddress, final int sourceType,
1582 final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001583 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001584 runOnServiceThread(new Runnable() {
1585 @Override
1586 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001587 if (!isTvDeviceEnabled()) {
1588 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001589 return;
1590 }
1591 tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
1592 }
1593 });
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09001594 }
Jungshik Jangf4249322014-08-21 14:17:05 +09001595
1596 @Override
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001597 public void sendMhlVendorCommand(final int portId, final int offset, final int length,
Jungshik Jangf4249322014-08-21 14:17:05 +09001598 final byte[] data) {
1599 enforceAccessPermission();
1600 runOnServiceThread(new Runnable() {
1601 @Override
1602 public void run() {
Jungshik Jangf4249322014-08-21 14:17:05 +09001603 if (!isControlEnabled()) {
1604 Slog.w(TAG, "Hdmi control is disabled.");
1605 return ;
1606 }
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001607 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jangf4249322014-08-21 14:17:05 +09001608 if (device == null) {
1609 Slog.w(TAG, "Invalid port id:" + portId);
1610 return;
1611 }
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001612 mMhlController.sendVendorCommand(portId, offset, length, data);
Jungshik Jangf4249322014-08-21 14:17:05 +09001613 }
1614 });
1615 }
1616
1617 @Override
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001618 public void addHdmiMhlVendorCommandListener(
1619 IHdmiMhlVendorCommandListener listener) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001620 enforceAccessPermission();
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001621 HdmiControlService.this.addHdmiMhlVendorCommandListener(listener);
Jungshik Jangf4249322014-08-21 14:17:05 +09001622 }
Terry Heo959d2db2014-08-28 16:45:41 +09001623
1624 @Override
1625 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1626 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
1627 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
1628
1629 pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
1630 pw.println("mProhibitMode: " + mProhibitMode);
1631 if (mCecController != null) {
1632 pw.println("mCecController: ");
1633 pw.increaseIndent();
1634 mCecController.dump(pw);
1635 pw.decreaseIndent();
1636 }
Jinsuk Kim61c94d12015-01-15 07:00:28 +09001637
1638 pw.println("mMhlController: ");
1639 pw.increaseIndent();
1640 mMhlController.dump(pw);
1641 pw.decreaseIndent();
1642
Terry Heo959d2db2014-08-28 16:45:41 +09001643 pw.println("mPortInfo: ");
1644 pw.increaseIndent();
1645 for (HdmiPortInfo hdmiPortInfo : mPortInfo) {
1646 pw.println("- " + hdmiPortInfo);
1647 }
1648 pw.decreaseIndent();
1649 pw.println("mPowerStatus: " + mPowerStatus);
1650 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001651 }
1652
Jungshik Janga5b74142014-06-23 18:03:10 +09001653 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +09001654 private void oneTouchPlay(final IHdmiControlCallback callback) {
1655 assertRunOnServiceThread();
1656 HdmiCecLocalDevicePlayback source = playback();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001657 if (source == null) {
1658 Slog.w(TAG, "Local playback device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001659 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001660 return;
1661 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09001662 source.oneTouchPlay(callback);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001663 }
1664
Jungshik Janga5b74142014-06-23 18:03:10 +09001665 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +09001666 private void queryDisplayStatus(final IHdmiControlCallback callback) {
1667 assertRunOnServiceThread();
1668 HdmiCecLocalDevicePlayback source = playback();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001669 if (source == null) {
1670 Slog.w(TAG, "Local playback device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001671 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001672 return;
1673 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09001674 source.queryDisplayStatus(callback);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001675 }
1676
Jinsuk Kim3cd30512014-12-04 11:05:09 +09001677 private void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
1678 final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001679 try {
1680 listener.asBinder().linkToDeath(record, 0);
1681 } catch (RemoteException e) {
1682 Slog.w(TAG, "Listener already died");
1683 return;
1684 }
1685 synchronized (mLock) {
1686 mHotplugEventListenerRecords.add(record);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001687 }
Jinsuk Kim3cd30512014-12-04 11:05:09 +09001688
1689 // Inform the listener of the initial state of each HDMI port by generating
1690 // hotplug events.
1691 runOnServiceThread(new Runnable() {
1692 @Override
1693 public void run() {
1694 synchronized (mLock) {
1695 if (!mHotplugEventListenerRecords.contains(record)) return;
1696 }
1697 for (HdmiPortInfo port : mPortInfo) {
1698 HdmiHotplugEvent event = new HdmiHotplugEvent(port.getId(),
1699 mCecController.isConnected(port.getId()));
1700 synchronized (mLock) {
1701 invokeHotplugEventListenerLocked(listener, event);
1702 }
1703 }
1704 }
1705 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001706 }
1707
1708 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
1709 synchronized (mLock) {
1710 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1711 if (record.mListener.asBinder() == listener.asBinder()) {
1712 listener.asBinder().unlinkToDeath(record, 0);
1713 mHotplugEventListenerRecords.remove(record);
1714 break;
1715 }
1716 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001717 }
1718 }
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001719
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001720 private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001721 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
1722 try {
1723 listener.asBinder().linkToDeath(record, 0);
1724 } catch (RemoteException e) {
1725 Slog.w(TAG, "Listener already died");
1726 return;
1727 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001728 synchronized (mLock) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001729 mDeviceEventListenerRecords.add(record);
1730 }
1731 }
1732
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001733 void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001734 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001735 for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001736 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09001737 record.mListener.onStatusChanged(device, status);
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001738 } catch (RemoteException e) {
1739 Slog.e(TAG, "Failed to report device event:" + e);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001740 }
1741 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001742 }
1743 }
1744
Jungshik Jangea67c182014-06-19 22:19:20 +09001745 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
1746 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
1747 listener);
1748 try {
1749 listener.asBinder().linkToDeath(record, 0);
1750 } catch (RemoteException e) {
1751 Slog.w(TAG, "Listener already died");
1752 return;
1753 }
1754 synchronized (mLock) {
Jungshik Jangea67c182014-06-19 22:19:20 +09001755 mSystemAudioModeChangeListenerRecords.add(record);
1756 }
1757 }
1758
1759 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
1760 synchronized (mLock) {
1761 for (SystemAudioModeChangeListenerRecord record :
1762 mSystemAudioModeChangeListenerRecords) {
1763 if (record.mListener.asBinder() == listener) {
1764 listener.asBinder().unlinkToDeath(record, 0);
1765 mSystemAudioModeChangeListenerRecords.remove(record);
1766 break;
1767 }
1768 }
Jungshik Jangea67c182014-06-19 22:19:20 +09001769 }
1770 }
1771
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001772 private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
Jungshik Jangf4249322014-08-21 14:17:05 +09001773 private final IHdmiInputChangeListener mListener;
1774
1775 public InputChangeListenerRecord(IHdmiInputChangeListener listener) {
1776 mListener = listener;
1777 }
1778
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001779 @Override
1780 public void binderDied() {
1781 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001782 mInputChangeListenerRecord = null;
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001783 }
1784 }
1785 }
1786
1787 private void setInputChangeListener(IHdmiInputChangeListener listener) {
1788 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001789 mInputChangeListenerRecord = new InputChangeListenerRecord(listener);
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001790 try {
1791 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
1792 } catch (RemoteException e) {
1793 Slog.w(TAG, "Listener already died");
1794 return;
1795 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001796 }
1797 }
1798
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001799 void invokeInputChangeListener(HdmiDeviceInfo info) {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001800 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001801 if (mInputChangeListenerRecord != null) {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001802 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09001803 mInputChangeListenerRecord.mListener.onChanged(info);
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001804 } catch (RemoteException e) {
1805 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
1806 }
1807 }
1808 }
1809 }
1810
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001811 private void setHdmiRecordListener(IHdmiRecordListener listener) {
Jungshik Jangb6591b82014-07-23 16:10:23 +09001812 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001813 mRecordListenerRecord = new HdmiRecordListenerRecord(listener);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001814 try {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001815 listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001816 } catch (RemoteException e) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001817 Slog.w(TAG, "Listener already died.", e);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001818 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09001819 }
1820 }
1821
1822 byte[] invokeRecordRequestListener(int recorderAddress) {
1823 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001824 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001825 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09001826 return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001827 } catch (RemoteException e) {
1828 Slog.w(TAG, "Failed to start record.", e);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001829 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09001830 }
1831 return EmptyArray.BYTE;
1832 }
1833 }
1834
Jungshik Jang326aef02014-11-05 12:50:35 +09001835 void invokeOneTouchRecordResult(int recorderAddress, int result) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001836 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001837 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001838 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09001839 mRecordListenerRecord.mListener.onOneTouchRecordResult(recorderAddress, result);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001840 } catch (RemoteException e) {
1841 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
1842 }
1843 }
1844 }
1845 }
1846
Jungshik Jang326aef02014-11-05 12:50:35 +09001847 void invokeTimerRecordingResult(int recorderAddress, int result) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001848 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001849 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001850 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09001851 mRecordListenerRecord.mListener.onTimerRecordingResult(recorderAddress, result);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001852 } catch (RemoteException e) {
Jungshik Jange5a93372014-07-25 13:41:14 +09001853 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
1854 }
1855 }
1856 }
1857 }
1858
Jungshik Jang326aef02014-11-05 12:50:35 +09001859 void invokeClearTimerRecordingResult(int recorderAddress, int result) {
Jungshik Jange5a93372014-07-25 13:41:14 +09001860 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001861 if (mRecordListenerRecord != null) {
Jungshik Jange5a93372014-07-25 13:41:14 +09001862 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09001863 mRecordListenerRecord.mListener.onClearTimerRecordingResult(recorderAddress,
1864 result);
Jungshik Jange5a93372014-07-25 13:41:14 +09001865 } catch (RemoteException e) {
1866 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001867 }
1868 }
1869 }
1870 }
1871
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001872 private void invokeCallback(IHdmiControlCallback callback, int result) {
1873 try {
1874 callback.onComplete(result);
1875 } catch (RemoteException e) {
1876 Slog.e(TAG, "Invoking callback failed:" + e);
1877 }
1878 }
Yuncheol Heo63a2e062014-05-27 23:06:01 +09001879
Jungshik Jangf4249322014-08-21 14:17:05 +09001880 private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener,
Jungshik Jangea67c182014-06-19 22:19:20 +09001881 boolean enabled) {
1882 try {
1883 listener.onStatusChanged(enabled);
1884 } catch (RemoteException e) {
1885 Slog.e(TAG, "Invoking callback failed:" + e);
1886 }
1887 }
1888
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001889 private void announceHotplugEvent(int portId, boolean connected) {
1890 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
Jungshik Jang60cffce2014-06-12 18:03:04 +09001891 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001892 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1893 invokeHotplugEventListenerLocked(record.mListener, event);
Jungshik Jang60cffce2014-06-12 18:03:04 +09001894 }
1895 }
1896 }
1897
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001898 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
Jungshik Jang60cffce2014-06-12 18:03:04 +09001899 HdmiHotplugEvent event) {
1900 try {
1901 listener.onReceived(event);
1902 } catch (RemoteException e) {
1903 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
1904 }
Jungshik Jange81e1082014-06-05 15:37:59 +09001905 }
1906
Jungshik Jang79c58a42014-06-16 16:45:36 +09001907 private HdmiCecLocalDeviceTv tv() {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001908 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
Jungshik Jang79c58a42014-06-16 16:45:36 +09001909 }
1910
Yuncheol Heoe946ed82014-07-25 14:05:19 +09001911 boolean isTvDevice() {
Yuncheol Heob8d62e72014-09-22 19:53:41 +09001912 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
Yuncheol Heoe946ed82014-07-25 14:05:19 +09001913 }
1914
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001915 boolean isTvDeviceEnabled() {
1916 return isTvDevice() && tv() != null;
1917 }
1918
Jungshik Jang79c58a42014-06-16 16:45:36 +09001919 private HdmiCecLocalDevicePlayback playback() {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001920 return (HdmiCecLocalDevicePlayback)
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001921 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
Jungshik Jang60cffce2014-06-12 18:03:04 +09001922 }
Jungshik Janga858d222014-06-23 17:17:47 +09001923
1924 AudioManager getAudioManager() {
1925 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
1926 }
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001927
1928 boolean isControlEnabled() {
1929 synchronized (mLock) {
1930 return mHdmiControlEnabled;
1931 }
1932 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09001933
Jungshik Jangf67113f2014-08-22 16:27:19 +09001934 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09001935 int getPowerStatus() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09001936 assertRunOnServiceThread();
Yuncheol Heo38db6292014-07-01 14:15:14 +09001937 return mPowerStatus;
1938 }
1939
Jungshik Jangf67113f2014-08-22 16:27:19 +09001940 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09001941 boolean isPowerOnOrTransient() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09001942 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001943 return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
1944 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Yuncheol Heo38db6292014-07-01 14:15:14 +09001945 }
1946
Jungshik Jangf67113f2014-08-22 16:27:19 +09001947 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09001948 boolean isPowerStandbyOrTransient() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09001949 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001950 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
1951 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09001952 }
1953
Jungshik Jangf67113f2014-08-22 16:27:19 +09001954 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09001955 boolean isPowerStandby() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09001956 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001957 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09001958 }
1959
1960 @ServiceThreadOnly
1961 void wakeUp() {
1962 assertRunOnServiceThread();
Yuncheol Heofc44e4e2014-08-04 19:41:09 +09001963 mWakeUpMessageReceived = true;
Jinsuk Kime26d8332015-01-09 08:55:41 +09001964 mPowerManager.wakeUp(SystemClock.uptimeMillis());
Yuncheol Heo38db6292014-07-01 14:15:14 +09001965 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
1966 // the intent, the sequence will continue at onWakeUp().
1967 }
1968
1969 @ServiceThreadOnly
1970 void standby() {
1971 assertRunOnServiceThread();
1972 mStandbyMessageReceived = true;
Jinsuk Kime26d8332015-01-09 08:55:41 +09001973 mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
Yuncheol Heo38db6292014-07-01 14:15:14 +09001974 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
1975 // the intent, the sequence will continue at onStandby().
1976 }
1977
1978 @ServiceThreadOnly
1979 private void onWakeUp() {
1980 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001981 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Yuncheol Heo38db6292014-07-01 14:15:14 +09001982 if (mCecController != null) {
Jungshik Janga9f10622014-07-11 15:36:39 +09001983 if (mHdmiControlEnabled) {
Yuncheol Heofc44e4e2014-08-04 19:41:09 +09001984 int startReason = INITIATED_BY_SCREEN_ON;
1985 if (mWakeUpMessageReceived) {
1986 startReason = INITIATED_BY_WAKE_UP_MESSAGE;
1987 }
1988 initializeCec(startReason);
Jungshik Janga9f10622014-07-11 15:36:39 +09001989 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09001990 } else {
1991 Slog.i(TAG, "Device does not support HDMI-CEC.");
1992 }
1993 // TODO: Initialize MHL local devices.
1994 }
1995
1996 @ServiceThreadOnly
1997 private void onStandby() {
1998 assertRunOnServiceThread();
Jinsuk Kime26d8332015-01-09 08:55:41 +09001999 if (!canGoToStandby()) return;
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002000 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
Yuncheol Heo0608b932014-10-13 16:39:18 +09002001 invokeVendorCommandListenersOnControlStateChanged(false,
2002 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002003
2004 final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
2005 disableDevices(new PendingActionClearedCallback() {
2006 @Override
2007 public void onCleared(HdmiCecLocalDevice device) {
2008 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
2009 devices.remove(device);
2010 if (devices.isEmpty()) {
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002011 onStandbyCompleted();
Yuncheol Heo4b542712014-07-30 20:31:06 +09002012 // We will not clear local devices here, since some OEM/SOC will keep passing
2013 // the received packets until the application processor enters to the sleep
2014 // actually.
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002015 }
2016 }
2017 });
2018 }
2019
Jinsuk Kime26d8332015-01-09 08:55:41 +09002020 private boolean canGoToStandby() {
2021 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2022 if (!device.canGoToStandby()) return false;
2023 }
2024 return true;
2025 }
2026
Terry Heo1ca0a432014-08-18 10:30:32 +09002027 @ServiceThreadOnly
2028 private void onLanguageChanged(String language) {
2029 assertRunOnServiceThread();
2030 mLanguage = language;
2031
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002032 if (isTvDeviceEnabled()) {
Terry Heo1ca0a432014-08-18 10:30:32 +09002033 tv().broadcastMenuLanguage(language);
Jinsuk Kim5b8cb002015-01-19 07:30:12 +09002034 mCecController.setOption(OPTION_CEC_SET_LANGUAGE, HdmiUtils.languageToInt(language));
Terry Heo1ca0a432014-08-18 10:30:32 +09002035 }
2036 }
2037
Jungshik Jangf67113f2014-08-22 16:27:19 +09002038 @ServiceThreadOnly
2039 String getLanguage() {
2040 assertRunOnServiceThread();
2041 return mLanguage;
2042 }
2043
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002044 private void disableDevices(PendingActionClearedCallback callback) {
Jungshik Jang350e68d2014-08-19 18:56:21 +09002045 if (mCecController != null) {
2046 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2047 device.disableDevice(mStandbyMessageReceived, callback);
2048 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002049 }
Jungshik Jang350e68d2014-08-19 18:56:21 +09002050
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09002051 mMhlController.clearAllLocalDevices();
Yuncheol Heo38db6292014-07-01 14:15:14 +09002052 }
2053
Yuncheol Heo38db6292014-07-01 14:15:14 +09002054 @ServiceThreadOnly
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002055 private void clearLocalDevices() {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002056 assertRunOnServiceThread();
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002057 if (mCecController == null) {
2058 return;
2059 }
2060 mCecController.clearLogicalAddress();
2061 mCecController.clearLocalDevices();
2062 }
2063
2064 @ServiceThreadOnly
2065 private void onStandbyCompleted() {
2066 assertRunOnServiceThread();
2067 Slog.v(TAG, "onStandbyCompleted");
2068
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002069 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002070 return;
2071 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002072 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002073 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002074 device.onStandby(mStandbyMessageReceived);
Yuncheol Heo38db6292014-07-01 14:15:14 +09002075 }
2076 mStandbyMessageReceived = false;
Jinsuk Kim964c00d2015-01-16 15:20:17 +09002077 mAddressAllocated = false;
Jinsuk Kim50084862014-08-07 13:11:40 +09002078 mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, DISABLED);
Jinsuk Kim5b8cb002015-01-19 07:30:12 +09002079 mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
Yuncheol Heo38db6292014-07-01 14:15:14 +09002080 }
Jinsuk Kim4d43d932014-07-03 16:43:58 +09002081
Jinsuk Kim119160a2014-07-07 18:48:10 +09002082 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
2083 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
2084 try {
2085 listener.asBinder().linkToDeath(record, 0);
2086 } catch (RemoteException e) {
2087 Slog.w(TAG, "Listener already died");
2088 return;
2089 }
2090 synchronized (mLock) {
2091 mVendorCommandListenerRecords.add(record);
2092 }
2093 }
2094
Yuncheol Heo0608b932014-10-13 16:39:18 +09002095 boolean invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress,
2096 byte[] params, boolean hasVendorId) {
Jinsuk Kim119160a2014-07-07 18:48:10 +09002097 synchronized (mLock) {
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09002098 if (mVendorCommandListenerRecords.isEmpty()) {
2099 return false;
2100 }
Jinsuk Kim119160a2014-07-07 18:48:10 +09002101 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2102 if (record.mDeviceType != deviceType) {
2103 continue;
2104 }
2105 try {
Yuncheol Heo0608b932014-10-13 16:39:18 +09002106 record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId);
Jinsuk Kim119160a2014-07-07 18:48:10 +09002107 } catch (RemoteException e) {
2108 Slog.e(TAG, "Failed to notify vendor command reception", e);
2109 }
2110 }
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09002111 return true;
Jinsuk Kim119160a2014-07-07 18:48:10 +09002112 }
2113 }
2114
Yuncheol Heo0608b932014-10-13 16:39:18 +09002115 boolean invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason) {
2116 synchronized (mLock) {
2117 if (mVendorCommandListenerRecords.isEmpty()) {
2118 return false;
2119 }
2120 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2121 try {
2122 record.mListener.onControlStateChanged(enabled, reason);
2123 } catch (RemoteException e) {
2124 Slog.e(TAG, "Failed to notify control-state-changed to vendor handler", e);
2125 }
2126 }
2127 return true;
2128 }
2129 }
2130
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002131 private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {
2132 HdmiMhlVendorCommandListenerRecord record =
2133 new HdmiMhlVendorCommandListenerRecord(listener);
Jungshik Jangf4249322014-08-21 14:17:05 +09002134 try {
2135 listener.asBinder().linkToDeath(record, 0);
2136 } catch (RemoteException e) {
2137 Slog.w(TAG, "Listener already died.");
2138 return;
2139 }
2140
2141 synchronized (mLock) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002142 mMhlVendorCommandListenerRecords.add(record);
Jungshik Jangf4249322014-08-21 14:17:05 +09002143 }
2144 }
2145
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002146 void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002147 synchronized (mLock) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002148 for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002149 try {
2150 record.mListener.onReceived(portId, offest, length, data);
2151 } catch (RemoteException e) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002152 Slog.e(TAG, "Failed to notify MHL vendor command", e);
Jungshik Jangf4249322014-08-21 14:17:05 +09002153 }
2154 }
2155 }
2156 }
2157
Jinsuk Kim4d43d932014-07-03 16:43:58 +09002158 boolean isProhibitMode() {
2159 synchronized (mLock) {
2160 return mProhibitMode;
2161 }
2162 }
2163
2164 void setProhibitMode(boolean enabled) {
2165 synchronized (mLock) {
2166 mProhibitMode = enabled;
2167 }
2168 }
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002169
2170 @ServiceThreadOnly
Jungshik Jang350e68d2014-08-19 18:56:21 +09002171 void setCecOption(int key, int value) {
Jinsuk Kim50084862014-08-07 13:11:40 +09002172 assertRunOnServiceThread();
2173 mCecController.setOption(key, value);
2174 }
2175
2176 @ServiceThreadOnly
2177 void setControlEnabled(boolean enabled) {
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002178 assertRunOnServiceThread();
2179
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002180 synchronized (mLock) {
2181 mHdmiControlEnabled = enabled;
2182 }
2183
2184 if (enabled) {
Yuncheol Heof1702482014-11-27 19:52:01 +09002185 enableHdmiControlService();
2186 return;
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002187 }
Yuncheol Heof1702482014-11-27 19:52:01 +09002188 // Call the vendor handler before the service is disabled.
2189 invokeVendorCommandListenersOnControlStateChanged(false,
2190 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING);
2191 // Post the remained tasks in the service thread again to give the vendor-issued-tasks
2192 // a chance to run.
2193 runOnServiceThread(new Runnable() {
2194 @Override
2195 public void run() {
2196 disableHdmiControlService();
2197 }
2198 });
2199 return;
2200 }
2201
2202 @ServiceThreadOnly
2203 private void enableHdmiControlService() {
2204 mCecController.setOption(OPTION_CEC_ENABLE, ENABLED);
2205 mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED);
2206
2207 initializeCec(INITIATED_BY_ENABLE_CEC);
2208 }
2209
2210 @ServiceThreadOnly
2211 private void disableHdmiControlService() {
2212 disableDevices(new PendingActionClearedCallback() {
2213 @Override
2214 public void onCleared(HdmiCecLocalDevice device) {
2215 assertRunOnServiceThread();
2216 mCecController.flush(new Runnable() {
2217 @Override
2218 public void run() {
2219 mCecController.setOption(OPTION_CEC_ENABLE, DISABLED);
2220 mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED);
2221 clearLocalDevices();
2222 }
2223 });
2224 }
2225 });
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002226 }
Jungshik Jang867b4e02014-08-12 13:41:30 +09002227
2228 @ServiceThreadOnly
2229 void setActivePortId(int portId) {
2230 assertRunOnServiceThread();
2231 mActivePortId = portId;
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002232
2233 // Resets last input for MHL, which stays valid only after the MHL device was selected,
2234 // and no further switching is done.
2235 setLastInputForMhl(Constants.INVALID_PORT_ID);
Jungshik Jang867b4e02014-08-12 13:41:30 +09002236 }
Yuncheol Heo08a1be82014-08-12 20:58:41 +09002237
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002238 @ServiceThreadOnly
2239 void setLastInputForMhl(int portId) {
2240 assertRunOnServiceThread();
2241 mLastInputMhl = portId;
2242 }
2243
2244 @ServiceThreadOnly
2245 int getLastInputForMhl() {
2246 assertRunOnServiceThread();
2247 return mLastInputMhl;
2248 }
2249
2250 /**
2251 * Performs input change, routing control for MHL device.
2252 *
2253 * @param portId MHL port, or the last port to go back to if {@code contentOn} is false
2254 * @param contentOn {@code true} if RAP data is content on; otherwise false
2255 */
2256 @ServiceThreadOnly
2257 void changeInputForMhl(int portId, boolean contentOn) {
2258 assertRunOnServiceThread();
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002259 if (tv() == null) return;
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002260 final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID;
Jinsuk Kimcb8661c2015-01-19 12:39:06 +09002261 if (portId != Constants.INVALID_PORT_ID) {
2262 tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() {
2263 @Override
2264 public void onComplete(int result) throws RemoteException {
2265 // Keep the last input to switch back later when RAP[ContentOff] is received.
2266 // This effectively sets the port to invalid one if the switching is for
2267 // RAP[ContentOff].
2268 setLastInputForMhl(lastInput);
2269 }
2270 });
2271 }
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002272 // MHL device is always directly connected to the port. Update the active port ID to avoid
2273 // unnecessary post-routing control task.
2274 tv().setActivePortId(portId);
2275
2276 // The port is either the MHL-enabled port where the mobile device is connected, or
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09002277 // the last port to go back to when turnoff command is received. Note that the last port
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002278 // may not be the MHL-enabled one. In this case the device info to be passed to
2279 // input change listener should be the one describing the corresponding HDMI port.
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09002280 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jinsuk Kimcb8661c2015-01-19 12:39:06 +09002281 HdmiDeviceInfo info = (device != null) ? device.getInfo()
2282 : mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002283 invokeInputChangeListener(info);
2284 }
2285
2286 void setMhlInputChangeEnabled(boolean enabled) {
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09002287 mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
Yuncheol Heo08a1be82014-08-12 20:58:41 +09002288
2289 synchronized (mLock) {
2290 mMhlInputChangeEnabled = enabled;
2291 }
2292 }
2293
2294 boolean isMhlInputChangeEnabled() {
2295 synchronized (mLock) {
2296 return mMhlInputChangeEnabled;
2297 }
2298 }
Jungshik Jang339227d2014-08-25 15:37:20 +09002299
2300 @ServiceThreadOnly
2301 void displayOsd(int messageId) {
2302 assertRunOnServiceThread();
2303 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2304 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2305 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2306 HdmiControlService.PERMISSION);
2307 }
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09002308
2309 @ServiceThreadOnly
2310 void displayOsd(int messageId, int extra) {
2311 assertRunOnServiceThread();
2312 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2313 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
Yuncheol Heo2b0da5c2014-10-22 14:32:27 +09002314 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra);
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09002315 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2316 HdmiControlService.PERMISSION);
2317 }
Jungshik Jang0792d372014-04-23 17:57:26 +09002318}