blob: 49a96d81a6cc8ec4f586ec3b08052808b77f8d5f [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
Jungshik Jang79c58a42014-06-16 16:45:36 +0900783 void runOnServiceThread(Runnable runnable) {
Jungshik Jang67ea5212014-05-15 14:05:24 +0900784 mHandler.post(runnable);
785 }
786
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900787 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
788 mHandler.postAtFrontOfQueue(runnable);
789 }
790
791 private void assertRunOnServiceThread() {
792 if (Looper.myLooper() != mHandler.getLooper()) {
793 throw new IllegalStateException("Should run on service thread.");
794 }
795 }
796
Jungshik Jang67ea5212014-05-15 14:05:24 +0900797 /**
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900798 * Transmit a CEC command to CEC bus.
799 *
800 * @param command CEC command to send out
Jungshik Jangd643f762014-05-22 19:28:09 +0900801 * @param callback interface used to the result of send command
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900802 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900803 @ServiceThreadOnly
Jungshik Jangd643f762014-05-22 19:28:09 +0900804 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900805 assertRunOnServiceThread();
Yuncheol Heo4c212892014-09-12 14:32:46 +0900806 if (mMessageValidator.isValid(command) == HdmiCecMessageValidator.OK) {
Jungshik Jang5f75cbd2014-08-07 12:02:29 +0900807 mCecController.sendCommand(command, callback);
808 } else {
Jungshik Jang2e8f1b62014-09-03 08:28:02 +0900809 HdmiLogger.error("Invalid message type:" + command);
Jungshik Jang5f75cbd2014-08-07 12:02:29 +0900810 if (callback != null) {
811 callback.onSendCompleted(Constants.SEND_RESULT_FAILURE);
812 }
813 }
Jungshik Jangd643f762014-05-22 19:28:09 +0900814 }
815
Jungshik Janga5b74142014-06-23 18:03:10 +0900816 @ServiceThreadOnly
Jungshik Jangd643f762014-05-22 19:28:09 +0900817 void sendCecCommand(HdmiCecMessage command) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900818 assertRunOnServiceThread();
Jungshik Jang5f75cbd2014-08-07 12:02:29 +0900819 sendCecCommand(command, null);
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900820 }
821
Yuncheol Heo6aae6522014-08-05 14:48:37 +0900822 /**
823 * Send <Feature Abort> command on the given CEC message if possible.
824 * If the aborted message is invalid, then it wont send the message.
825 * @param command original command to be aborted
826 * @param reason reason of feature abort
827 */
828 @ServiceThreadOnly
829 void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) {
830 assertRunOnServiceThread();
831 mCecController.maySendFeatureAbortCommand(command, reason);
832 }
833
Jungshik Janga5b74142014-06-23 18:03:10 +0900834 @ServiceThreadOnly
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900835 boolean handleCecCommand(HdmiCecMessage message) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900836 assertRunOnServiceThread();
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900837 if (!mAddressAllocated) {
838 mCecMessageBuffer.bufferMessage(message);
839 return true;
840 }
Yuncheol Heo4c212892014-09-12 14:32:46 +0900841 int errorCode = mMessageValidator.isValid(message);
842 if (errorCode != HdmiCecMessageValidator.OK) {
Yuncheol Heoa95f1a92014-11-06 08:25:39 +0900843 // We'll not response on the messages with the invalid source or destination
844 // or with parameter length shorter than specified in the standard.
Yuncheol Heo4c212892014-09-12 14:32:46 +0900845 if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) {
846 maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
847 }
848 return true;
Yuncheol Heo75a77e72014-07-09 18:27:53 +0900849 }
Jungshik Jang092b4452014-06-11 15:19:17 +0900850 return dispatchMessageToLocalDevice(message);
851 }
852
Jinsuk Kim1481a422014-12-17 16:15:05 +0900853 void setAudioReturnChannel(int portId, boolean enabled) {
854 mCecController.setAudioReturnChannel(portId, enabled);
Jungshik Jang60cffce2014-06-12 18:03:04 +0900855 }
856
Jungshik Janga5b74142014-06-23 18:03:10 +0900857 @ServiceThreadOnly
Jungshik Jang092b4452014-06-11 15:19:17 +0900858 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900859 assertRunOnServiceThread();
Jungshik Jang092b4452014-06-11 15:19:17 +0900860 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900861 if (device.dispatchMessage(message)
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900862 && message.getDestination() != Constants.ADDR_BROADCAST) {
Jungshik Jang092b4452014-06-11 15:19:17 +0900863 return true;
864 }
865 }
Jungshik Jang60cffce2014-06-12 18:03:04 +0900866
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900867 if (message.getDestination() != Constants.ADDR_BROADCAST) {
Jungshik Jang2e8f1b62014-09-03 08:28:02 +0900868 HdmiLogger.warning("Unhandled cec command:" + message);
Jungshik Jang3a959fc2014-07-03 09:34:05 +0900869 }
Jungshik Jang092b4452014-06-11 15:19:17 +0900870 return false;
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900871 }
872
Jungshik Jang67ea5212014-05-15 14:05:24 +0900873 /**
874 * Called when a new hotplug event is issued.
875 *
Jinsuk Kimed086452014-08-18 15:01:53 +0900876 * @param portId hdmi port number where hot plug event issued.
Jungshik Jang67ea5212014-05-15 14:05:24 +0900877 * @param connected whether to be plugged in or not
878 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900879 @ServiceThreadOnly
Jinsuk Kimed086452014-08-18 15:01:53 +0900880 void onHotplug(int portId, boolean connected) {
Jungshik Jang60cffce2014-06-12 18:03:04 +0900881 assertRunOnServiceThread();
Yuncheol Heob5021862014-09-02 10:36:04 +0900882
Yuncheol Heob8d62e72014-09-22 19:53:41 +0900883 if (connected && !isTvDevice()) {
884 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
885 for (int type : mLocalDevices) {
886 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
887 if (localDevice == null) {
888 localDevice = HdmiCecLocalDevice.create(this, type);
889 localDevice.init();
890 }
891 localDevices.add(localDevice);
Yuncheol Heob5021862014-09-02 10:36:04 +0900892 }
Yuncheol Heob8d62e72014-09-22 19:53:41 +0900893 allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
Yuncheol Heob5021862014-09-02 10:36:04 +0900894 }
Yuncheol Heob5021862014-09-02 10:36:04 +0900895
Jungshik Jang79c58a42014-06-16 16:45:36 +0900896 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jinsuk Kimed086452014-08-18 15:01:53 +0900897 device.onHotplug(portId, connected);
Jungshik Jang60cffce2014-06-12 18:03:04 +0900898 }
Jinsuk Kimed086452014-08-18 15:01:53 +0900899 announceHotplugEvent(portId, connected);
Jungshik Jang67ea5212014-05-15 14:05:24 +0900900 }
901
Jungshik Jang02bb4262014-05-23 16:48:31 +0900902 /**
903 * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
904 * devices.
905 *
906 * @param callback an interface used to get a list of all remote devices' address
Jungshik Jang1de51422014-07-03 11:14:26 +0900907 * @param sourceAddress a logical address of source device where sends polling message
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900908 * @param pickStrategy strategy how to pick polling candidates
Jungshik Jang02bb4262014-05-23 16:48:31 +0900909 * @param retryCount the number of retry used to send polling message to remote devices
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900910 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
Jungshik Jang02bb4262014-05-23 16:48:31 +0900911 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900912 @ServiceThreadOnly
Jungshik Jang1de51422014-07-03 11:14:26 +0900913 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
914 int retryCount) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900915 assertRunOnServiceThread();
Jungshik Jang1de51422014-07-03 11:14:26 +0900916 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
917 retryCount);
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900918 }
919
920 private int checkPollStrategy(int pickStrategy) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900921 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900922 if (strategy == 0) {
923 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
924 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900925 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900926 if (iterationStrategy == 0) {
927 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
928 }
929 return strategy | iterationStrategy;
Jungshik Jang02bb4262014-05-23 16:48:31 +0900930 }
931
Jungshik Jang60cffce2014-06-12 18:03:04 +0900932 List<HdmiCecLocalDevice> getAllLocalDevices() {
933 assertRunOnServiceThread();
934 return mCecController.getLocalDeviceList();
935 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900936
Jungshik Jang79c58a42014-06-16 16:45:36 +0900937 Object getServiceLock() {
938 return mLock;
939 }
940
941 void setAudioStatus(boolean mute, int volume) {
Jungshik Jangb69aafbf2014-07-11 16:29:06 +0900942 AudioManager audioManager = getAudioManager();
943 boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
944 if (mute) {
945 if (!muted) {
946 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
947 }
948 } else {
949 if (muted) {
950 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
951 }
952 // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
953 // volume change notification back to hdmi control service.
954 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
Jungshik Jang1a6be6e2014-09-16 11:04:54 +0900955 AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
Jungshik Jangb69aafbf2014-07-11 16:29:06 +0900956 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900957 }
958
Jungshik Jangea67c182014-06-19 22:19:20 +0900959 void announceSystemAudioModeChange(boolean enabled) {
Jungshik Jangf4249322014-08-21 14:17:05 +0900960 synchronized (mLock) {
961 for (SystemAudioModeChangeListenerRecord record :
962 mSystemAudioModeChangeListenerRecords) {
963 invokeSystemAudioModeChangeLocked(record.mListener, enabled);
964 }
Jungshik Jangea67c182014-06-19 22:19:20 +0900965 }
966 }
967
Jungshik Jang410ca9c2014-08-07 18:04:14 +0900968 private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
Jungshik Jang42c98002014-06-12 13:17:44 +0900969 // TODO: find better name instead of model name.
970 String displayName = Build.MODEL;
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900971 return new HdmiDeviceInfo(logicalAddress,
Jinsuk Kim2b152012014-07-25 08:22:26 +0900972 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
973 getVendorId(), displayName);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900974 }
975
Jungshik Jang7df52862014-08-11 14:35:27 +0900976 @ServiceThreadOnly
Jungshik Jang7df52862014-08-11 14:35:27 +0900977 void handleMhlHotplugEvent(int portId, boolean connected) {
978 assertRunOnServiceThread();
Jinsuk Kim93eed0c2014-10-14 11:52:22 +0900979 // Hotplug event is used to add/remove MHL devices as TV input.
Jungshik Jang7df52862014-08-11 14:35:27 +0900980 if (connected) {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +0900981 HdmiMhlLocalDeviceStub newDevice = new HdmiMhlLocalDeviceStub(this, portId);
982 HdmiMhlLocalDeviceStub oldDevice = mMhlController.addLocalDevice(newDevice);
Jungshik Jang7df52862014-08-11 14:35:27 +0900983 if (oldDevice != null) {
984 oldDevice.onDeviceRemoved();
985 Slog.i(TAG, "Old device of port " + portId + " is removed");
986 }
Jinsuk Kim93eed0c2014-10-14 11:52:22 +0900987 invokeDeviceEventListeners(newDevice.getInfo(), DEVICE_EVENT_ADD_DEVICE);
988 updateSafeMhlInput();
Jungshik Jang7df52862014-08-11 14:35:27 +0900989 } else {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +0900990 HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +0900991 if (device != null) {
992 device.onDeviceRemoved();
Jinsuk Kim93eed0c2014-10-14 11:52:22 +0900993 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE);
994 updateSafeMhlInput();
Jungshik Jang7df52862014-08-11 14:35:27 +0900995 } else {
996 Slog.w(TAG, "No device to remove:[portId=" + portId);
997 }
998 }
Jinsuk Kimed086452014-08-18 15:01:53 +0900999 announceHotplugEvent(portId, connected);
Jungshik Jang7df52862014-08-11 14:35:27 +09001000 }
1001
1002 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001003 void handleMhlBusModeChanged(int portId, int busmode) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001004 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001005 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001006 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001007 device.setBusMode(busmode);
Jungshik Jang7df52862014-08-11 14:35:27 +09001008 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001009 Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId +
1010 ", busmode:" + busmode + "]");
Jungshik Jang7df52862014-08-11 14:35:27 +09001011 }
1012 }
1013
1014 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001015 void handleMhlBusOvercurrent(int portId, boolean on) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001016 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001017 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001018 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001019 device.onBusOvercurrentDetected(on);
Jungshik Jang7df52862014-08-11 14:35:27 +09001020 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001021 Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]");
Jungshik Jang7df52862014-08-11 14:35:27 +09001022 }
1023 }
1024
1025 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001026 void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001027 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001028 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jinsuk Kimed086452014-08-18 15:01:53 +09001029
Jungshik Jang7df52862014-08-11 14:35:27 +09001030 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001031 device.setDeviceStatusChange(adopterId, deviceId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001032 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001033 Slog.w(TAG, "No mhl device exists for device status event[portId:"
Jungshik Jang7df52862014-08-11 14:35:27 +09001034 + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]");
1035 }
1036 }
1037
Jinsuk Kimed086452014-08-18 15:01:53 +09001038 @ServiceThreadOnly
1039 private void updateSafeMhlInput() {
1040 assertRunOnServiceThread();
1041 List<HdmiDeviceInfo> inputs = Collections.emptyList();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001042 SparseArray<HdmiMhlLocalDeviceStub> devices = mMhlController.getAllLocalDevices();
Jinsuk Kimed086452014-08-18 15:01:53 +09001043 for (int i = 0; i < devices.size(); ++i) {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001044 HdmiMhlLocalDeviceStub device = devices.valueAt(i);
Jinsuk Kimed086452014-08-18 15:01:53 +09001045 HdmiDeviceInfo info = device.getInfo();
1046 if (info != null) {
1047 if (inputs.isEmpty()) {
1048 inputs = new ArrayList<>();
1049 }
1050 inputs.add(device.getInfo());
1051 }
1052 }
1053 synchronized (mLock) {
1054 mMhlDevices = inputs;
1055 }
1056 }
1057
1058 private List<HdmiDeviceInfo> getMhlDevicesLocked() {
1059 return mMhlDevices;
1060 }
1061
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001062 private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient {
1063 private final IHdmiMhlVendorCommandListener mListener;
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001064
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001065 public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) {
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001066 mListener = listener;
1067 }
1068
1069 @Override
1070 public void binderDied() {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001071 mMhlVendorCommandListenerRecords.remove(this);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001072 }
1073 }
1074
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001075 // Record class that monitors the event of the caller of being killed. Used to clean up
1076 // the listener list and record list accordingly.
1077 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
1078 private final IHdmiHotplugEventListener mListener;
1079
1080 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
1081 mListener = listener;
1082 }
1083
1084 @Override
1085 public void binderDied() {
1086 synchronized (mLock) {
1087 mHotplugEventListenerRecords.remove(this);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001088 }
1089 }
Jinsuk Kim3cd30512014-12-04 11:05:09 +09001090
1091 @Override
1092 public boolean equals(Object obj) {
1093 if (!(obj instanceof HotplugEventListenerRecord)) return false;
1094 if (obj == this) return true;
1095 HotplugEventListenerRecord other = (HotplugEventListenerRecord) obj;
1096 return other.mListener == this.mListener;
1097 }
1098
1099 @Override
1100 public int hashCode() {
1101 return mListener.hashCode();
1102 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001103 }
1104
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001105 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
1106 private final IHdmiDeviceEventListener mListener;
1107
1108 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
1109 mListener = listener;
1110 }
1111
1112 @Override
Jungshik Jangea67c182014-06-19 22:19:20 +09001113 public void binderDied() {
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001114 synchronized (mLock) {
1115 mDeviceEventListenerRecords.remove(this);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001116 }
1117 }
1118 }
1119
Jungshik Jangea67c182014-06-19 22:19:20 +09001120 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
Yuncheol Heo38db6292014-07-01 14:15:14 +09001121 private final IHdmiSystemAudioModeChangeListener mListener;
Jungshik Jangea67c182014-06-19 22:19:20 +09001122
1123 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
1124 mListener = listener;
1125 }
1126
1127 @Override
1128 public void binderDied() {
1129 synchronized (mLock) {
1130 mSystemAudioModeChangeListenerRecords.remove(this);
Jungshik Jangea67c182014-06-19 22:19:20 +09001131 }
1132 }
1133 }
1134
Jinsuk Kim119160a2014-07-07 18:48:10 +09001135 class VendorCommandListenerRecord implements IBinder.DeathRecipient {
1136 private final IHdmiVendorCommandListener mListener;
1137 private final int mDeviceType;
1138
1139 public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
1140 mListener = listener;
1141 mDeviceType = deviceType;
1142 }
1143
1144 @Override
1145 public void binderDied() {
1146 synchronized (mLock) {
1147 mVendorCommandListenerRecords.remove(this);
1148 }
1149 }
1150 }
1151
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001152 private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
Jungshik Jangf4249322014-08-21 14:17:05 +09001153 private final IHdmiRecordListener mListener;
1154
1155 public HdmiRecordListenerRecord(IHdmiRecordListener listener) {
1156 mListener = listener;
1157 }
1158
Jungshik Jangb6591b82014-07-23 16:10:23 +09001159 @Override
1160 public void binderDied() {
1161 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001162 mRecordListenerRecord = null;
Jungshik Jangb6591b82014-07-23 16:10:23 +09001163 }
1164 }
1165 }
1166
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001167 private void enforceAccessPermission() {
1168 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
1169 }
1170
1171 private final class BinderService extends IHdmiControlService.Stub {
1172 @Override
1173 public int[] getSupportedTypes() {
1174 enforceAccessPermission();
Jinsuk Kim0340bbc2014-06-05 11:07:47 +09001175 // mLocalDevices is an unmodifiable list - no lock necesary.
1176 int[] localDevices = new int[mLocalDevices.size()];
1177 for (int i = 0; i < localDevices.length; ++i) {
1178 localDevices[i] = mLocalDevices.get(i);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001179 }
Jinsuk Kim0340bbc2014-06-05 11:07:47 +09001180 return localDevices;
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001181 }
1182
1183 @Override
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001184 public HdmiDeviceInfo getActiveSource() {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001185 enforceAccessPermission();
Jinsuk Kim7e742062014-07-30 13:19:13 +09001186 HdmiCecLocalDeviceTv tv = tv();
1187 if (tv == null) {
1188 Slog.w(TAG, "Local tv device not available");
1189 return null;
1190 }
1191 ActiveSource activeSource = tv.getActiveSource();
1192 if (activeSource.isValid()) {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001193 return new HdmiDeviceInfo(activeSource.logicalAddress,
1194 activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID,
1195 HdmiDeviceInfo.DEVICE_INACTIVE, 0, "");
Jinsuk Kim7e742062014-07-30 13:19:13 +09001196 }
1197 int activePath = tv.getActivePath();
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001198 if (activePath != HdmiDeviceInfo.PATH_INVALID) {
Jinsuk Kim7640d982015-01-28 16:44:07 +09001199 HdmiDeviceInfo info = tv.getSafeDeviceInfoByPath(activePath);
Jinsuk Kimd47abef2015-01-17 07:38:24 +09001200 return (info != null) ? info : new HdmiDeviceInfo(activePath, tv.getActivePortId());
Jinsuk Kim7e742062014-07-30 13:19:13 +09001201 }
1202 return null;
1203 }
1204
1205 @Override
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001206 public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) {
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001207 enforceAccessPermission();
1208 runOnServiceThread(new Runnable() {
1209 @Override
1210 public void run() {
Jinsuk Kim72b7d732014-07-24 09:15:35 +09001211 if (callback == null) {
1212 Slog.e(TAG, "Callback cannot be null");
1213 return;
1214 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09001215 HdmiCecLocalDeviceTv tv = tv();
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001216 if (tv == null) {
Jinsuk Kima062a932014-06-18 10:00:39 +09001217 Slog.w(TAG, "Local tv device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001218 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001219 return;
1220 }
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001221 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001222 if (device != null) {
1223 if (device.getPortId() == tv.getActivePortId()) {
1224 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
Jinsuk Kim87f22a22014-08-20 10:40:12 +09001225 return;
1226 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001227 // Upon selecting MHL device, we send RAP[Content On] to wake up
1228 // the connected mobile device, start routing control to switch ports.
1229 // callback is handled by MHL action.
1230 device.turnOn(callback);
Yuncheol Heo7c5d31e2014-09-03 16:28:54 +09001231 tv.doManualPortSwitching(device.getPortId(), null);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001232 return;
Jinsuk Kim87f22a22014-08-20 10:40:12 +09001233 }
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001234 tv.deviceSelect(deviceId, callback);
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001235 }
1236 });
1237 }
1238
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001239 @Override
Jinsuk Kima062a932014-06-18 10:00:39 +09001240 public void portSelect(final int portId, final IHdmiControlCallback callback) {
1241 enforceAccessPermission();
1242 runOnServiceThread(new Runnable() {
1243 @Override
1244 public void run() {
Jinsuk Kim72b7d732014-07-24 09:15:35 +09001245 if (callback == null) {
1246 Slog.e(TAG, "Callback cannot be null");
1247 return;
1248 }
Jinsuk Kima062a932014-06-18 10:00:39 +09001249 HdmiCecLocalDeviceTv tv = tv();
1250 if (tv == null) {
1251 Slog.w(TAG, "Local tv device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001252 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kima062a932014-06-18 10:00:39 +09001253 return;
1254 }
Jinsuk Kim83335712014-06-24 07:57:00 +09001255 tv.doManualPortSwitching(portId, callback);
Jinsuk Kima062a932014-06-18 10:00:39 +09001256 }
1257 });
1258 }
1259
1260 @Override
Jinsuk Kimc068bb52014-07-07 16:59:20 +09001261 public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
Jinsuk Kima062a932014-06-18 10:00:39 +09001262 enforceAccessPermission();
1263 runOnServiceThread(new Runnable() {
1264 @Override
1265 public void run() {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001266 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001267 if (device != null) {
1268 device.sendKeyEvent(keyCode, isPressed);
1269 return;
Jinsuk Kima062a932014-06-18 10:00:39 +09001270 }
Jungshik Jang4612a6e2014-08-12 22:01:23 +09001271 if (mCecController != null) {
1272 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
1273 if (localDevice == null) {
1274 Slog.w(TAG, "Local device not available");
1275 return;
1276 }
1277 localDevice.sendKeyEvent(keyCode, isPressed);
1278 }
Jinsuk Kima062a932014-06-18 10:00:39 +09001279 }
1280 });
1281 }
1282
1283 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001284 public void oneTouchPlay(final IHdmiControlCallback callback) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001285 enforceAccessPermission();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001286 runOnServiceThread(new Runnable() {
1287 @Override
1288 public void run() {
1289 HdmiControlService.this.oneTouchPlay(callback);
1290 }
1291 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001292 }
1293
1294 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001295 public void queryDisplayStatus(final IHdmiControlCallback callback) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001296 enforceAccessPermission();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001297 runOnServiceThread(new Runnable() {
1298 @Override
1299 public void run() {
1300 HdmiControlService.this.queryDisplayStatus(callback);
1301 }
1302 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001303 }
1304
1305 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001306 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001307 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001308 HdmiControlService.this.addHotplugEventListener(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001309 }
1310
1311 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001312 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001313 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001314 HdmiControlService.this.removeHotplugEventListener(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001315 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001316
1317 @Override
1318 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
1319 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001320 HdmiControlService.this.addDeviceEventListener(listener);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001321 }
1322
1323 @Override
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001324 public List<HdmiPortInfo> getPortInfo() {
1325 enforceAccessPermission();
Jungshik Jang2738e2d2014-08-19 09:30:05 +09001326 return HdmiControlService.this.getPortInfo();
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001327 }
Jungshik Jangea67c182014-06-19 22:19:20 +09001328
1329 @Override
1330 public boolean canChangeSystemAudioMode() {
1331 enforceAccessPermission();
1332 HdmiCecLocalDeviceTv tv = tv();
1333 if (tv == null) {
1334 return false;
1335 }
Jungshik Jange9cf1582014-06-23 17:28:58 +09001336 return tv.hasSystemAudioDevice();
Jungshik Jangea67c182014-06-19 22:19:20 +09001337 }
1338
1339 @Override
1340 public boolean getSystemAudioMode() {
1341 enforceAccessPermission();
1342 HdmiCecLocalDeviceTv tv = tv();
1343 if (tv == null) {
1344 return false;
1345 }
Jungshik Jang377dcbd2014-07-15 15:49:02 +09001346 return tv.isSystemAudioActivated();
Jungshik Jangea67c182014-06-19 22:19:20 +09001347 }
1348
1349 @Override
1350 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
1351 enforceAccessPermission();
1352 runOnServiceThread(new Runnable() {
1353 @Override
1354 public void run() {
1355 HdmiCecLocalDeviceTv tv = tv();
1356 if (tv == null) {
1357 Slog.w(TAG, "Local tv device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001358 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jungshik Jangea67c182014-06-19 22:19:20 +09001359 return;
1360 }
1361 tv.changeSystemAudioMode(enabled, callback);
1362 }
1363 });
1364 }
1365
1366 @Override
1367 public void addSystemAudioModeChangeListener(
1368 final IHdmiSystemAudioModeChangeListener listener) {
1369 enforceAccessPermission();
1370 HdmiControlService.this.addSystemAudioModeChangeListner(listener);
1371 }
1372
1373 @Override
1374 public void removeSystemAudioModeChangeListener(
1375 final IHdmiSystemAudioModeChangeListener listener) {
1376 enforceAccessPermission();
1377 HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
1378 }
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001379
1380 @Override
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001381 public void setInputChangeListener(final IHdmiInputChangeListener listener) {
1382 enforceAccessPermission();
1383 HdmiControlService.this.setInputChangeListener(listener);
1384 }
1385
1386 @Override
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001387 public List<HdmiDeviceInfo> getInputDevices() {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001388 enforceAccessPermission();
1389 // No need to hold the lock for obtaining TV device as the local device instance
1390 // is preserved while the HDMI control is enabled.
1391 HdmiCecLocalDeviceTv tv = tv();
Jinsuk Kimed086452014-08-18 15:01:53 +09001392 synchronized (mLock) {
1393 List<HdmiDeviceInfo> cecDevices = (tv == null)
1394 ? Collections.<HdmiDeviceInfo>emptyList()
1395 : tv.getSafeExternalInputsLocked();
1396 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked());
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001397 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001398 }
1399
Jinsuk Kimbdf27fb2014-10-20 10:00:04 +09001400 // Returns all the CEC devices on the bus including system audio, switch,
1401 // even those of reserved type.
1402 @Override
1403 public List<HdmiDeviceInfo> getDeviceList() {
1404 enforceAccessPermission();
1405 HdmiCecLocalDeviceTv tv = tv();
1406 synchronized (mLock) {
1407 return (tv == null)
1408 ? Collections.<HdmiDeviceInfo>emptyList()
1409 : tv.getSafeCecDevicesLocked();
1410 }
1411 }
1412
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001413 @Override
Jungshik Jang41d97462014-06-30 22:26:29 +09001414 public void setSystemAudioVolume(final int oldIndex, final int newIndex,
1415 final int maxIndex) {
1416 enforceAccessPermission();
1417 runOnServiceThread(new Runnable() {
1418 @Override
1419 public void run() {
1420 HdmiCecLocalDeviceTv tv = tv();
1421 if (tv == null) {
1422 Slog.w(TAG, "Local tv device not available");
1423 return;
1424 }
1425 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
1426 }
1427 });
1428 }
1429
1430 @Override
1431 public void setSystemAudioMute(final boolean mute) {
1432 enforceAccessPermission();
1433 runOnServiceThread(new Runnable() {
1434 @Override
1435 public void run() {
1436 HdmiCecLocalDeviceTv tv = tv();
1437 if (tv == null) {
1438 Slog.w(TAG, "Local tv device not available");
1439 return;
1440 }
1441 tv.changeMute(mute);
1442 }
1443 });
1444 }
1445
1446 @Override
Jungshik Janga13da0d2014-06-30 16:26:06 +09001447 public void setArcMode(final boolean enabled) {
1448 enforceAccessPermission();
1449 runOnServiceThread(new Runnable() {
1450 @Override
1451 public void run() {
1452 HdmiCecLocalDeviceTv tv = tv();
1453 if (tv == null) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09001454 Slog.w(TAG, "Local tv device not available to change arc mode.");
Jungshik Janga13da0d2014-06-30 16:26:06 +09001455 return;
1456 }
1457 }
1458 });
1459 }
Jinsuk Kim160a6e52014-07-02 06:16:36 +09001460
1461 @Override
Jinsuk Kim4d43d932014-07-03 16:43:58 +09001462 public void setProhibitMode(final boolean enabled) {
1463 enforceAccessPermission();
1464 if (!isTvDevice()) {
1465 return;
1466 }
1467 HdmiControlService.this.setProhibitMode(enabled);
1468 }
Jinsuk Kim119160a2014-07-07 18:48:10 +09001469
1470 @Override
1471 public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
1472 final int deviceType) {
1473 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001474 HdmiControlService.this.addVendorCommandListener(listener, deviceType);
Jinsuk Kim119160a2014-07-07 18:48:10 +09001475 }
1476
1477 @Override
1478 public void sendVendorCommand(final int deviceType, final int targetAddress,
1479 final byte[] params, final boolean hasVendorId) {
1480 enforceAccessPermission();
1481 runOnServiceThread(new Runnable() {
1482 @Override
1483 public void run() {
1484 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1485 if (device == null) {
1486 Slog.w(TAG, "Local device not available");
1487 return;
1488 }
1489 if (hasVendorId) {
1490 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1491 device.getDeviceInfo().getLogicalAddress(), targetAddress,
1492 getVendorId(), params));
1493 } else {
1494 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1495 device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1496 }
1497 }
1498 });
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001499 }
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09001500
1501 @Override
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09001502 public void sendStandby(final int deviceType, final int deviceId) {
1503 enforceAccessPermission();
1504 runOnServiceThread(new Runnable() {
1505 @Override
1506 public void run() {
Jinsuk Kim61c94d12015-01-15 07:00:28 +09001507 HdmiMhlLocalDeviceStub mhlDevice = mMhlController.getLocalDeviceById(deviceId);
1508 if (mhlDevice != null) {
1509 mhlDevice.sendStandby();
1510 return;
1511 }
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09001512 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1513 if (device == null) {
1514 Slog.w(TAG, "Local device not available");
1515 return;
1516 }
1517 device.sendStandby(deviceId);
1518 }
1519 });
1520 }
1521
1522 @Override
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001523 public void setHdmiRecordListener(IHdmiRecordListener listener) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001524 enforceAccessPermission();
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001525 HdmiControlService.this.setHdmiRecordListener(listener);
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09001526 }
1527
1528 @Override
Jungshik Jangb6591b82014-07-23 16:10:23 +09001529 public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001530 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001531 runOnServiceThread(new Runnable() {
1532 @Override
1533 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001534 if (!isTvDeviceEnabled()) {
1535 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001536 return;
1537 }
1538 tv().startOneTouchRecord(recorderAddress, recordSource);
1539 }
1540 });
Jungshik Jangbffb0632014-07-22 16:56:52 +09001541 }
1542
1543 @Override
Jungshik Jangb6591b82014-07-23 16:10:23 +09001544 public void stopOneTouchRecord(final int recorderAddress) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001545 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001546 runOnServiceThread(new Runnable() {
1547 @Override
1548 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001549 if (!isTvDeviceEnabled()) {
1550 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001551 return;
1552 }
1553 tv().stopOneTouchRecord(recorderAddress);
1554 }
1555 });
1556 }
1557
1558 @Override
1559 public void startTimerRecording(final int recorderAddress, final int sourceType,
1560 final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001561 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001562 runOnServiceThread(new Runnable() {
1563 @Override
1564 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001565 if (!isTvDeviceEnabled()) {
1566 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001567 return;
1568 }
1569 tv().startTimerRecording(recorderAddress, sourceType, recordSource);
1570 }
1571 });
1572 }
1573
1574 @Override
1575 public void clearTimerRecording(final int recorderAddress, final int sourceType,
1576 final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001577 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001578 runOnServiceThread(new Runnable() {
1579 @Override
1580 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001581 if (!isTvDeviceEnabled()) {
1582 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001583 return;
1584 }
1585 tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
1586 }
1587 });
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09001588 }
Jungshik Jangf4249322014-08-21 14:17:05 +09001589
1590 @Override
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001591 public void sendMhlVendorCommand(final int portId, final int offset, final int length,
Jungshik Jangf4249322014-08-21 14:17:05 +09001592 final byte[] data) {
1593 enforceAccessPermission();
1594 runOnServiceThread(new Runnable() {
1595 @Override
1596 public void run() {
Jungshik Jangf4249322014-08-21 14:17:05 +09001597 if (!isControlEnabled()) {
1598 Slog.w(TAG, "Hdmi control is disabled.");
1599 return ;
1600 }
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001601 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jangf4249322014-08-21 14:17:05 +09001602 if (device == null) {
1603 Slog.w(TAG, "Invalid port id:" + portId);
1604 return;
1605 }
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001606 mMhlController.sendVendorCommand(portId, offset, length, data);
Jungshik Jangf4249322014-08-21 14:17:05 +09001607 }
1608 });
1609 }
1610
1611 @Override
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001612 public void addHdmiMhlVendorCommandListener(
1613 IHdmiMhlVendorCommandListener listener) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001614 enforceAccessPermission();
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001615 HdmiControlService.this.addHdmiMhlVendorCommandListener(listener);
Jungshik Jangf4249322014-08-21 14:17:05 +09001616 }
Terry Heo959d2db2014-08-28 16:45:41 +09001617
1618 @Override
1619 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1620 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
1621 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
1622
1623 pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
1624 pw.println("mProhibitMode: " + mProhibitMode);
1625 if (mCecController != null) {
1626 pw.println("mCecController: ");
1627 pw.increaseIndent();
1628 mCecController.dump(pw);
1629 pw.decreaseIndent();
1630 }
Jinsuk Kim61c94d12015-01-15 07:00:28 +09001631
1632 pw.println("mMhlController: ");
1633 pw.increaseIndent();
1634 mMhlController.dump(pw);
1635 pw.decreaseIndent();
1636
Terry Heo959d2db2014-08-28 16:45:41 +09001637 pw.println("mPortInfo: ");
1638 pw.increaseIndent();
1639 for (HdmiPortInfo hdmiPortInfo : mPortInfo) {
1640 pw.println("- " + hdmiPortInfo);
1641 }
1642 pw.decreaseIndent();
1643 pw.println("mPowerStatus: " + mPowerStatus);
1644 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001645 }
1646
Jungshik Janga5b74142014-06-23 18:03:10 +09001647 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +09001648 private void oneTouchPlay(final IHdmiControlCallback callback) {
1649 assertRunOnServiceThread();
1650 HdmiCecLocalDevicePlayback source = playback();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001651 if (source == null) {
1652 Slog.w(TAG, "Local playback device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001653 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001654 return;
1655 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09001656 source.oneTouchPlay(callback);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001657 }
1658
Jungshik Janga5b74142014-06-23 18:03:10 +09001659 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +09001660 private void queryDisplayStatus(final IHdmiControlCallback callback) {
1661 assertRunOnServiceThread();
1662 HdmiCecLocalDevicePlayback source = playback();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001663 if (source == null) {
1664 Slog.w(TAG, "Local playback device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001665 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001666 return;
1667 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09001668 source.queryDisplayStatus(callback);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001669 }
1670
Jinsuk Kim3cd30512014-12-04 11:05:09 +09001671 private void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
1672 final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001673 try {
1674 listener.asBinder().linkToDeath(record, 0);
1675 } catch (RemoteException e) {
1676 Slog.w(TAG, "Listener already died");
1677 return;
1678 }
1679 synchronized (mLock) {
1680 mHotplugEventListenerRecords.add(record);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001681 }
Jinsuk Kim3cd30512014-12-04 11:05:09 +09001682
1683 // Inform the listener of the initial state of each HDMI port by generating
1684 // hotplug events.
1685 runOnServiceThread(new Runnable() {
1686 @Override
1687 public void run() {
1688 synchronized (mLock) {
1689 if (!mHotplugEventListenerRecords.contains(record)) return;
1690 }
1691 for (HdmiPortInfo port : mPortInfo) {
1692 HdmiHotplugEvent event = new HdmiHotplugEvent(port.getId(),
1693 mCecController.isConnected(port.getId()));
1694 synchronized (mLock) {
1695 invokeHotplugEventListenerLocked(listener, event);
1696 }
1697 }
1698 }
1699 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001700 }
1701
1702 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
1703 synchronized (mLock) {
1704 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1705 if (record.mListener.asBinder() == listener.asBinder()) {
1706 listener.asBinder().unlinkToDeath(record, 0);
1707 mHotplugEventListenerRecords.remove(record);
1708 break;
1709 }
1710 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001711 }
1712 }
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001713
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001714 private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001715 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
1716 try {
1717 listener.asBinder().linkToDeath(record, 0);
1718 } catch (RemoteException e) {
1719 Slog.w(TAG, "Listener already died");
1720 return;
1721 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001722 synchronized (mLock) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001723 mDeviceEventListenerRecords.add(record);
1724 }
1725 }
1726
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001727 void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001728 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001729 for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001730 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09001731 record.mListener.onStatusChanged(device, status);
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001732 } catch (RemoteException e) {
1733 Slog.e(TAG, "Failed to report device event:" + e);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001734 }
1735 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001736 }
1737 }
1738
Jungshik Jangea67c182014-06-19 22:19:20 +09001739 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
1740 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
1741 listener);
1742 try {
1743 listener.asBinder().linkToDeath(record, 0);
1744 } catch (RemoteException e) {
1745 Slog.w(TAG, "Listener already died");
1746 return;
1747 }
1748 synchronized (mLock) {
Jungshik Jangea67c182014-06-19 22:19:20 +09001749 mSystemAudioModeChangeListenerRecords.add(record);
1750 }
1751 }
1752
1753 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
1754 synchronized (mLock) {
1755 for (SystemAudioModeChangeListenerRecord record :
1756 mSystemAudioModeChangeListenerRecords) {
1757 if (record.mListener.asBinder() == listener) {
1758 listener.asBinder().unlinkToDeath(record, 0);
1759 mSystemAudioModeChangeListenerRecords.remove(record);
1760 break;
1761 }
1762 }
Jungshik Jangea67c182014-06-19 22:19:20 +09001763 }
1764 }
1765
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001766 private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
Jungshik Jangf4249322014-08-21 14:17:05 +09001767 private final IHdmiInputChangeListener mListener;
1768
1769 public InputChangeListenerRecord(IHdmiInputChangeListener listener) {
1770 mListener = listener;
1771 }
1772
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001773 @Override
1774 public void binderDied() {
1775 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001776 mInputChangeListenerRecord = null;
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001777 }
1778 }
1779 }
1780
1781 private void setInputChangeListener(IHdmiInputChangeListener listener) {
1782 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001783 mInputChangeListenerRecord = new InputChangeListenerRecord(listener);
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001784 try {
1785 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
1786 } catch (RemoteException e) {
1787 Slog.w(TAG, "Listener already died");
1788 return;
1789 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001790 }
1791 }
1792
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001793 void invokeInputChangeListener(HdmiDeviceInfo info) {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001794 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001795 if (mInputChangeListenerRecord != null) {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001796 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09001797 mInputChangeListenerRecord.mListener.onChanged(info);
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001798 } catch (RemoteException e) {
1799 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
1800 }
1801 }
1802 }
1803 }
1804
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001805 private void setHdmiRecordListener(IHdmiRecordListener listener) {
Jungshik Jangb6591b82014-07-23 16:10:23 +09001806 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001807 mRecordListenerRecord = new HdmiRecordListenerRecord(listener);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001808 try {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001809 listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001810 } catch (RemoteException e) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001811 Slog.w(TAG, "Listener already died.", e);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001812 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09001813 }
1814 }
1815
1816 byte[] invokeRecordRequestListener(int recorderAddress) {
1817 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001818 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001819 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09001820 return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001821 } catch (RemoteException e) {
1822 Slog.w(TAG, "Failed to start record.", e);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001823 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09001824 }
1825 return EmptyArray.BYTE;
1826 }
1827 }
1828
Jungshik Jang326aef02014-11-05 12:50:35 +09001829 void invokeOneTouchRecordResult(int recorderAddress, int result) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001830 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001831 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001832 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09001833 mRecordListenerRecord.mListener.onOneTouchRecordResult(recorderAddress, result);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001834 } catch (RemoteException e) {
1835 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
1836 }
1837 }
1838 }
1839 }
1840
Jungshik Jang326aef02014-11-05 12:50:35 +09001841 void invokeTimerRecordingResult(int recorderAddress, int result) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001842 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001843 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001844 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09001845 mRecordListenerRecord.mListener.onTimerRecordingResult(recorderAddress, result);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001846 } catch (RemoteException e) {
Jungshik Jange5a93372014-07-25 13:41:14 +09001847 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
1848 }
1849 }
1850 }
1851 }
1852
Jungshik Jang326aef02014-11-05 12:50:35 +09001853 void invokeClearTimerRecordingResult(int recorderAddress, int result) {
Jungshik Jange5a93372014-07-25 13:41:14 +09001854 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001855 if (mRecordListenerRecord != null) {
Jungshik Jange5a93372014-07-25 13:41:14 +09001856 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09001857 mRecordListenerRecord.mListener.onClearTimerRecordingResult(recorderAddress,
1858 result);
Jungshik Jange5a93372014-07-25 13:41:14 +09001859 } catch (RemoteException e) {
1860 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001861 }
1862 }
1863 }
1864 }
1865
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001866 private void invokeCallback(IHdmiControlCallback callback, int result) {
1867 try {
1868 callback.onComplete(result);
1869 } catch (RemoteException e) {
1870 Slog.e(TAG, "Invoking callback failed:" + e);
1871 }
1872 }
Yuncheol Heo63a2e062014-05-27 23:06:01 +09001873
Jungshik Jangf4249322014-08-21 14:17:05 +09001874 private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener,
Jungshik Jangea67c182014-06-19 22:19:20 +09001875 boolean enabled) {
1876 try {
1877 listener.onStatusChanged(enabled);
1878 } catch (RemoteException e) {
1879 Slog.e(TAG, "Invoking callback failed:" + e);
1880 }
1881 }
1882
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001883 private void announceHotplugEvent(int portId, boolean connected) {
1884 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
Jungshik Jang60cffce2014-06-12 18:03:04 +09001885 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001886 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1887 invokeHotplugEventListenerLocked(record.mListener, event);
Jungshik Jang60cffce2014-06-12 18:03:04 +09001888 }
1889 }
1890 }
1891
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001892 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
Jungshik Jang60cffce2014-06-12 18:03:04 +09001893 HdmiHotplugEvent event) {
1894 try {
1895 listener.onReceived(event);
1896 } catch (RemoteException e) {
1897 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
1898 }
Jungshik Jange81e1082014-06-05 15:37:59 +09001899 }
1900
Jungshik Jang79c58a42014-06-16 16:45:36 +09001901 private HdmiCecLocalDeviceTv tv() {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001902 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
Jungshik Jang79c58a42014-06-16 16:45:36 +09001903 }
1904
Yuncheol Heoe946ed82014-07-25 14:05:19 +09001905 boolean isTvDevice() {
Yuncheol Heob8d62e72014-09-22 19:53:41 +09001906 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
Yuncheol Heoe946ed82014-07-25 14:05:19 +09001907 }
1908
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001909 boolean isTvDeviceEnabled() {
1910 return isTvDevice() && tv() != null;
1911 }
1912
Jungshik Jang79c58a42014-06-16 16:45:36 +09001913 private HdmiCecLocalDevicePlayback playback() {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001914 return (HdmiCecLocalDevicePlayback)
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001915 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
Jungshik Jang60cffce2014-06-12 18:03:04 +09001916 }
Jungshik Janga858d222014-06-23 17:17:47 +09001917
1918 AudioManager getAudioManager() {
1919 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
1920 }
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001921
1922 boolean isControlEnabled() {
1923 synchronized (mLock) {
1924 return mHdmiControlEnabled;
1925 }
1926 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09001927
Jungshik Jangf67113f2014-08-22 16:27:19 +09001928 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09001929 int getPowerStatus() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09001930 assertRunOnServiceThread();
Yuncheol Heo38db6292014-07-01 14:15:14 +09001931 return mPowerStatus;
1932 }
1933
Jungshik Jangf67113f2014-08-22 16:27:19 +09001934 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09001935 boolean isPowerOnOrTransient() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09001936 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001937 return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
1938 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Yuncheol Heo38db6292014-07-01 14:15:14 +09001939 }
1940
Jungshik Jangf67113f2014-08-22 16:27:19 +09001941 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09001942 boolean isPowerStandbyOrTransient() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09001943 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001944 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
1945 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09001946 }
1947
Jungshik Jangf67113f2014-08-22 16:27:19 +09001948 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09001949 boolean isPowerStandby() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09001950 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001951 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09001952 }
1953
1954 @ServiceThreadOnly
1955 void wakeUp() {
1956 assertRunOnServiceThread();
Yuncheol Heofc44e4e2014-08-04 19:41:09 +09001957 mWakeUpMessageReceived = true;
Jinsuk Kime26d8332015-01-09 08:55:41 +09001958 mPowerManager.wakeUp(SystemClock.uptimeMillis());
Yuncheol Heo38db6292014-07-01 14:15:14 +09001959 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
1960 // the intent, the sequence will continue at onWakeUp().
1961 }
1962
1963 @ServiceThreadOnly
1964 void standby() {
1965 assertRunOnServiceThread();
1966 mStandbyMessageReceived = true;
Jinsuk Kime26d8332015-01-09 08:55:41 +09001967 mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
Yuncheol Heo38db6292014-07-01 14:15:14 +09001968 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
1969 // the intent, the sequence will continue at onStandby().
1970 }
1971
1972 @ServiceThreadOnly
1973 private void onWakeUp() {
1974 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001975 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Yuncheol Heo38db6292014-07-01 14:15:14 +09001976 if (mCecController != null) {
Jungshik Janga9f10622014-07-11 15:36:39 +09001977 if (mHdmiControlEnabled) {
Yuncheol Heofc44e4e2014-08-04 19:41:09 +09001978 int startReason = INITIATED_BY_SCREEN_ON;
1979 if (mWakeUpMessageReceived) {
1980 startReason = INITIATED_BY_WAKE_UP_MESSAGE;
1981 }
1982 initializeCec(startReason);
Jungshik Janga9f10622014-07-11 15:36:39 +09001983 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09001984 } else {
1985 Slog.i(TAG, "Device does not support HDMI-CEC.");
1986 }
1987 // TODO: Initialize MHL local devices.
1988 }
1989
1990 @ServiceThreadOnly
1991 private void onStandby() {
1992 assertRunOnServiceThread();
Jinsuk Kime26d8332015-01-09 08:55:41 +09001993 if (!canGoToStandby()) return;
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001994 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
Yuncheol Heo0608b932014-10-13 16:39:18 +09001995 invokeVendorCommandListenersOnControlStateChanged(false,
1996 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);
Jungshik Jang4fc1d102014-07-09 19:24:50 +09001997
1998 final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
1999 disableDevices(new PendingActionClearedCallback() {
2000 @Override
2001 public void onCleared(HdmiCecLocalDevice device) {
2002 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
2003 devices.remove(device);
2004 if (devices.isEmpty()) {
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002005 onStandbyCompleted();
Yuncheol Heo4b542712014-07-30 20:31:06 +09002006 // We will not clear local devices here, since some OEM/SOC will keep passing
2007 // the received packets until the application processor enters to the sleep
2008 // actually.
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002009 }
2010 }
2011 });
2012 }
2013
Jinsuk Kime26d8332015-01-09 08:55:41 +09002014 private boolean canGoToStandby() {
2015 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2016 if (!device.canGoToStandby()) return false;
2017 }
2018 return true;
2019 }
2020
Terry Heo1ca0a432014-08-18 10:30:32 +09002021 @ServiceThreadOnly
2022 private void onLanguageChanged(String language) {
2023 assertRunOnServiceThread();
2024 mLanguage = language;
2025
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002026 if (isTvDeviceEnabled()) {
Terry Heo1ca0a432014-08-18 10:30:32 +09002027 tv().broadcastMenuLanguage(language);
Jinsuk Kim5b8cb002015-01-19 07:30:12 +09002028 mCecController.setOption(OPTION_CEC_SET_LANGUAGE, HdmiUtils.languageToInt(language));
Terry Heo1ca0a432014-08-18 10:30:32 +09002029 }
2030 }
2031
Jungshik Jangf67113f2014-08-22 16:27:19 +09002032 @ServiceThreadOnly
2033 String getLanguage() {
2034 assertRunOnServiceThread();
2035 return mLanguage;
2036 }
2037
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002038 private void disableDevices(PendingActionClearedCallback callback) {
Jungshik Jang350e68d2014-08-19 18:56:21 +09002039 if (mCecController != null) {
2040 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2041 device.disableDevice(mStandbyMessageReceived, callback);
2042 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002043 }
Jungshik Jang350e68d2014-08-19 18:56:21 +09002044
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09002045 mMhlController.clearAllLocalDevices();
Yuncheol Heo38db6292014-07-01 14:15:14 +09002046 }
2047
Yuncheol Heo38db6292014-07-01 14:15:14 +09002048 @ServiceThreadOnly
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002049 private void clearLocalDevices() {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002050 assertRunOnServiceThread();
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002051 if (mCecController == null) {
2052 return;
2053 }
2054 mCecController.clearLogicalAddress();
2055 mCecController.clearLocalDevices();
2056 }
2057
2058 @ServiceThreadOnly
2059 private void onStandbyCompleted() {
2060 assertRunOnServiceThread();
2061 Slog.v(TAG, "onStandbyCompleted");
2062
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002063 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002064 return;
2065 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002066 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002067 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002068 device.onStandby(mStandbyMessageReceived);
Yuncheol Heo38db6292014-07-01 14:15:14 +09002069 }
2070 mStandbyMessageReceived = false;
Jinsuk Kim964c00d2015-01-16 15:20:17 +09002071 mAddressAllocated = false;
Jinsuk Kim50084862014-08-07 13:11:40 +09002072 mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, DISABLED);
Jinsuk Kim5b8cb002015-01-19 07:30:12 +09002073 mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
Yuncheol Heo38db6292014-07-01 14:15:14 +09002074 }
Jinsuk Kim4d43d932014-07-03 16:43:58 +09002075
Jinsuk Kim119160a2014-07-07 18:48:10 +09002076 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
2077 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
2078 try {
2079 listener.asBinder().linkToDeath(record, 0);
2080 } catch (RemoteException e) {
2081 Slog.w(TAG, "Listener already died");
2082 return;
2083 }
2084 synchronized (mLock) {
2085 mVendorCommandListenerRecords.add(record);
2086 }
2087 }
2088
Yuncheol Heo0608b932014-10-13 16:39:18 +09002089 boolean invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress,
2090 byte[] params, boolean hasVendorId) {
Jinsuk Kim119160a2014-07-07 18:48:10 +09002091 synchronized (mLock) {
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09002092 if (mVendorCommandListenerRecords.isEmpty()) {
2093 return false;
2094 }
Jinsuk Kim119160a2014-07-07 18:48:10 +09002095 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2096 if (record.mDeviceType != deviceType) {
2097 continue;
2098 }
2099 try {
Yuncheol Heo0608b932014-10-13 16:39:18 +09002100 record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId);
Jinsuk Kim119160a2014-07-07 18:48:10 +09002101 } catch (RemoteException e) {
2102 Slog.e(TAG, "Failed to notify vendor command reception", e);
2103 }
2104 }
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09002105 return true;
Jinsuk Kim119160a2014-07-07 18:48:10 +09002106 }
2107 }
2108
Yuncheol Heo0608b932014-10-13 16:39:18 +09002109 boolean invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason) {
2110 synchronized (mLock) {
2111 if (mVendorCommandListenerRecords.isEmpty()) {
2112 return false;
2113 }
2114 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2115 try {
2116 record.mListener.onControlStateChanged(enabled, reason);
2117 } catch (RemoteException e) {
2118 Slog.e(TAG, "Failed to notify control-state-changed to vendor handler", e);
2119 }
2120 }
2121 return true;
2122 }
2123 }
2124
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002125 private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {
2126 HdmiMhlVendorCommandListenerRecord record =
2127 new HdmiMhlVendorCommandListenerRecord(listener);
Jungshik Jangf4249322014-08-21 14:17:05 +09002128 try {
2129 listener.asBinder().linkToDeath(record, 0);
2130 } catch (RemoteException e) {
2131 Slog.w(TAG, "Listener already died.");
2132 return;
2133 }
2134
2135 synchronized (mLock) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002136 mMhlVendorCommandListenerRecords.add(record);
Jungshik Jangf4249322014-08-21 14:17:05 +09002137 }
2138 }
2139
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002140 void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002141 synchronized (mLock) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002142 for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002143 try {
2144 record.mListener.onReceived(portId, offest, length, data);
2145 } catch (RemoteException e) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002146 Slog.e(TAG, "Failed to notify MHL vendor command", e);
Jungshik Jangf4249322014-08-21 14:17:05 +09002147 }
2148 }
2149 }
2150 }
2151
Jinsuk Kim4d43d932014-07-03 16:43:58 +09002152 boolean isProhibitMode() {
2153 synchronized (mLock) {
2154 return mProhibitMode;
2155 }
2156 }
2157
2158 void setProhibitMode(boolean enabled) {
2159 synchronized (mLock) {
2160 mProhibitMode = enabled;
2161 }
2162 }
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002163
2164 @ServiceThreadOnly
Jungshik Jang350e68d2014-08-19 18:56:21 +09002165 void setCecOption(int key, int value) {
Jinsuk Kim50084862014-08-07 13:11:40 +09002166 assertRunOnServiceThread();
2167 mCecController.setOption(key, value);
2168 }
2169
2170 @ServiceThreadOnly
2171 void setControlEnabled(boolean enabled) {
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002172 assertRunOnServiceThread();
2173
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002174 synchronized (mLock) {
2175 mHdmiControlEnabled = enabled;
2176 }
2177
2178 if (enabled) {
Yuncheol Heof1702482014-11-27 19:52:01 +09002179 enableHdmiControlService();
2180 return;
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002181 }
Yuncheol Heof1702482014-11-27 19:52:01 +09002182 // Call the vendor handler before the service is disabled.
2183 invokeVendorCommandListenersOnControlStateChanged(false,
2184 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING);
2185 // Post the remained tasks in the service thread again to give the vendor-issued-tasks
2186 // a chance to run.
2187 runOnServiceThread(new Runnable() {
2188 @Override
2189 public void run() {
2190 disableHdmiControlService();
2191 }
2192 });
2193 return;
2194 }
2195
2196 @ServiceThreadOnly
2197 private void enableHdmiControlService() {
2198 mCecController.setOption(OPTION_CEC_ENABLE, ENABLED);
2199 mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED);
2200
2201 initializeCec(INITIATED_BY_ENABLE_CEC);
2202 }
2203
2204 @ServiceThreadOnly
2205 private void disableHdmiControlService() {
2206 disableDevices(new PendingActionClearedCallback() {
2207 @Override
2208 public void onCleared(HdmiCecLocalDevice device) {
2209 assertRunOnServiceThread();
2210 mCecController.flush(new Runnable() {
2211 @Override
2212 public void run() {
2213 mCecController.setOption(OPTION_CEC_ENABLE, DISABLED);
2214 mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED);
2215 clearLocalDevices();
2216 }
2217 });
2218 }
2219 });
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002220 }
Jungshik Jang867b4e02014-08-12 13:41:30 +09002221
2222 @ServiceThreadOnly
2223 void setActivePortId(int portId) {
2224 assertRunOnServiceThread();
2225 mActivePortId = portId;
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002226
2227 // Resets last input for MHL, which stays valid only after the MHL device was selected,
2228 // and no further switching is done.
2229 setLastInputForMhl(Constants.INVALID_PORT_ID);
Jungshik Jang867b4e02014-08-12 13:41:30 +09002230 }
Yuncheol Heo08a1be82014-08-12 20:58:41 +09002231
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002232 @ServiceThreadOnly
2233 void setLastInputForMhl(int portId) {
2234 assertRunOnServiceThread();
2235 mLastInputMhl = portId;
2236 }
2237
2238 @ServiceThreadOnly
2239 int getLastInputForMhl() {
2240 assertRunOnServiceThread();
2241 return mLastInputMhl;
2242 }
2243
2244 /**
2245 * Performs input change, routing control for MHL device.
2246 *
2247 * @param portId MHL port, or the last port to go back to if {@code contentOn} is false
2248 * @param contentOn {@code true} if RAP data is content on; otherwise false
2249 */
2250 @ServiceThreadOnly
2251 void changeInputForMhl(int portId, boolean contentOn) {
2252 assertRunOnServiceThread();
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002253 if (tv() == null) return;
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002254 final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID;
Jinsuk Kimcb8661c2015-01-19 12:39:06 +09002255 if (portId != Constants.INVALID_PORT_ID) {
2256 tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() {
2257 @Override
2258 public void onComplete(int result) throws RemoteException {
2259 // Keep the last input to switch back later when RAP[ContentOff] is received.
2260 // This effectively sets the port to invalid one if the switching is for
2261 // RAP[ContentOff].
2262 setLastInputForMhl(lastInput);
2263 }
2264 });
2265 }
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002266 // MHL device is always directly connected to the port. Update the active port ID to avoid
2267 // unnecessary post-routing control task.
2268 tv().setActivePortId(portId);
2269
2270 // The port is either the MHL-enabled port where the mobile device is connected, or
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09002271 // the last port to go back to when turnoff command is received. Note that the last port
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002272 // may not be the MHL-enabled one. In this case the device info to be passed to
2273 // input change listener should be the one describing the corresponding HDMI port.
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09002274 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jinsuk Kimcb8661c2015-01-19 12:39:06 +09002275 HdmiDeviceInfo info = (device != null) ? device.getInfo()
2276 : mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002277 invokeInputChangeListener(info);
2278 }
2279
2280 void setMhlInputChangeEnabled(boolean enabled) {
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09002281 mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
Yuncheol Heo08a1be82014-08-12 20:58:41 +09002282
2283 synchronized (mLock) {
2284 mMhlInputChangeEnabled = enabled;
2285 }
2286 }
2287
2288 boolean isMhlInputChangeEnabled() {
2289 synchronized (mLock) {
2290 return mMhlInputChangeEnabled;
2291 }
2292 }
Jungshik Jang339227d2014-08-25 15:37:20 +09002293
2294 @ServiceThreadOnly
2295 void displayOsd(int messageId) {
2296 assertRunOnServiceThread();
2297 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2298 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2299 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2300 HdmiControlService.PERMISSION);
2301 }
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09002302
2303 @ServiceThreadOnly
2304 void displayOsd(int messageId, int extra) {
2305 assertRunOnServiceThread();
2306 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2307 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
Yuncheol Heo2b0da5c2014-10-22 14:32:27 +09002308 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra);
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09002309 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2310 HdmiControlService.PERMISSION);
2311 }
Jungshik Jang0792d372014-04-23 17:57:26 +09002312}