blob: 5dc9d02a71c5d2697769b66a3def992f61317c8f [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;
Jinsuk Kimf98b9e82015-10-05 14:24:48 +090080import com.android.server.hdmi.SelectRequestBuffer.DeviceSelectRequest;
81import com.android.server.hdmi.SelectRequestBuffer.PortSelectRequest;
Jungshik Jang0792d372014-04-23 17:57:26 +090082
Jungshik Jangb6591b82014-07-23 16:10:23 +090083import libcore.util.EmptyArray;
84
Terry Heo959d2db2014-08-28 16:45:41 +090085import java.io.FileDescriptor;
86import java.io.PrintWriter;
Jinsuk Kim78d695d2014-05-13 16:36:15 +090087import java.util.ArrayList;
Jinsuk Kimf4eb72d2014-07-25 13:02:51 +090088import java.util.Arrays;
Jinsuk Kim0340bbc2014-06-05 11:07:47 +090089import java.util.Collections;
Jungshik Jang02bb4262014-05-23 16:48:31 +090090import java.util.List;
Terry Heo1ca0a432014-08-18 10:30:32 +090091import java.util.Locale;
Jungshik Janga1fa91f2014-05-08 20:56:41 +090092
Jungshik Jang0792d372014-04-23 17:57:26 +090093/**
94 * Provides a service for sending and processing HDMI control messages,
95 * HDMI-CEC and MHL control command, and providing the information on both standard.
96 */
97public final class HdmiControlService extends SystemService {
98 private static final String TAG = "HdmiControlService";
Jinsuk Kim5fe3a6c2014-10-29 07:02:06 +090099 private final Locale HONG_KONG = new Locale("zh", "HK");
100 private final Locale MACAU = new Locale("zh", "MO");
Jungshik Jang0792d372014-04-23 17:57:26 +0900101
Jinsuk Kimc7eba0f2014-07-07 14:18:02 +0900102 static final String PERMISSION = "android.permission.HDMI_CEC";
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900103
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900104 // The reason code to initiate intializeCec().
105 static final int INITIATED_BY_ENABLE_CEC = 0;
106 static final int INITIATED_BY_BOOT_UP = 1;
107 static final int INITIATED_BY_SCREEN_ON = 2;
108 static final int INITIATED_BY_WAKE_UP_MESSAGE = 3;
Yuncheol Heob5021862014-09-02 10:36:04 +0900109 static final int INITIATED_BY_HOTPLUG = 4;
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900110
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900111 // The reason code representing the intent action that drives the standby
112 // procedure. The procedure starts either by Intent.ACTION_SCREEN_OFF or
113 // Intent.ACTION_SHUTDOWN.
114 static final int STANDBY_SCREEN_OFF = 0;
115 static final int STANDBY_SHUTDOWN = 1;
116
Jungshik Jangd643f762014-05-22 19:28:09 +0900117 /**
118 * Interface to report send result.
119 */
120 interface SendMessageCallback {
121 /**
122 * Called when {@link HdmiControlService#sendCecCommand} is completed.
123 *
Yuncheol Heoece603b2014-05-23 20:10:19 +0900124 * @param error result of send request.
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900125 * <ul>
126 * <li>{@link Constants#SEND_RESULT_SUCCESS}
127 * <li>{@link Constants#SEND_RESULT_NAK}
128 * <li>{@link Constants#SEND_RESULT_FAILURE}
129 * </ul>
Jungshik Jangd643f762014-05-22 19:28:09 +0900130 */
Jungshik Jangd643f762014-05-22 19:28:09 +0900131 void onSendCompleted(int error);
132 }
133
Jungshik Jang02bb4262014-05-23 16:48:31 +0900134 /**
135 * Interface to get a list of available logical devices.
136 */
137 interface DevicePollingCallback {
138 /**
139 * Called when device polling is finished.
140 *
141 * @param ackedAddress a list of logical addresses of available devices
142 */
143 void onPollingFinished(List<Integer> ackedAddress);
144 }
145
Terry Heo1ca0a432014-08-18 10:30:32 +0900146 private class HdmiControlBroadcastReceiver extends BroadcastReceiver {
Jungshik Jangf67113f2014-08-22 16:27:19 +0900147 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +0900148 @Override
149 public void onReceive(Context context, Intent intent) {
Jungshik Jangf67113f2014-08-22 16:27:19 +0900150 assertRunOnServiceThread();
Yuncheol Heo38db6292014-07-01 14:15:14 +0900151 switch (intent.getAction()) {
152 case Intent.ACTION_SCREEN_OFF:
153 if (isPowerOnOrTransient()) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900154 onStandby(STANDBY_SCREEN_OFF);
Yuncheol Heo38db6292014-07-01 14:15:14 +0900155 }
156 break;
157 case Intent.ACTION_SCREEN_ON:
158 if (isPowerStandbyOrTransient()) {
159 onWakeUp();
160 }
161 break;
Terry Heo1ca0a432014-08-18 10:30:32 +0900162 case Intent.ACTION_CONFIGURATION_CHANGED:
Jinsuk Kim5fe3a6c2014-10-29 07:02:06 +0900163 String language = getMenuLanguage();
Terry Heo1ca0a432014-08-18 10:30:32 +0900164 if (!mLanguage.equals(language)) {
165 onLanguageChanged(language);
166 }
167 break;
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900168 case Intent.ACTION_SHUTDOWN:
169 if (isPowerOnOrTransient()) {
170 onStandby(STANDBY_SHUTDOWN);
171 }
172 break;
Yuncheol Heo38db6292014-07-01 14:15:14 +0900173 }
174 }
Jinsuk Kim5fe3a6c2014-10-29 07:02:06 +0900175
176 private String getMenuLanguage() {
177 Locale locale = Locale.getDefault();
178 if (locale.equals(Locale.TAIWAN) || locale.equals(HONG_KONG) || locale.equals(MACAU)) {
179 // Android always returns "zho" for all Chinese variants.
180 // Use "bibliographic" code defined in CEC639-2 for traditional
181 // Chinese used in Taiwan/Hong Kong/Macau.
182 return "chi";
183 } else {
184 return locale.getISO3Language();
185 }
186 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900187 }
188
Jungshik Jang0792d372014-04-23 17:57:26 +0900189 // A thread to handle synchronous IO of CEC and MHL control service.
190 // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
191 // and sparse call it shares a thread to handle IO operations.
192 private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
193
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900194 // Used to synchronize the access to the service.
195 private final Object mLock = new Object();
196
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900197 // Type of logical devices hosted in the system. Stored in the unmodifiable list.
198 private final List<Integer> mLocalDevices;
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900199
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900200 // List of records for hotplug event listener to handle the the caller killed in action.
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900201 @GuardedBy("mLock")
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900202 private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
203 new ArrayList<>();
204
Jungshik Jangf4249322014-08-21 14:17:05 +0900205 // List of records for device event listener to handle the caller killed in action.
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900206 @GuardedBy("mLock")
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +0900207 private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
208 new ArrayList<>();
209
Jungshik Jangf4249322014-08-21 14:17:05 +0900210 // List of records for vendor command listener to handle the caller killed in action.
Jinsuk Kim119160a2014-07-07 18:48:10 +0900211 @GuardedBy("mLock")
212 private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords =
213 new ArrayList<>();
214
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +0900215 @GuardedBy("mLock")
216 private InputChangeListenerRecord mInputChangeListenerRecord;
217
Jungshik Jangb6591b82014-07-23 16:10:23 +0900218 @GuardedBy("mLock")
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900219 private HdmiRecordListenerRecord mRecordListenerRecord;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900220
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900221 // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
222 // handling will be disabled and no request will be handled.
223 @GuardedBy("mLock")
224 private boolean mHdmiControlEnabled;
225
Jinsuk Kim4d43d932014-07-03 16:43:58 +0900226 // Set to true while the service is in normal mode. While set to false, no input change is
227 // allowed. Used for situations where input change can confuse users such as channel auto-scan,
228 // system upgrade, etc., a.k.a. "prohibit mode".
229 @GuardedBy("mLock")
230 private boolean mProhibitMode;
231
Jungshik Jangea67c182014-06-19 22:19:20 +0900232 // List of records for system audio mode change to handle the the caller killed in action.
233 private final ArrayList<SystemAudioModeChangeListenerRecord>
234 mSystemAudioModeChangeListenerRecords = new ArrayList<>();
235
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900236 // Handler used to run a task in service thread.
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900237 private final Handler mHandler = new Handler();
238
Jinsuk Kim50084862014-08-07 13:11:40 +0900239 private final SettingsObserver mSettingsObserver;
240
Jungshik Jangf4249322014-08-21 14:17:05 +0900241 private final HdmiControlBroadcastReceiver
242 mHdmiControlBroadcastReceiver = new HdmiControlBroadcastReceiver();
243
Jungshik Jang0792d372014-04-23 17:57:26 +0900244 @Nullable
245 private HdmiCecController mCecController;
246
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900247 // HDMI port information. Stored in the unmodifiable list to keep the static information
248 // from being modified.
249 private List<HdmiPortInfo> mPortInfo;
250
Jinsuk Kim2b152012014-07-25 08:22:26 +0900251 // Map from path(physical address) to port ID.
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900252 private UnmodifiableSparseIntArray mPortIdMap;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900253
254 // Map from port ID to HdmiPortInfo.
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900255 private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900256
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900257 // Map from port ID to HdmiDeviceInfo.
258 private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap;
259
Yuncheol Heo75a77e72014-07-09 18:27:53 +0900260 private HdmiCecMessageValidator mMessageValidator;
261
Yuncheol Heo38db6292014-07-01 14:15:14 +0900262 @ServiceThreadOnly
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900263 private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +0900264
265 @ServiceThreadOnly
Terry Heo1ca0a432014-08-18 10:30:32 +0900266 private String mLanguage = Locale.getDefault().getISO3Language();
267
268 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +0900269 private boolean mStandbyMessageReceived = false;
270
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900271 @ServiceThreadOnly
272 private boolean mWakeUpMessageReceived = false;
273
Jungshik Jang867b4e02014-08-12 13:41:30 +0900274 @ServiceThreadOnly
275 private int mActivePortId = Constants.INVALID_PORT_ID;
276
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900277 // Set to true while the input change by MHL is allowed.
278 @GuardedBy("mLock")
279 private boolean mMhlInputChangeEnabled;
280
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +0900281 // List of records for MHL Vendor command listener to handle the caller killed in action.
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900282 @GuardedBy("mLock")
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +0900283 private final ArrayList<HdmiMhlVendorCommandListenerRecord>
284 mMhlVendorCommandListenerRecords = new ArrayList<>();
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900285
286 @GuardedBy("mLock")
287 private List<HdmiDeviceInfo> mMhlDevices;
288
289 @Nullable
Jinsuk Kim78104122014-08-26 19:32:34 +0900290 private HdmiMhlControllerStub mMhlController;
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900291
Jinsuk Kim7fa3a662014-11-07 15:20:24 +0900292 @Nullable
293 private TvInputManager mTvInputManager;
294
Jinsuk Kime26d8332015-01-09 08:55:41 +0900295 @Nullable
296 private PowerManager mPowerManager;
297
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900298 // Last input port before switching to the MHL port. Should switch back to this port
299 // when the mobile device sends the request one touch play with off.
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900300 // Gets invalidated if we go to other port/input.
301 @ServiceThreadOnly
302 private int mLastInputMhl = Constants.INVALID_PORT_ID;
303
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900304 // Set to true if the logical address allocation is completed.
305 private boolean mAddressAllocated = false;
306
307 // Buffer for processing the incoming cec messages while allocating logical addresses.
308 private final class CecMessageBuffer {
309 private List<HdmiCecMessage> mBuffer = new ArrayList<>();
310
311 public void bufferMessage(HdmiCecMessage message) {
312 switch (message.getOpcode()) {
313 case Constants.MESSAGE_ACTIVE_SOURCE:
314 bufferActiveSource(message);
315 break;
316 case Constants.MESSAGE_IMAGE_VIEW_ON:
317 case Constants.MESSAGE_TEXT_VIEW_ON:
318 bufferImageOrTextViewOn(message);
319 break;
320 // Add here if new message that needs to buffer
321 default:
322 // Do not need to buffer messages other than above
323 break;
324 }
325 }
326
327 public void processMessages() {
328 for (final HdmiCecMessage message : mBuffer) {
329 runOnServiceThread(new Runnable() {
330 @Override
331 public void run() {
332 handleCecCommand(message);
333 }
334 });
335 }
336 mBuffer.clear();
337 }
338
339 private void bufferActiveSource(HdmiCecMessage message) {
340 if (!replaceMessageIfBuffered(message, Constants.MESSAGE_ACTIVE_SOURCE)) {
341 mBuffer.add(message);
342 }
343 }
344
345 private void bufferImageOrTextViewOn(HdmiCecMessage message) {
346 if (!replaceMessageIfBuffered(message, Constants.MESSAGE_IMAGE_VIEW_ON) &&
347 !replaceMessageIfBuffered(message, Constants.MESSAGE_TEXT_VIEW_ON)) {
348 mBuffer.add(message);
349 }
350 }
351
352 // Returns true if the message is replaced
353 private boolean replaceMessageIfBuffered(HdmiCecMessage message, int opcode) {
354 for (int i = 0; i < mBuffer.size(); i++) {
355 HdmiCecMessage bufferedMessage = mBuffer.get(i);
356 if (bufferedMessage.getOpcode() == opcode) {
357 mBuffer.set(i, message);
358 return true;
359 }
360 }
361 return false;
362 }
363 }
364
Jinsuk Kimf98b9e82015-10-05 14:24:48 +0900365 private final CecMessageBuffer mCecMessageBuffer = new CecMessageBuffer();
366
367 private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer();
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900368
Jungshik Jang0792d372014-04-23 17:57:26 +0900369 public HdmiControlService(Context context) {
370 super(context);
Yuncheol Heo7d9acc72014-08-12 15:30:49 +0900371 mLocalDevices = getIntList(SystemProperties.get(Constants.PROPERTY_DEVICE_TYPE));
Jinsuk Kim50084862014-08-07 13:11:40 +0900372 mSettingsObserver = new SettingsObserver(mHandler);
Jungshik Jang0792d372014-04-23 17:57:26 +0900373 }
374
Yuncheol Heo7d9acc72014-08-12 15:30:49 +0900375 private static List<Integer> getIntList(String string) {
376 ArrayList<Integer> list = new ArrayList<>();
377 TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
378 splitter.setString(string);
379 for (String item : splitter) {
380 try {
381 list.add(Integer.parseInt(item));
382 } catch (NumberFormatException e) {
383 Slog.w(TAG, "Can't parseInt: " + item);
384 }
385 }
386 return Collections.unmodifiableList(list);
387 }
388
Jungshik Jang0792d372014-04-23 17:57:26 +0900389 @Override
390 public void onStart() {
Jungshik Jang2f51aec2014-05-20 14:37:38 +0900391 mIoThread.start();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900392 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900393 mProhibitMode = false;
394 mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
Yuncheol Heo08a1be82014-08-12 20:58:41 +0900395 mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900396
Jungshik Janga9f10622014-07-11 15:36:39 +0900397 mCecController = HdmiCecController.create(this);
Jinsuk Kima8a5e502014-05-15 16:51:49 +0900398 if (mCecController != null) {
Jungshik Janga9f10622014-07-11 15:36:39 +0900399 if (mHdmiControlEnabled) {
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900400 initializeCec(INITIATED_BY_BOOT_UP);
Jungshik Janga9f10622014-07-11 15:36:39 +0900401 }
Jinsuk Kima8a5e502014-05-15 16:51:49 +0900402 } else {
Jungshik Jang0792d372014-04-23 17:57:26 +0900403 Slog.i(TAG, "Device does not support HDMI-CEC.");
Jinsuk Kim08f1ab02014-10-13 10:38:16 +0900404 return;
Jungshik Jang0792d372014-04-23 17:57:26 +0900405 }
406
Jinsuk Kim78104122014-08-26 19:32:34 +0900407 mMhlController = HdmiMhlControllerStub.create(this);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900408 if (!mMhlController.isReady()) {
Jungshik Jang0792d372014-04-23 17:57:26 +0900409 Slog.i(TAG, "Device does not support MHL-control.");
410 }
Jinsuk Kimed086452014-08-18 15:01:53 +0900411 mMhlDevices = Collections.emptyList();
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900412
413 initPortInfo();
Yuncheol Heo75a77e72014-07-09 18:27:53 +0900414 mMessageValidator = new HdmiCecMessageValidator(this);
Jinsuk Kim8692fc62014-05-29 07:39:22 +0900415 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900416
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900417 if (mCecController != null) {
Yuncheol Heo0608b932014-10-13 16:39:18 +0900418 // Register broadcast receiver for power state change.
Yuncheol Heo38db6292014-07-01 14:15:14 +0900419 IntentFilter filter = new IntentFilter();
420 filter.addAction(Intent.ACTION_SCREEN_OFF);
421 filter.addAction(Intent.ACTION_SCREEN_ON);
Rob McConnell30595562016-03-11 11:03:00 +0000422 filter.addAction(Intent.ACTION_SHUTDOWN);
Terry Heo1ca0a432014-08-18 10:30:32 +0900423 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
424 getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter);
Yuncheol Heo0608b932014-10-13 16:39:18 +0900425
426 // Register ContentObserver to monitor the settings change.
427 registerContentObserver();
Yuncheol Heo38db6292014-07-01 14:15:14 +0900428 }
Jinsuk Kim5b8cb002015-01-19 07:30:12 +0900429 mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED);
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900430 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900431
Jinsuk Kim7fa3a662014-11-07 15:20:24 +0900432 @Override
433 public void onBootPhase(int phase) {
434 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
435 mTvInputManager = (TvInputManager) getContext().getSystemService(
436 Context.TV_INPUT_SERVICE);
Jinsuk Kime26d8332015-01-09 08:55:41 +0900437 mPowerManager = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
Jinsuk Kim7fa3a662014-11-07 15:20:24 +0900438 }
439 }
440
441 TvInputManager getTvInputManager() {
442 return mTvInputManager;
443 }
444
445 void registerTvInputCallback(TvInputCallback callback) {
446 if (mTvInputManager == null) return;
447 mTvInputManager.registerCallback(callback, mHandler);
448 }
449
450 void unregisterTvInputCallback(TvInputCallback callback) {
451 if (mTvInputManager == null) return;
452 mTvInputManager.unregisterCallback(callback);
453 }
454
Jinsuk Kime26d8332015-01-09 08:55:41 +0900455 PowerManager getPowerManager() {
456 return mPowerManager;
457 }
458
Yuncheol Heo25c20292014-07-31 17:59:39 +0900459 /**
460 * Called when the initialization of local devices is complete.
461 */
Yuncheol Heo0608b932014-10-13 16:39:18 +0900462 private void onInitializeCecComplete(int initiatedBy) {
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900463 if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
464 mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
465 }
466 mWakeUpMessageReceived = false;
467
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900468 if (isTvDeviceEnabled()) {
Jinsuk Kim50084862014-08-07 13:11:40 +0900469 mCecController.setOption(OPTION_CEC_AUTO_WAKEUP, toInt(tv().getAutoWakeup()));
Yuncheol Heo0608b932014-10-13 16:39:18 +0900470 }
471 int reason = -1;
472 switch (initiatedBy) {
473 case INITIATED_BY_BOOT_UP:
474 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_START;
475 break;
476 case INITIATED_BY_ENABLE_CEC:
477 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING;
478 break;
479 case INITIATED_BY_SCREEN_ON:
480 case INITIATED_BY_WAKE_UP_MESSAGE:
481 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_WAKEUP;
482 break;
483 }
484 if (reason != -1) {
485 invokeVendorCommandListenersOnControlStateChanged(true, reason);
Yuncheol Heo25c20292014-07-31 17:59:39 +0900486 }
487 }
488
Jinsuk Kim50084862014-08-07 13:11:40 +0900489 private void registerContentObserver() {
490 ContentResolver resolver = getContext().getContentResolver();
491 String[] settings = new String[] {
492 Global.HDMI_CONTROL_ENABLED,
493 Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
494 Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
495 Global.MHL_INPUT_SWITCHING_ENABLED,
496 Global.MHL_POWER_CHARGE_ENABLED
497 };
Jungshik Jang5691b2f2014-08-18 16:50:12 +0900498 for (String s : settings) {
Jinsuk Kim50084862014-08-07 13:11:40 +0900499 resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
500 UserHandle.USER_ALL);
501 }
502 }
503
504 private class SettingsObserver extends ContentObserver {
505 public SettingsObserver(Handler handler) {
506 super(handler);
507 }
508
Jungshik Jangf67113f2014-08-22 16:27:19 +0900509 // onChange is set up to run in service thread.
Jinsuk Kim50084862014-08-07 13:11:40 +0900510 @Override
511 public void onChange(boolean selfChange, Uri uri) {
512 String option = uri.getLastPathSegment();
513 boolean enabled = readBooleanSetting(option, true);
514 switch (option) {
515 case Global.HDMI_CONTROL_ENABLED:
516 setControlEnabled(enabled);
517 break;
518 case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900519 if (isTvDeviceEnabled()) {
520 tv().setAutoWakeup(enabled);
521 }
Jungshik Jang350e68d2014-08-19 18:56:21 +0900522 setCecOption(OPTION_CEC_AUTO_WAKEUP, toInt(enabled));
Jinsuk Kim50084862014-08-07 13:11:40 +0900523 break;
524 case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900525 for (int type : mLocalDevices) {
526 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
Terry Heodd371ec2015-12-10 15:31:05 +0900527 if (localDevice != null) {
528 localDevice.setAutoDeviceOff(enabled);
529 }
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900530 }
Jinsuk Kim50084862014-08-07 13:11:40 +0900531 // No need to propagate to HAL.
532 break;
533 case Global.MHL_INPUT_SWITCHING_ENABLED:
Yuncheol Heo08a1be82014-08-12 20:58:41 +0900534 setMhlInputChangeEnabled(enabled);
Jinsuk Kim50084862014-08-07 13:11:40 +0900535 break;
536 case Global.MHL_POWER_CHARGE_ENABLED:
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900537 mMhlController.setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled));
Jinsuk Kim50084862014-08-07 13:11:40 +0900538 break;
539 }
540 }
541 }
542
543 private static int toInt(boolean enabled) {
544 return enabled ? ENABLED : DISABLED;
545 }
546
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900547 boolean readBooleanSetting(String key, boolean defVal) {
548 ContentResolver cr = getContext().getContentResolver();
Jinsuk Kim50084862014-08-07 13:11:40 +0900549 return Global.getInt(cr, key, toInt(defVal)) == ENABLED;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900550 }
551
552 void writeBooleanSetting(String key, boolean value) {
553 ContentResolver cr = getContext().getContentResolver();
Jinsuk Kim50084862014-08-07 13:11:40 +0900554 Global.putInt(cr, key, toInt(value));
555 }
556
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900557 private void initializeCec(int initiatedBy) {
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900558 mAddressAllocated = false;
Jinsuk Kim50084862014-08-07 13:11:40 +0900559 mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, ENABLED);
Jinsuk Kim5b8cb002015-01-19 07:30:12 +0900560 mCecController.setOption(OPTION_CEC_SET_LANGUAGE, HdmiUtils.languageToInt(mLanguage));
Yuncheol Heob5021862014-09-02 10:36:04 +0900561 initializeLocalDevices(initiatedBy);
Jungshik Janga9f10622014-07-11 15:36:39 +0900562 }
563
Jungshik Janga5b74142014-06-23 18:03:10 +0900564 @ServiceThreadOnly
Yuncheol Heob5021862014-09-02 10:36:04 +0900565 private void initializeLocalDevices(final int initiatedBy) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900566 assertRunOnServiceThread();
Yuncheol Heob5021862014-09-02 10:36:04 +0900567 // A container for [Device type, Local device info].
568 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
Yuncheol Heob5021862014-09-02 10:36:04 +0900569 for (int type : mLocalDevices) {
Jinsuk Kim6f87b4e2014-10-10 14:40:29 +0900570 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
571 if (localDevice == null) {
572 localDevice = HdmiCecLocalDevice.create(this, type);
573 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900574 localDevice.init();
Yuncheol Heob5021862014-09-02 10:36:04 +0900575 localDevices.add(localDevice);
576 }
Jinsuk Kim6f87b4e2014-10-10 14:40:29 +0900577 // It's now safe to flush existing local devices from mCecController since they were
578 // already moved to 'localDevices'.
579 clearLocalDevices();
Yuncheol Heob5021862014-09-02 10:36:04 +0900580 allocateLogicalAddress(localDevices, initiatedBy);
581 }
582
583 @ServiceThreadOnly
584 private void allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices,
585 final int initiatedBy) {
586 assertRunOnServiceThread();
Yuncheol Heo89ec14e2014-09-16 15:53:59 +0900587 mCecController.clearLogicalAddress();
Yuncheol Heob5021862014-09-02 10:36:04 +0900588 final ArrayList<HdmiCecLocalDevice> allocatedDevices = new ArrayList<>();
589 final int[] finished = new int[1];
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900590 mAddressAllocated = allocatingDevices.isEmpty();
591
Jinsuk Kimf98b9e82015-10-05 14:24:48 +0900592 // For TV device, select request can be invoked while address allocation or device
593 // discovery is in progress. Initialize the request here at the start of allocation,
594 // and process the collected requests later when the allocation and device discovery
595 // is all completed.
596 mSelectRequestBuffer.clear();
597
Yuncheol Heob5021862014-09-02 10:36:04 +0900598 for (final HdmiCecLocalDevice localDevice : allocatingDevices) {
599 mCecController.allocateLogicalAddress(localDevice.getType(),
Jungshik Jang3ee65722014-06-03 16:22:30 +0900600 localDevice.getPreferredAddress(), new AllocateAddressCallback() {
601 @Override
602 public void onAllocated(int deviceType, int logicalAddress) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900603 if (logicalAddress == Constants.ADDR_UNREGISTERED) {
Jungshik Jang3ee65722014-06-03 16:22:30 +0900604 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
605 } else {
Jungshik Jang410ca9c2014-08-07 18:04:14 +0900606 // Set POWER_STATUS_ON to all local devices because they share lifetime
607 // with system.
608 HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
609 HdmiControlManager.POWER_STATUS_ON);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900610 localDevice.setDeviceInfo(deviceInfo);
611 mCecController.addLocalDevice(deviceType, localDevice);
612 mCecController.addLogicalAddress(logicalAddress);
Yuncheol Heob5021862014-09-02 10:36:04 +0900613 allocatedDevices.add(localDevice);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900614 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900615
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900616 // Address allocation completed for all devices. Notify each device.
Yuncheol Heob5021862014-09-02 10:36:04 +0900617 if (allocatingDevices.size() == ++finished[0]) {
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900618 mAddressAllocated = true;
Yuncheol Heob5021862014-09-02 10:36:04 +0900619 if (initiatedBy != INITIATED_BY_HOTPLUG) {
620 // In case of the hotplug we don't call onInitializeCecComplete()
621 // since we reallocate the logical address only.
Yuncheol Heo0608b932014-10-13 16:39:18 +0900622 onInitializeCecComplete(initiatedBy);
Yuncheol Heob5021862014-09-02 10:36:04 +0900623 }
624 notifyAddressAllocated(allocatedDevices, initiatedBy);
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900625 mCecMessageBuffer.processMessages();
Jungshik Jang3ee65722014-06-03 16:22:30 +0900626 }
627 }
628 });
629 }
630 }
631
Jungshik Janga5b74142014-06-23 18:03:10 +0900632 @ServiceThreadOnly
Yuncheol Heob5021862014-09-02 10:36:04 +0900633 private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900634 assertRunOnServiceThread();
Yuncheol Heob5021862014-09-02 10:36:04 +0900635 for (HdmiCecLocalDevice device : devices) {
636 int address = device.getDeviceInfo().getLogicalAddress();
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900637 device.handleAddressAllocated(address, initiatedBy);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900638 }
Jinsuk Kimf98b9e82015-10-05 14:24:48 +0900639 if (isTvDeviceEnabled()) {
640 tv().setSelectRequestBuffer(mSelectRequestBuffer);
641 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900642 }
643
Donghyun Chofc462b92016-05-13 21:06:02 +0900644 boolean isAddressAllocated() {
645 return mAddressAllocated;
646 }
647
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900648 // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
649 // keep them in one place.
Jungshik Janga5b74142014-06-23 18:03:10 +0900650 @ServiceThreadOnly
Jinsuk Kim2b152012014-07-25 08:22:26 +0900651 private void initPortInfo() {
Jungshik Janga5b74142014-06-23 18:03:10 +0900652 assertRunOnServiceThread();
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900653 HdmiPortInfo[] cecPortInfo = null;
654
655 // CEC HAL provides majority of the info while MHL does only MHL support flag for
656 // each port. Return empty array if CEC HAL didn't provide the info.
657 if (mCecController != null) {
658 cecPortInfo = mCecController.getPortInfos();
659 }
660 if (cecPortInfo == null) {
Jinsuk Kim2b152012014-07-25 08:22:26 +0900661 return;
662 }
663
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900664 SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
665 SparseIntArray portIdMap = new SparseIntArray();
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900666 SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>();
Jinsuk Kim2b152012014-07-25 08:22:26 +0900667 for (HdmiPortInfo info : cecPortInfo) {
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900668 portIdMap.put(info.getAddress(), info.getId());
669 portInfoMap.put(info.getId(), info);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900670 portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900671 }
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900672 mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
673 mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900674 mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900675
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900676 HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
677 ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
678 for (HdmiPortInfo info : mhlPortInfo) {
679 if (info.isMhlSupported()) {
680 mhlSupportedPorts.add(info.getId());
681 }
682 }
683
684 // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use
685 // cec port info if we do not have have port that supports MHL.
686 if (mhlSupportedPorts.isEmpty()) {
Jinsuk Kimf4eb72d2014-07-25 13:02:51 +0900687 mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo));
688 return;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900689 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900690 ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
691 for (HdmiPortInfo info : cecPortInfo) {
692 if (mhlSupportedPorts.contains(info.getId())) {
693 result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
694 info.isCecSupported(), true, info.isArcSupported()));
695 } else {
696 result.add(info);
697 }
698 }
699 mPortInfo = Collections.unmodifiableList(result);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900700 }
701
Jungshik Jang2738e2d2014-08-19 09:30:05 +0900702 List<HdmiPortInfo> getPortInfo() {
703 return mPortInfo;
704 }
705
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900706 /**
707 * Returns HDMI port information for the given port id.
708 *
709 * @param portId HDMI port id
710 * @return {@link HdmiPortInfo} for the given port
711 */
712 HdmiPortInfo getPortInfo(int portId) {
Jinsuk Kim2b152012014-07-25 08:22:26 +0900713 return mPortInfoMap.get(portId, null);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900714 }
715
Jungshik Jange9c77c82014-04-24 20:30:09 +0900716 /**
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900717 * Returns the routing path (physical address) of the HDMI port for the given
718 * port id.
719 */
720 int portIdToPath(int portId) {
721 HdmiPortInfo portInfo = getPortInfo(portId);
722 if (portInfo == null) {
723 Slog.e(TAG, "Cannot find the port info: " + portId);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900724 return Constants.INVALID_PHYSICAL_ADDRESS;
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900725 }
726 return portInfo.getAddress();
727 }
728
729 /**
730 * Returns the id of HDMI port located at the top of the hierarchy of
731 * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
732 * the port id to be returned is the ID associated with the port address
733 * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
734 */
735 int pathToPortId(int path) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900736 int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900737 return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900738 }
739
Jinsuk Kim09ffc842014-07-11 17:04:32 +0900740 boolean isValidPortId(int portId) {
Jinsuk Kim2b152012014-07-25 08:22:26 +0900741 return getPortInfo(portId) != null;
Jinsuk Kim09ffc842014-07-11 17:04:32 +0900742 }
743
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900744 /**
Jungshik Jange9c77c82014-04-24 20:30:09 +0900745 * Returns {@link Looper} for IO operation.
746 *
747 * <p>Declared as package-private.
748 */
749 Looper getIoLooper() {
750 return mIoThread.getLooper();
751 }
752
753 /**
754 * Returns {@link Looper} of main thread. Use this {@link Looper} instance
755 * for tasks that are running on main service thread.
756 *
757 * <p>Declared as package-private.
758 */
759 Looper getServiceLooper() {
Jungshik Jang67ea5212014-05-15 14:05:24 +0900760 return mHandler.getLooper();
Jungshik Jange9c77c82014-04-24 20:30:09 +0900761 }
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900762
763 /**
Jungshik Jang3ee65722014-06-03 16:22:30 +0900764 * Returns physical address of the device.
765 */
766 int getPhysicalAddress() {
767 return mCecController.getPhysicalAddress();
768 }
769
770 /**
771 * Returns vendor id of CEC service.
772 */
773 int getVendorId() {
774 return mCecController.getVendorId();
775 }
776
Jungshik Janga5b74142014-06-23 18:03:10 +0900777 @ServiceThreadOnly
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900778 HdmiDeviceInfo getDeviceInfo(int logicalAddress) {
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900779 assertRunOnServiceThread();
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900780 return tv() == null ? null : tv().getCecDeviceInfo(logicalAddress);
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900781 }
782
Jinsuk Kim6ad7cbd2015-01-06 11:30:56 +0900783 @ServiceThreadOnly
784 HdmiDeviceInfo getDeviceInfoByPort(int port) {
785 assertRunOnServiceThread();
786 HdmiMhlLocalDeviceStub info = mMhlController.getLocalDevice(port);
787 if (info != null) {
788 return info.getInfo();
789 }
790 return null;
791 }
792
Jungshik Jang3ee65722014-06-03 16:22:30 +0900793 /**
Jungshik Jang092b4452014-06-11 15:19:17 +0900794 * Returns version of CEC.
795 */
796 int getCecVersion() {
797 return mCecController.getVersion();
798 }
799
800 /**
Jungshik Jang60cffce2014-06-12 18:03:04 +0900801 * Whether a device of the specified physical address is connected to ARC enabled port.
802 */
803 boolean isConnectedToArcPort(int physicalAddress) {
Jungshik Jang339227d2014-08-25 15:37:20 +0900804 int portId = pathToPortId(physicalAddress);
Jinsuk Kim2b152012014-07-25 08:22:26 +0900805 if (portId != Constants.INVALID_PORT_ID) {
806 return mPortInfoMap.get(portId).isArcSupported();
Jungshik Jang60cffce2014-06-12 18:03:04 +0900807 }
808 return false;
809 }
810
Jinsuk Kim7b0cf642015-04-14 09:43:45 +0900811 @ServiceThreadOnly
812 boolean isConnected(int portId) {
813 assertRunOnServiceThread();
814 return mCecController.isConnected(portId);
815 }
816
Jungshik Jang79c58a42014-06-16 16:45:36 +0900817 void runOnServiceThread(Runnable runnable) {
Jungshik Jang67ea5212014-05-15 14:05:24 +0900818 mHandler.post(runnable);
819 }
820
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900821 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
822 mHandler.postAtFrontOfQueue(runnable);
823 }
824
825 private void assertRunOnServiceThread() {
826 if (Looper.myLooper() != mHandler.getLooper()) {
827 throw new IllegalStateException("Should run on service thread.");
828 }
829 }
830
Jungshik Jang67ea5212014-05-15 14:05:24 +0900831 /**
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900832 * Transmit a CEC command to CEC bus.
833 *
834 * @param command CEC command to send out
Jungshik Jangd643f762014-05-22 19:28:09 +0900835 * @param callback interface used to the result of send command
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900836 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900837 @ServiceThreadOnly
Jungshik Jangd643f762014-05-22 19:28:09 +0900838 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900839 assertRunOnServiceThread();
Yuncheol Heo4c212892014-09-12 14:32:46 +0900840 if (mMessageValidator.isValid(command) == HdmiCecMessageValidator.OK) {
Jungshik Jang5f75cbd2014-08-07 12:02:29 +0900841 mCecController.sendCommand(command, callback);
842 } else {
Jungshik Jang2e8f1b62014-09-03 08:28:02 +0900843 HdmiLogger.error("Invalid message type:" + command);
Jungshik Jang5f75cbd2014-08-07 12:02:29 +0900844 if (callback != null) {
845 callback.onSendCompleted(Constants.SEND_RESULT_FAILURE);
846 }
847 }
Jungshik Jangd643f762014-05-22 19:28:09 +0900848 }
849
Jungshik Janga5b74142014-06-23 18:03:10 +0900850 @ServiceThreadOnly
Jungshik Jangd643f762014-05-22 19:28:09 +0900851 void sendCecCommand(HdmiCecMessage command) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900852 assertRunOnServiceThread();
Jungshik Jang5f75cbd2014-08-07 12:02:29 +0900853 sendCecCommand(command, null);
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900854 }
855
Yuncheol Heo6aae6522014-08-05 14:48:37 +0900856 /**
857 * Send <Feature Abort> command on the given CEC message if possible.
858 * If the aborted message is invalid, then it wont send the message.
859 * @param command original command to be aborted
860 * @param reason reason of feature abort
861 */
862 @ServiceThreadOnly
863 void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) {
864 assertRunOnServiceThread();
865 mCecController.maySendFeatureAbortCommand(command, reason);
866 }
867
Jungshik Janga5b74142014-06-23 18:03:10 +0900868 @ServiceThreadOnly
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900869 boolean handleCecCommand(HdmiCecMessage message) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900870 assertRunOnServiceThread();
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900871 if (!mAddressAllocated) {
872 mCecMessageBuffer.bufferMessage(message);
873 return true;
874 }
Yuncheol Heo4c212892014-09-12 14:32:46 +0900875 int errorCode = mMessageValidator.isValid(message);
876 if (errorCode != HdmiCecMessageValidator.OK) {
Yuncheol Heoa95f1a92014-11-06 08:25:39 +0900877 // We'll not response on the messages with the invalid source or destination
878 // or with parameter length shorter than specified in the standard.
Yuncheol Heo4c212892014-09-12 14:32:46 +0900879 if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) {
880 maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
881 }
882 return true;
Yuncheol Heo75a77e72014-07-09 18:27:53 +0900883 }
Jungshik Jang092b4452014-06-11 15:19:17 +0900884 return dispatchMessageToLocalDevice(message);
885 }
886
Jinsuk Kim1481a422014-12-17 16:15:05 +0900887 void setAudioReturnChannel(int portId, boolean enabled) {
888 mCecController.setAudioReturnChannel(portId, enabled);
Jungshik Jang60cffce2014-06-12 18:03:04 +0900889 }
890
Jungshik Janga5b74142014-06-23 18:03:10 +0900891 @ServiceThreadOnly
Jungshik Jang092b4452014-06-11 15:19:17 +0900892 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900893 assertRunOnServiceThread();
Jungshik Jang092b4452014-06-11 15:19:17 +0900894 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900895 if (device.dispatchMessage(message)
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900896 && message.getDestination() != Constants.ADDR_BROADCAST) {
Jungshik Jang092b4452014-06-11 15:19:17 +0900897 return true;
898 }
899 }
Jungshik Jang60cffce2014-06-12 18:03:04 +0900900
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900901 if (message.getDestination() != Constants.ADDR_BROADCAST) {
Jungshik Jang2e8f1b62014-09-03 08:28:02 +0900902 HdmiLogger.warning("Unhandled cec command:" + message);
Jungshik Jang3a959fc2014-07-03 09:34:05 +0900903 }
Jungshik Jang092b4452014-06-11 15:19:17 +0900904 return false;
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900905 }
906
Jungshik Jang67ea5212014-05-15 14:05:24 +0900907 /**
908 * Called when a new hotplug event is issued.
909 *
Jinsuk Kimed086452014-08-18 15:01:53 +0900910 * @param portId hdmi port number where hot plug event issued.
Jungshik Jang67ea5212014-05-15 14:05:24 +0900911 * @param connected whether to be plugged in or not
912 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900913 @ServiceThreadOnly
Jinsuk Kimed086452014-08-18 15:01:53 +0900914 void onHotplug(int portId, boolean connected) {
Jungshik Jang60cffce2014-06-12 18:03:04 +0900915 assertRunOnServiceThread();
Yuncheol Heob5021862014-09-02 10:36:04 +0900916
Yuncheol Heob8d62e72014-09-22 19:53:41 +0900917 if (connected && !isTvDevice()) {
918 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
919 for (int type : mLocalDevices) {
920 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
921 if (localDevice == null) {
922 localDevice = HdmiCecLocalDevice.create(this, type);
923 localDevice.init();
924 }
925 localDevices.add(localDevice);
Yuncheol Heob5021862014-09-02 10:36:04 +0900926 }
Yuncheol Heob8d62e72014-09-22 19:53:41 +0900927 allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
Yuncheol Heob5021862014-09-02 10:36:04 +0900928 }
Yuncheol Heob5021862014-09-02 10:36:04 +0900929
Jungshik Jang79c58a42014-06-16 16:45:36 +0900930 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jinsuk Kimed086452014-08-18 15:01:53 +0900931 device.onHotplug(portId, connected);
Jungshik Jang60cffce2014-06-12 18:03:04 +0900932 }
Jinsuk Kimed086452014-08-18 15:01:53 +0900933 announceHotplugEvent(portId, connected);
Jungshik Jang67ea5212014-05-15 14:05:24 +0900934 }
935
Jungshik Jang02bb4262014-05-23 16:48:31 +0900936 /**
937 * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
938 * devices.
939 *
940 * @param callback an interface used to get a list of all remote devices' address
Jungshik Jang1de51422014-07-03 11:14:26 +0900941 * @param sourceAddress a logical address of source device where sends polling message
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900942 * @param pickStrategy strategy how to pick polling candidates
Jungshik Jang02bb4262014-05-23 16:48:31 +0900943 * @param retryCount the number of retry used to send polling message to remote devices
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900944 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
Jungshik Jang02bb4262014-05-23 16:48:31 +0900945 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900946 @ServiceThreadOnly
Jungshik Jang1de51422014-07-03 11:14:26 +0900947 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
948 int retryCount) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900949 assertRunOnServiceThread();
Jungshik Jang1de51422014-07-03 11:14:26 +0900950 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
951 retryCount);
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900952 }
953
954 private int checkPollStrategy(int pickStrategy) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900955 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900956 if (strategy == 0) {
957 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
958 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900959 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900960 if (iterationStrategy == 0) {
961 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
962 }
963 return strategy | iterationStrategy;
Jungshik Jang02bb4262014-05-23 16:48:31 +0900964 }
965
Jungshik Jang60cffce2014-06-12 18:03:04 +0900966 List<HdmiCecLocalDevice> getAllLocalDevices() {
967 assertRunOnServiceThread();
968 return mCecController.getLocalDeviceList();
969 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900970
Jungshik Jang79c58a42014-06-16 16:45:36 +0900971 Object getServiceLock() {
972 return mLock;
973 }
974
975 void setAudioStatus(boolean mute, int volume) {
Jungshik Jangb69aafbf2014-07-11 16:29:06 +0900976 AudioManager audioManager = getAudioManager();
977 boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
978 if (mute) {
979 if (!muted) {
980 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
981 }
982 } else {
983 if (muted) {
984 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
985 }
986 // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
987 // volume change notification back to hdmi control service.
988 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
Jungshik Jang1a6be6e2014-09-16 11:04:54 +0900989 AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
Jungshik Jangb69aafbf2014-07-11 16:29:06 +0900990 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900991 }
992
Jungshik Jangea67c182014-06-19 22:19:20 +0900993 void announceSystemAudioModeChange(boolean enabled) {
Jungshik Jangf4249322014-08-21 14:17:05 +0900994 synchronized (mLock) {
995 for (SystemAudioModeChangeListenerRecord record :
996 mSystemAudioModeChangeListenerRecords) {
997 invokeSystemAudioModeChangeLocked(record.mListener, enabled);
998 }
Jungshik Jangea67c182014-06-19 22:19:20 +0900999 }
1000 }
1001
Jungshik Jang410ca9c2014-08-07 18:04:14 +09001002 private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
Jungshik Jang42c98002014-06-12 13:17:44 +09001003 // TODO: find better name instead of model name.
1004 String displayName = Build.MODEL;
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001005 return new HdmiDeviceInfo(logicalAddress,
Jinsuk Kim2b152012014-07-25 08:22:26 +09001006 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
1007 getVendorId(), displayName);
Jungshik Jang3ee65722014-06-03 16:22:30 +09001008 }
1009
Jungshik Jang7df52862014-08-11 14:35:27 +09001010 @ServiceThreadOnly
Jungshik Jang7df52862014-08-11 14:35:27 +09001011 void handleMhlHotplugEvent(int portId, boolean connected) {
1012 assertRunOnServiceThread();
Jinsuk Kim93eed0c2014-10-14 11:52:22 +09001013 // Hotplug event is used to add/remove MHL devices as TV input.
Jungshik Jang7df52862014-08-11 14:35:27 +09001014 if (connected) {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001015 HdmiMhlLocalDeviceStub newDevice = new HdmiMhlLocalDeviceStub(this, portId);
1016 HdmiMhlLocalDeviceStub oldDevice = mMhlController.addLocalDevice(newDevice);
Jungshik Jang7df52862014-08-11 14:35:27 +09001017 if (oldDevice != null) {
1018 oldDevice.onDeviceRemoved();
1019 Slog.i(TAG, "Old device of port " + portId + " is removed");
1020 }
Jinsuk Kim93eed0c2014-10-14 11:52:22 +09001021 invokeDeviceEventListeners(newDevice.getInfo(), DEVICE_EVENT_ADD_DEVICE);
1022 updateSafeMhlInput();
Jungshik Jang7df52862014-08-11 14:35:27 +09001023 } else {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001024 HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001025 if (device != null) {
1026 device.onDeviceRemoved();
Jinsuk Kim93eed0c2014-10-14 11:52:22 +09001027 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE);
1028 updateSafeMhlInput();
Jungshik Jang7df52862014-08-11 14:35:27 +09001029 } else {
1030 Slog.w(TAG, "No device to remove:[portId=" + portId);
1031 }
1032 }
Jinsuk Kimed086452014-08-18 15:01:53 +09001033 announceHotplugEvent(portId, connected);
Jungshik Jang7df52862014-08-11 14:35:27 +09001034 }
1035
1036 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001037 void handleMhlBusModeChanged(int portId, int busmode) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001038 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001039 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001040 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001041 device.setBusMode(busmode);
Jungshik Jang7df52862014-08-11 14:35:27 +09001042 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001043 Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId +
1044 ", busmode:" + busmode + "]");
Jungshik Jang7df52862014-08-11 14:35:27 +09001045 }
1046 }
1047
1048 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001049 void handleMhlBusOvercurrent(int portId, boolean on) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001050 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001051 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001052 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001053 device.onBusOvercurrentDetected(on);
Jungshik Jang7df52862014-08-11 14:35:27 +09001054 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001055 Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]");
Jungshik Jang7df52862014-08-11 14:35:27 +09001056 }
1057 }
1058
1059 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001060 void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001061 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001062 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jinsuk Kimed086452014-08-18 15:01:53 +09001063
Jungshik Jang7df52862014-08-11 14:35:27 +09001064 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001065 device.setDeviceStatusChange(adopterId, deviceId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001066 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001067 Slog.w(TAG, "No mhl device exists for device status event[portId:"
Jungshik Jang7df52862014-08-11 14:35:27 +09001068 + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]");
1069 }
1070 }
1071
Jinsuk Kimed086452014-08-18 15:01:53 +09001072 @ServiceThreadOnly
1073 private void updateSafeMhlInput() {
1074 assertRunOnServiceThread();
1075 List<HdmiDeviceInfo> inputs = Collections.emptyList();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001076 SparseArray<HdmiMhlLocalDeviceStub> devices = mMhlController.getAllLocalDevices();
Jinsuk Kimed086452014-08-18 15:01:53 +09001077 for (int i = 0; i < devices.size(); ++i) {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001078 HdmiMhlLocalDeviceStub device = devices.valueAt(i);
Jinsuk Kimed086452014-08-18 15:01:53 +09001079 HdmiDeviceInfo info = device.getInfo();
1080 if (info != null) {
1081 if (inputs.isEmpty()) {
1082 inputs = new ArrayList<>();
1083 }
1084 inputs.add(device.getInfo());
1085 }
1086 }
1087 synchronized (mLock) {
1088 mMhlDevices = inputs;
1089 }
1090 }
1091
1092 private List<HdmiDeviceInfo> getMhlDevicesLocked() {
1093 return mMhlDevices;
1094 }
1095
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001096 private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient {
1097 private final IHdmiMhlVendorCommandListener mListener;
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001098
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001099 public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) {
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001100 mListener = listener;
1101 }
1102
1103 @Override
1104 public void binderDied() {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001105 mMhlVendorCommandListenerRecords.remove(this);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001106 }
1107 }
1108
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001109 // Record class that monitors the event of the caller of being killed. Used to clean up
1110 // the listener list and record list accordingly.
1111 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
1112 private final IHdmiHotplugEventListener mListener;
1113
1114 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
1115 mListener = listener;
1116 }
1117
1118 @Override
1119 public void binderDied() {
1120 synchronized (mLock) {
1121 mHotplugEventListenerRecords.remove(this);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001122 }
1123 }
Jinsuk Kim3cd30512014-12-04 11:05:09 +09001124
1125 @Override
1126 public boolean equals(Object obj) {
1127 if (!(obj instanceof HotplugEventListenerRecord)) return false;
1128 if (obj == this) return true;
1129 HotplugEventListenerRecord other = (HotplugEventListenerRecord) obj;
1130 return other.mListener == this.mListener;
1131 }
1132
1133 @Override
1134 public int hashCode() {
1135 return mListener.hashCode();
1136 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001137 }
1138
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001139 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
1140 private final IHdmiDeviceEventListener mListener;
1141
1142 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
1143 mListener = listener;
1144 }
1145
1146 @Override
Jungshik Jangea67c182014-06-19 22:19:20 +09001147 public void binderDied() {
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001148 synchronized (mLock) {
1149 mDeviceEventListenerRecords.remove(this);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001150 }
1151 }
1152 }
1153
Jungshik Jangea67c182014-06-19 22:19:20 +09001154 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
Yuncheol Heo38db6292014-07-01 14:15:14 +09001155 private final IHdmiSystemAudioModeChangeListener mListener;
Jungshik Jangea67c182014-06-19 22:19:20 +09001156
1157 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
1158 mListener = listener;
1159 }
1160
1161 @Override
1162 public void binderDied() {
1163 synchronized (mLock) {
1164 mSystemAudioModeChangeListenerRecords.remove(this);
Jungshik Jangea67c182014-06-19 22:19:20 +09001165 }
1166 }
1167 }
1168
Jinsuk Kim119160a2014-07-07 18:48:10 +09001169 class VendorCommandListenerRecord implements IBinder.DeathRecipient {
1170 private final IHdmiVendorCommandListener mListener;
1171 private final int mDeviceType;
1172
1173 public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
1174 mListener = listener;
1175 mDeviceType = deviceType;
1176 }
1177
1178 @Override
1179 public void binderDied() {
1180 synchronized (mLock) {
1181 mVendorCommandListenerRecords.remove(this);
1182 }
1183 }
1184 }
1185
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001186 private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
Jungshik Jangf4249322014-08-21 14:17:05 +09001187 private final IHdmiRecordListener mListener;
1188
1189 public HdmiRecordListenerRecord(IHdmiRecordListener listener) {
1190 mListener = listener;
1191 }
1192
Jungshik Jangb6591b82014-07-23 16:10:23 +09001193 @Override
1194 public void binderDied() {
1195 synchronized (mLock) {
Donghyun Chofbbeb3e2016-04-15 09:12:03 +09001196 if (mRecordListenerRecord == this) {
1197 mRecordListenerRecord = null;
1198 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09001199 }
1200 }
1201 }
1202
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001203 private void enforceAccessPermission() {
1204 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
1205 }
1206
1207 private final class BinderService extends IHdmiControlService.Stub {
1208 @Override
1209 public int[] getSupportedTypes() {
1210 enforceAccessPermission();
Jinsuk Kim0340bbc2014-06-05 11:07:47 +09001211 // mLocalDevices is an unmodifiable list - no lock necesary.
1212 int[] localDevices = new int[mLocalDevices.size()];
1213 for (int i = 0; i < localDevices.length; ++i) {
1214 localDevices[i] = mLocalDevices.get(i);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001215 }
Jinsuk Kim0340bbc2014-06-05 11:07:47 +09001216 return localDevices;
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001217 }
1218
1219 @Override
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001220 public HdmiDeviceInfo getActiveSource() {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001221 enforceAccessPermission();
Jinsuk Kim7e742062014-07-30 13:19:13 +09001222 HdmiCecLocalDeviceTv tv = tv();
1223 if (tv == null) {
1224 Slog.w(TAG, "Local tv device not available");
1225 return null;
1226 }
1227 ActiveSource activeSource = tv.getActiveSource();
1228 if (activeSource.isValid()) {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001229 return new HdmiDeviceInfo(activeSource.logicalAddress,
1230 activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID,
1231 HdmiDeviceInfo.DEVICE_INACTIVE, 0, "");
Jinsuk Kim7e742062014-07-30 13:19:13 +09001232 }
1233 int activePath = tv.getActivePath();
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001234 if (activePath != HdmiDeviceInfo.PATH_INVALID) {
Jinsuk Kim7640d982015-01-28 16:44:07 +09001235 HdmiDeviceInfo info = tv.getSafeDeviceInfoByPath(activePath);
Jinsuk Kimd47abef2015-01-17 07:38:24 +09001236 return (info != null) ? info : new HdmiDeviceInfo(activePath, tv.getActivePortId());
Jinsuk Kim7e742062014-07-30 13:19:13 +09001237 }
1238 return null;
1239 }
1240
1241 @Override
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001242 public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) {
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001243 enforceAccessPermission();
1244 runOnServiceThread(new Runnable() {
1245 @Override
1246 public void run() {
Jinsuk Kim72b7d732014-07-24 09:15:35 +09001247 if (callback == null) {
1248 Slog.e(TAG, "Callback cannot be null");
1249 return;
1250 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09001251 HdmiCecLocalDeviceTv tv = tv();
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001252 if (tv == null) {
Jinsuk Kimf98b9e82015-10-05 14:24:48 +09001253 if (!mAddressAllocated) {
1254 mSelectRequestBuffer.set(SelectRequestBuffer.newDeviceSelect(
1255 HdmiControlService.this, deviceId, callback));
1256 return;
1257 }
Jinsuk Kima062a932014-06-18 10:00:39 +09001258 Slog.w(TAG, "Local tv device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001259 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001260 return;
1261 }
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001262 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001263 if (device != null) {
1264 if (device.getPortId() == tv.getActivePortId()) {
1265 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
Jinsuk Kim87f22a22014-08-20 10:40:12 +09001266 return;
1267 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001268 // Upon selecting MHL device, we send RAP[Content On] to wake up
1269 // the connected mobile device, start routing control to switch ports.
1270 // callback is handled by MHL action.
1271 device.turnOn(callback);
Yuncheol Heo7c5d31e2014-09-03 16:28:54 +09001272 tv.doManualPortSwitching(device.getPortId(), null);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001273 return;
Jinsuk Kim87f22a22014-08-20 10:40:12 +09001274 }
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001275 tv.deviceSelect(deviceId, callback);
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001276 }
1277 });
1278 }
1279
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001280 @Override
Jinsuk Kima062a932014-06-18 10:00:39 +09001281 public void portSelect(final int portId, final IHdmiControlCallback callback) {
1282 enforceAccessPermission();
1283 runOnServiceThread(new Runnable() {
1284 @Override
1285 public void run() {
Jinsuk Kim72b7d732014-07-24 09:15:35 +09001286 if (callback == null) {
1287 Slog.e(TAG, "Callback cannot be null");
1288 return;
1289 }
Jinsuk Kima062a932014-06-18 10:00:39 +09001290 HdmiCecLocalDeviceTv tv = tv();
1291 if (tv == null) {
Jinsuk Kimf98b9e82015-10-05 14:24:48 +09001292 if (!mAddressAllocated) {
1293 mSelectRequestBuffer.set(SelectRequestBuffer.newPortSelect(
1294 HdmiControlService.this, portId, callback));
1295 return;
1296 }
Jinsuk Kima062a932014-06-18 10:00:39 +09001297 Slog.w(TAG, "Local tv device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001298 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kima062a932014-06-18 10:00:39 +09001299 return;
1300 }
Jinsuk Kim83335712014-06-24 07:57:00 +09001301 tv.doManualPortSwitching(portId, callback);
Jinsuk Kima062a932014-06-18 10:00:39 +09001302 }
1303 });
1304 }
1305
1306 @Override
Jinsuk Kimc068bb52014-07-07 16:59:20 +09001307 public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
Jinsuk Kima062a932014-06-18 10:00:39 +09001308 enforceAccessPermission();
1309 runOnServiceThread(new Runnable() {
1310 @Override
1311 public void run() {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001312 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001313 if (device != null) {
1314 device.sendKeyEvent(keyCode, isPressed);
1315 return;
Jinsuk Kima062a932014-06-18 10:00:39 +09001316 }
Jungshik Jang4612a6e2014-08-12 22:01:23 +09001317 if (mCecController != null) {
1318 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
1319 if (localDevice == null) {
1320 Slog.w(TAG, "Local device not available");
1321 return;
1322 }
1323 localDevice.sendKeyEvent(keyCode, isPressed);
1324 }
Jinsuk Kima062a932014-06-18 10:00:39 +09001325 }
1326 });
1327 }
1328
1329 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001330 public void oneTouchPlay(final IHdmiControlCallback callback) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001331 enforceAccessPermission();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001332 runOnServiceThread(new Runnable() {
1333 @Override
1334 public void run() {
1335 HdmiControlService.this.oneTouchPlay(callback);
1336 }
1337 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001338 }
1339
1340 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001341 public void queryDisplayStatus(final IHdmiControlCallback callback) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001342 enforceAccessPermission();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001343 runOnServiceThread(new Runnable() {
1344 @Override
1345 public void run() {
1346 HdmiControlService.this.queryDisplayStatus(callback);
1347 }
1348 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001349 }
1350
1351 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001352 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001353 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001354 HdmiControlService.this.addHotplugEventListener(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001355 }
1356
1357 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001358 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001359 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001360 HdmiControlService.this.removeHotplugEventListener(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001361 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001362
1363 @Override
1364 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
1365 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001366 HdmiControlService.this.addDeviceEventListener(listener);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001367 }
1368
1369 @Override
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001370 public List<HdmiPortInfo> getPortInfo() {
1371 enforceAccessPermission();
Jungshik Jang2738e2d2014-08-19 09:30:05 +09001372 return HdmiControlService.this.getPortInfo();
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001373 }
Jungshik Jangea67c182014-06-19 22:19:20 +09001374
1375 @Override
1376 public boolean canChangeSystemAudioMode() {
1377 enforceAccessPermission();
1378 HdmiCecLocalDeviceTv tv = tv();
1379 if (tv == null) {
1380 return false;
1381 }
Jungshik Jange9cf1582014-06-23 17:28:58 +09001382 return tv.hasSystemAudioDevice();
Jungshik Jangea67c182014-06-19 22:19:20 +09001383 }
1384
1385 @Override
1386 public boolean getSystemAudioMode() {
1387 enforceAccessPermission();
1388 HdmiCecLocalDeviceTv tv = tv();
1389 if (tv == null) {
1390 return false;
1391 }
Jungshik Jang377dcbd2014-07-15 15:49:02 +09001392 return tv.isSystemAudioActivated();
Jungshik Jangea67c182014-06-19 22:19:20 +09001393 }
1394
1395 @Override
1396 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
1397 enforceAccessPermission();
1398 runOnServiceThread(new Runnable() {
1399 @Override
1400 public void run() {
1401 HdmiCecLocalDeviceTv tv = tv();
1402 if (tv == null) {
1403 Slog.w(TAG, "Local tv device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001404 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jungshik Jangea67c182014-06-19 22:19:20 +09001405 return;
1406 }
1407 tv.changeSystemAudioMode(enabled, callback);
1408 }
1409 });
1410 }
1411
1412 @Override
1413 public void addSystemAudioModeChangeListener(
1414 final IHdmiSystemAudioModeChangeListener listener) {
1415 enforceAccessPermission();
1416 HdmiControlService.this.addSystemAudioModeChangeListner(listener);
1417 }
1418
1419 @Override
1420 public void removeSystemAudioModeChangeListener(
1421 final IHdmiSystemAudioModeChangeListener listener) {
1422 enforceAccessPermission();
1423 HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
1424 }
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001425
1426 @Override
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001427 public void setInputChangeListener(final IHdmiInputChangeListener listener) {
1428 enforceAccessPermission();
1429 HdmiControlService.this.setInputChangeListener(listener);
1430 }
1431
1432 @Override
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001433 public List<HdmiDeviceInfo> getInputDevices() {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001434 enforceAccessPermission();
1435 // No need to hold the lock for obtaining TV device as the local device instance
1436 // is preserved while the HDMI control is enabled.
1437 HdmiCecLocalDeviceTv tv = tv();
Jinsuk Kimed086452014-08-18 15:01:53 +09001438 synchronized (mLock) {
1439 List<HdmiDeviceInfo> cecDevices = (tv == null)
1440 ? Collections.<HdmiDeviceInfo>emptyList()
1441 : tv.getSafeExternalInputsLocked();
1442 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked());
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001443 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001444 }
1445
Jinsuk Kimbdf27fb2014-10-20 10:00:04 +09001446 // Returns all the CEC devices on the bus including system audio, switch,
1447 // even those of reserved type.
1448 @Override
1449 public List<HdmiDeviceInfo> getDeviceList() {
1450 enforceAccessPermission();
1451 HdmiCecLocalDeviceTv tv = tv();
1452 synchronized (mLock) {
1453 return (tv == null)
1454 ? Collections.<HdmiDeviceInfo>emptyList()
1455 : tv.getSafeCecDevicesLocked();
1456 }
1457 }
1458
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001459 @Override
Jungshik Jang41d97462014-06-30 22:26:29 +09001460 public void setSystemAudioVolume(final int oldIndex, final int newIndex,
1461 final int maxIndex) {
1462 enforceAccessPermission();
1463 runOnServiceThread(new Runnable() {
1464 @Override
1465 public void run() {
1466 HdmiCecLocalDeviceTv tv = tv();
1467 if (tv == null) {
1468 Slog.w(TAG, "Local tv device not available");
1469 return;
1470 }
1471 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
1472 }
1473 });
1474 }
1475
1476 @Override
1477 public void setSystemAudioMute(final boolean mute) {
1478 enforceAccessPermission();
1479 runOnServiceThread(new Runnable() {
1480 @Override
1481 public void run() {
1482 HdmiCecLocalDeviceTv tv = tv();
1483 if (tv == null) {
1484 Slog.w(TAG, "Local tv device not available");
1485 return;
1486 }
1487 tv.changeMute(mute);
1488 }
1489 });
1490 }
1491
1492 @Override
Jungshik Janga13da0d2014-06-30 16:26:06 +09001493 public void setArcMode(final boolean enabled) {
1494 enforceAccessPermission();
1495 runOnServiceThread(new Runnable() {
1496 @Override
1497 public void run() {
1498 HdmiCecLocalDeviceTv tv = tv();
1499 if (tv == null) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09001500 Slog.w(TAG, "Local tv device not available to change arc mode.");
Jungshik Janga13da0d2014-06-30 16:26:06 +09001501 return;
1502 }
1503 }
1504 });
1505 }
Jinsuk Kim160a6e52014-07-02 06:16:36 +09001506
1507 @Override
Jinsuk Kim4d43d932014-07-03 16:43:58 +09001508 public void setProhibitMode(final boolean enabled) {
1509 enforceAccessPermission();
1510 if (!isTvDevice()) {
1511 return;
1512 }
1513 HdmiControlService.this.setProhibitMode(enabled);
1514 }
Jinsuk Kim119160a2014-07-07 18:48:10 +09001515
1516 @Override
1517 public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
1518 final int deviceType) {
1519 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001520 HdmiControlService.this.addVendorCommandListener(listener, deviceType);
Jinsuk Kim119160a2014-07-07 18:48:10 +09001521 }
1522
1523 @Override
1524 public void sendVendorCommand(final int deviceType, final int targetAddress,
1525 final byte[] params, final boolean hasVendorId) {
1526 enforceAccessPermission();
1527 runOnServiceThread(new Runnable() {
1528 @Override
1529 public void run() {
1530 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1531 if (device == null) {
1532 Slog.w(TAG, "Local device not available");
1533 return;
1534 }
1535 if (hasVendorId) {
1536 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1537 device.getDeviceInfo().getLogicalAddress(), targetAddress,
1538 getVendorId(), params));
1539 } else {
1540 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1541 device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1542 }
1543 }
1544 });
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001545 }
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09001546
1547 @Override
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09001548 public void sendStandby(final int deviceType, final int deviceId) {
1549 enforceAccessPermission();
1550 runOnServiceThread(new Runnable() {
1551 @Override
1552 public void run() {
Jinsuk Kim61c94d12015-01-15 07:00:28 +09001553 HdmiMhlLocalDeviceStub mhlDevice = mMhlController.getLocalDeviceById(deviceId);
1554 if (mhlDevice != null) {
1555 mhlDevice.sendStandby();
1556 return;
1557 }
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09001558 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1559 if (device == null) {
1560 Slog.w(TAG, "Local device not available");
1561 return;
1562 }
1563 device.sendStandby(deviceId);
1564 }
1565 });
1566 }
1567
1568 @Override
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001569 public void setHdmiRecordListener(IHdmiRecordListener listener) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001570 enforceAccessPermission();
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001571 HdmiControlService.this.setHdmiRecordListener(listener);
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09001572 }
1573
1574 @Override
Jungshik Jangb6591b82014-07-23 16:10:23 +09001575 public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001576 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001577 runOnServiceThread(new Runnable() {
1578 @Override
1579 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001580 if (!isTvDeviceEnabled()) {
1581 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001582 return;
1583 }
1584 tv().startOneTouchRecord(recorderAddress, recordSource);
1585 }
1586 });
Jungshik Jangbffb0632014-07-22 16:56:52 +09001587 }
1588
1589 @Override
Jungshik Jangb6591b82014-07-23 16:10:23 +09001590 public void stopOneTouchRecord(final int recorderAddress) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001591 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001592 runOnServiceThread(new Runnable() {
1593 @Override
1594 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001595 if (!isTvDeviceEnabled()) {
1596 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001597 return;
1598 }
1599 tv().stopOneTouchRecord(recorderAddress);
1600 }
1601 });
1602 }
1603
1604 @Override
1605 public void startTimerRecording(final int recorderAddress, final int sourceType,
1606 final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001607 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001608 runOnServiceThread(new Runnable() {
1609 @Override
1610 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001611 if (!isTvDeviceEnabled()) {
1612 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001613 return;
1614 }
1615 tv().startTimerRecording(recorderAddress, sourceType, recordSource);
1616 }
1617 });
1618 }
1619
1620 @Override
1621 public void clearTimerRecording(final int recorderAddress, final int sourceType,
1622 final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001623 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001624 runOnServiceThread(new Runnable() {
1625 @Override
1626 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001627 if (!isTvDeviceEnabled()) {
1628 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001629 return;
1630 }
1631 tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
1632 }
1633 });
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09001634 }
Jungshik Jangf4249322014-08-21 14:17:05 +09001635
1636 @Override
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001637 public void sendMhlVendorCommand(final int portId, final int offset, final int length,
Jungshik Jangf4249322014-08-21 14:17:05 +09001638 final byte[] data) {
1639 enforceAccessPermission();
1640 runOnServiceThread(new Runnable() {
1641 @Override
1642 public void run() {
Jungshik Jangf4249322014-08-21 14:17:05 +09001643 if (!isControlEnabled()) {
1644 Slog.w(TAG, "Hdmi control is disabled.");
1645 return ;
1646 }
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001647 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jangf4249322014-08-21 14:17:05 +09001648 if (device == null) {
1649 Slog.w(TAG, "Invalid port id:" + portId);
1650 return;
1651 }
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001652 mMhlController.sendVendorCommand(portId, offset, length, data);
Jungshik Jangf4249322014-08-21 14:17:05 +09001653 }
1654 });
1655 }
1656
1657 @Override
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001658 public void addHdmiMhlVendorCommandListener(
1659 IHdmiMhlVendorCommandListener listener) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001660 enforceAccessPermission();
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001661 HdmiControlService.this.addHdmiMhlVendorCommandListener(listener);
Jungshik Jangf4249322014-08-21 14:17:05 +09001662 }
Terry Heo959d2db2014-08-28 16:45:41 +09001663
1664 @Override
1665 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1666 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
1667 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
1668
1669 pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
1670 pw.println("mProhibitMode: " + mProhibitMode);
1671 if (mCecController != null) {
1672 pw.println("mCecController: ");
1673 pw.increaseIndent();
1674 mCecController.dump(pw);
1675 pw.decreaseIndent();
1676 }
Jinsuk Kim61c94d12015-01-15 07:00:28 +09001677
1678 pw.println("mMhlController: ");
1679 pw.increaseIndent();
1680 mMhlController.dump(pw);
1681 pw.decreaseIndent();
1682
Terry Heo959d2db2014-08-28 16:45:41 +09001683 pw.println("mPortInfo: ");
1684 pw.increaseIndent();
1685 for (HdmiPortInfo hdmiPortInfo : mPortInfo) {
1686 pw.println("- " + hdmiPortInfo);
1687 }
1688 pw.decreaseIndent();
1689 pw.println("mPowerStatus: " + mPowerStatus);
1690 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001691 }
1692
Jungshik Janga5b74142014-06-23 18:03:10 +09001693 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +09001694 private void oneTouchPlay(final IHdmiControlCallback callback) {
1695 assertRunOnServiceThread();
1696 HdmiCecLocalDevicePlayback source = playback();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001697 if (source == null) {
1698 Slog.w(TAG, "Local playback device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001699 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001700 return;
1701 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09001702 source.oneTouchPlay(callback);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001703 }
1704
Jungshik Janga5b74142014-06-23 18:03:10 +09001705 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +09001706 private void queryDisplayStatus(final IHdmiControlCallback callback) {
1707 assertRunOnServiceThread();
1708 HdmiCecLocalDevicePlayback source = playback();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001709 if (source == null) {
1710 Slog.w(TAG, "Local playback device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001711 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001712 return;
1713 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09001714 source.queryDisplayStatus(callback);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001715 }
1716
Jinsuk Kim3cd30512014-12-04 11:05:09 +09001717 private void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
1718 final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001719 try {
1720 listener.asBinder().linkToDeath(record, 0);
1721 } catch (RemoteException e) {
1722 Slog.w(TAG, "Listener already died");
1723 return;
1724 }
1725 synchronized (mLock) {
1726 mHotplugEventListenerRecords.add(record);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001727 }
Jinsuk Kim3cd30512014-12-04 11:05:09 +09001728
1729 // Inform the listener of the initial state of each HDMI port by generating
1730 // hotplug events.
1731 runOnServiceThread(new Runnable() {
1732 @Override
1733 public void run() {
1734 synchronized (mLock) {
1735 if (!mHotplugEventListenerRecords.contains(record)) return;
1736 }
1737 for (HdmiPortInfo port : mPortInfo) {
1738 HdmiHotplugEvent event = new HdmiHotplugEvent(port.getId(),
1739 mCecController.isConnected(port.getId()));
1740 synchronized (mLock) {
1741 invokeHotplugEventListenerLocked(listener, event);
1742 }
1743 }
1744 }
1745 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001746 }
1747
1748 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
1749 synchronized (mLock) {
1750 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1751 if (record.mListener.asBinder() == listener.asBinder()) {
1752 listener.asBinder().unlinkToDeath(record, 0);
1753 mHotplugEventListenerRecords.remove(record);
1754 break;
1755 }
1756 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001757 }
1758 }
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001759
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001760 private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001761 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
1762 try {
1763 listener.asBinder().linkToDeath(record, 0);
1764 } catch (RemoteException e) {
1765 Slog.w(TAG, "Listener already died");
1766 return;
1767 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001768 synchronized (mLock) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001769 mDeviceEventListenerRecords.add(record);
1770 }
1771 }
1772
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001773 void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001774 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001775 for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001776 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09001777 record.mListener.onStatusChanged(device, status);
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001778 } catch (RemoteException e) {
1779 Slog.e(TAG, "Failed to report device event:" + e);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001780 }
1781 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001782 }
1783 }
1784
Jungshik Jangea67c182014-06-19 22:19:20 +09001785 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
1786 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
1787 listener);
1788 try {
1789 listener.asBinder().linkToDeath(record, 0);
1790 } catch (RemoteException e) {
1791 Slog.w(TAG, "Listener already died");
1792 return;
1793 }
1794 synchronized (mLock) {
Jungshik Jangea67c182014-06-19 22:19:20 +09001795 mSystemAudioModeChangeListenerRecords.add(record);
1796 }
1797 }
1798
1799 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
1800 synchronized (mLock) {
1801 for (SystemAudioModeChangeListenerRecord record :
1802 mSystemAudioModeChangeListenerRecords) {
1803 if (record.mListener.asBinder() == listener) {
1804 listener.asBinder().unlinkToDeath(record, 0);
1805 mSystemAudioModeChangeListenerRecords.remove(record);
1806 break;
1807 }
1808 }
Jungshik Jangea67c182014-06-19 22:19:20 +09001809 }
1810 }
1811
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001812 private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
Jungshik Jangf4249322014-08-21 14:17:05 +09001813 private final IHdmiInputChangeListener mListener;
1814
1815 public InputChangeListenerRecord(IHdmiInputChangeListener listener) {
1816 mListener = listener;
1817 }
1818
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001819 @Override
1820 public void binderDied() {
1821 synchronized (mLock) {
Donghyun Chofbbeb3e2016-04-15 09:12:03 +09001822 if (mInputChangeListenerRecord == this) {
1823 mInputChangeListenerRecord = null;
1824 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001825 }
1826 }
1827 }
1828
1829 private void setInputChangeListener(IHdmiInputChangeListener listener) {
1830 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001831 mInputChangeListenerRecord = new InputChangeListenerRecord(listener);
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001832 try {
1833 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
1834 } catch (RemoteException e) {
1835 Slog.w(TAG, "Listener already died");
1836 return;
1837 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001838 }
1839 }
1840
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001841 void invokeInputChangeListener(HdmiDeviceInfo info) {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001842 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001843 if (mInputChangeListenerRecord != null) {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001844 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09001845 mInputChangeListenerRecord.mListener.onChanged(info);
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001846 } catch (RemoteException e) {
1847 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
1848 }
1849 }
1850 }
1851 }
1852
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001853 private void setHdmiRecordListener(IHdmiRecordListener listener) {
Jungshik Jangb6591b82014-07-23 16:10:23 +09001854 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001855 mRecordListenerRecord = new HdmiRecordListenerRecord(listener);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001856 try {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001857 listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001858 } catch (RemoteException e) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001859 Slog.w(TAG, "Listener already died.", e);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001860 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09001861 }
1862 }
1863
1864 byte[] invokeRecordRequestListener(int recorderAddress) {
1865 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001866 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001867 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09001868 return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001869 } catch (RemoteException e) {
1870 Slog.w(TAG, "Failed to start record.", e);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001871 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09001872 }
1873 return EmptyArray.BYTE;
1874 }
1875 }
1876
Jungshik Jang326aef02014-11-05 12:50:35 +09001877 void invokeOneTouchRecordResult(int recorderAddress, int result) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001878 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001879 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001880 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09001881 mRecordListenerRecord.mListener.onOneTouchRecordResult(recorderAddress, result);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001882 } catch (RemoteException e) {
1883 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
1884 }
1885 }
1886 }
1887 }
1888
Jungshik Jang326aef02014-11-05 12:50:35 +09001889 void invokeTimerRecordingResult(int recorderAddress, int result) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001890 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001891 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001892 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09001893 mRecordListenerRecord.mListener.onTimerRecordingResult(recorderAddress, result);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001894 } catch (RemoteException e) {
Jungshik Jange5a93372014-07-25 13:41:14 +09001895 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
1896 }
1897 }
1898 }
1899 }
1900
Jungshik Jang326aef02014-11-05 12:50:35 +09001901 void invokeClearTimerRecordingResult(int recorderAddress, int result) {
Jungshik Jange5a93372014-07-25 13:41:14 +09001902 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001903 if (mRecordListenerRecord != null) {
Jungshik Jange5a93372014-07-25 13:41:14 +09001904 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09001905 mRecordListenerRecord.mListener.onClearTimerRecordingResult(recorderAddress,
1906 result);
Jungshik Jange5a93372014-07-25 13:41:14 +09001907 } catch (RemoteException e) {
1908 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001909 }
1910 }
1911 }
1912 }
1913
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001914 private void invokeCallback(IHdmiControlCallback callback, int result) {
1915 try {
1916 callback.onComplete(result);
1917 } catch (RemoteException e) {
1918 Slog.e(TAG, "Invoking callback failed:" + e);
1919 }
1920 }
Yuncheol Heo63a2e062014-05-27 23:06:01 +09001921
Jungshik Jangf4249322014-08-21 14:17:05 +09001922 private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener,
Jungshik Jangea67c182014-06-19 22:19:20 +09001923 boolean enabled) {
1924 try {
1925 listener.onStatusChanged(enabled);
1926 } catch (RemoteException e) {
1927 Slog.e(TAG, "Invoking callback failed:" + e);
1928 }
1929 }
1930
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001931 private void announceHotplugEvent(int portId, boolean connected) {
1932 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
Jungshik Jang60cffce2014-06-12 18:03:04 +09001933 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001934 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1935 invokeHotplugEventListenerLocked(record.mListener, event);
Jungshik Jang60cffce2014-06-12 18:03:04 +09001936 }
1937 }
1938 }
1939
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001940 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
Jungshik Jang60cffce2014-06-12 18:03:04 +09001941 HdmiHotplugEvent event) {
1942 try {
1943 listener.onReceived(event);
1944 } catch (RemoteException e) {
1945 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
1946 }
Jungshik Jange81e1082014-06-05 15:37:59 +09001947 }
1948
Jinsuk Kimf98b9e82015-10-05 14:24:48 +09001949 public HdmiCecLocalDeviceTv tv() {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001950 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
Jungshik Jang79c58a42014-06-16 16:45:36 +09001951 }
1952
Yuncheol Heoe946ed82014-07-25 14:05:19 +09001953 boolean isTvDevice() {
Yuncheol Heob8d62e72014-09-22 19:53:41 +09001954 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
Yuncheol Heoe946ed82014-07-25 14:05:19 +09001955 }
1956
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001957 boolean isTvDeviceEnabled() {
1958 return isTvDevice() && tv() != null;
1959 }
1960
Jungshik Jang79c58a42014-06-16 16:45:36 +09001961 private HdmiCecLocalDevicePlayback playback() {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001962 return (HdmiCecLocalDevicePlayback)
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001963 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
Jungshik Jang60cffce2014-06-12 18:03:04 +09001964 }
Jungshik Janga858d222014-06-23 17:17:47 +09001965
1966 AudioManager getAudioManager() {
1967 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
1968 }
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001969
1970 boolean isControlEnabled() {
1971 synchronized (mLock) {
1972 return mHdmiControlEnabled;
1973 }
1974 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09001975
Jungshik Jangf67113f2014-08-22 16:27:19 +09001976 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09001977 int getPowerStatus() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09001978 assertRunOnServiceThread();
Yuncheol Heo38db6292014-07-01 14:15:14 +09001979 return mPowerStatus;
1980 }
1981
Jungshik Jangf67113f2014-08-22 16:27:19 +09001982 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09001983 boolean isPowerOnOrTransient() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09001984 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001985 return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
1986 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Yuncheol Heo38db6292014-07-01 14:15:14 +09001987 }
1988
Jungshik Jangf67113f2014-08-22 16:27:19 +09001989 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09001990 boolean isPowerStandbyOrTransient() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09001991 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001992 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
1993 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09001994 }
1995
Jungshik Jangf67113f2014-08-22 16:27:19 +09001996 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09001997 boolean isPowerStandby() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09001998 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001999 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002000 }
2001
2002 @ServiceThreadOnly
2003 void wakeUp() {
2004 assertRunOnServiceThread();
Yuncheol Heofc44e4e2014-08-04 19:41:09 +09002005 mWakeUpMessageReceived = true;
Dianne Hackborn280a64e2015-07-13 14:48:08 -07002006 mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.server.hdmi:WAKE");
Yuncheol Heo38db6292014-07-01 14:15:14 +09002007 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
2008 // the intent, the sequence will continue at onWakeUp().
2009 }
2010
2011 @ServiceThreadOnly
2012 void standby() {
2013 assertRunOnServiceThread();
2014 mStandbyMessageReceived = true;
Jinsuk Kime26d8332015-01-09 08:55:41 +09002015 mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
Yuncheol Heo38db6292014-07-01 14:15:14 +09002016 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
2017 // the intent, the sequence will continue at onStandby().
2018 }
2019
2020 @ServiceThreadOnly
2021 private void onWakeUp() {
2022 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002023 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002024 if (mCecController != null) {
Jungshik Janga9f10622014-07-11 15:36:39 +09002025 if (mHdmiControlEnabled) {
Yuncheol Heofc44e4e2014-08-04 19:41:09 +09002026 int startReason = INITIATED_BY_SCREEN_ON;
2027 if (mWakeUpMessageReceived) {
2028 startReason = INITIATED_BY_WAKE_UP_MESSAGE;
2029 }
2030 initializeCec(startReason);
Jungshik Janga9f10622014-07-11 15:36:39 +09002031 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002032 } else {
2033 Slog.i(TAG, "Device does not support HDMI-CEC.");
2034 }
2035 // TODO: Initialize MHL local devices.
2036 }
2037
2038 @ServiceThreadOnly
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +09002039 private void onStandby(final int standbyAction) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002040 assertRunOnServiceThread();
Jinsuk Kime26d8332015-01-09 08:55:41 +09002041 if (!canGoToStandby()) return;
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002042 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
Yuncheol Heo0608b932014-10-13 16:39:18 +09002043 invokeVendorCommandListenersOnControlStateChanged(false,
2044 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002045
2046 final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
2047 disableDevices(new PendingActionClearedCallback() {
2048 @Override
2049 public void onCleared(HdmiCecLocalDevice device) {
2050 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
2051 devices.remove(device);
2052 if (devices.isEmpty()) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +09002053 onStandbyCompleted(standbyAction);
Yuncheol Heo4b542712014-07-30 20:31:06 +09002054 // We will not clear local devices here, since some OEM/SOC will keep passing
2055 // the received packets until the application processor enters to the sleep
2056 // actually.
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002057 }
2058 }
2059 });
2060 }
2061
Jinsuk Kime26d8332015-01-09 08:55:41 +09002062 private boolean canGoToStandby() {
2063 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2064 if (!device.canGoToStandby()) return false;
2065 }
2066 return true;
2067 }
2068
Terry Heo1ca0a432014-08-18 10:30:32 +09002069 @ServiceThreadOnly
2070 private void onLanguageChanged(String language) {
2071 assertRunOnServiceThread();
2072 mLanguage = language;
2073
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002074 if (isTvDeviceEnabled()) {
Terry Heo1ca0a432014-08-18 10:30:32 +09002075 tv().broadcastMenuLanguage(language);
Jinsuk Kim5b8cb002015-01-19 07:30:12 +09002076 mCecController.setOption(OPTION_CEC_SET_LANGUAGE, HdmiUtils.languageToInt(language));
Terry Heo1ca0a432014-08-18 10:30:32 +09002077 }
2078 }
2079
Jungshik Jangf67113f2014-08-22 16:27:19 +09002080 @ServiceThreadOnly
2081 String getLanguage() {
2082 assertRunOnServiceThread();
2083 return mLanguage;
2084 }
2085
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002086 private void disableDevices(PendingActionClearedCallback callback) {
Jungshik Jang350e68d2014-08-19 18:56:21 +09002087 if (mCecController != null) {
2088 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2089 device.disableDevice(mStandbyMessageReceived, callback);
2090 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002091 }
Jungshik Jang350e68d2014-08-19 18:56:21 +09002092
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09002093 mMhlController.clearAllLocalDevices();
Yuncheol Heo38db6292014-07-01 14:15:14 +09002094 }
2095
Yuncheol Heo38db6292014-07-01 14:15:14 +09002096 @ServiceThreadOnly
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002097 private void clearLocalDevices() {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002098 assertRunOnServiceThread();
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002099 if (mCecController == null) {
2100 return;
2101 }
2102 mCecController.clearLogicalAddress();
2103 mCecController.clearLocalDevices();
2104 }
2105
2106 @ServiceThreadOnly
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +09002107 private void onStandbyCompleted(int standbyAction) {
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002108 assertRunOnServiceThread();
2109 Slog.v(TAG, "onStandbyCompleted");
2110
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002111 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002112 return;
2113 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002114 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002115 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +09002116 device.onStandby(mStandbyMessageReceived, standbyAction);
Yuncheol Heo38db6292014-07-01 14:15:14 +09002117 }
2118 mStandbyMessageReceived = false;
Jinsuk Kim964c00d2015-01-16 15:20:17 +09002119 mAddressAllocated = false;
Jinsuk Kim50084862014-08-07 13:11:40 +09002120 mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, DISABLED);
Jinsuk Kim5b8cb002015-01-19 07:30:12 +09002121 mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
Yuncheol Heo38db6292014-07-01 14:15:14 +09002122 }
Jinsuk Kim4d43d932014-07-03 16:43:58 +09002123
Jinsuk Kim119160a2014-07-07 18:48:10 +09002124 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
2125 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
2126 try {
2127 listener.asBinder().linkToDeath(record, 0);
2128 } catch (RemoteException e) {
2129 Slog.w(TAG, "Listener already died");
2130 return;
2131 }
2132 synchronized (mLock) {
2133 mVendorCommandListenerRecords.add(record);
2134 }
2135 }
2136
Yuncheol Heo0608b932014-10-13 16:39:18 +09002137 boolean invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress,
2138 byte[] params, boolean hasVendorId) {
Jinsuk Kim119160a2014-07-07 18:48:10 +09002139 synchronized (mLock) {
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09002140 if (mVendorCommandListenerRecords.isEmpty()) {
2141 return false;
2142 }
Jinsuk Kim119160a2014-07-07 18:48:10 +09002143 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2144 if (record.mDeviceType != deviceType) {
2145 continue;
2146 }
2147 try {
Yuncheol Heo0608b932014-10-13 16:39:18 +09002148 record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId);
Jinsuk Kim119160a2014-07-07 18:48:10 +09002149 } catch (RemoteException e) {
2150 Slog.e(TAG, "Failed to notify vendor command reception", e);
2151 }
2152 }
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09002153 return true;
Jinsuk Kim119160a2014-07-07 18:48:10 +09002154 }
2155 }
2156
Yuncheol Heo0608b932014-10-13 16:39:18 +09002157 boolean invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason) {
2158 synchronized (mLock) {
2159 if (mVendorCommandListenerRecords.isEmpty()) {
2160 return false;
2161 }
2162 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2163 try {
2164 record.mListener.onControlStateChanged(enabled, reason);
2165 } catch (RemoteException e) {
2166 Slog.e(TAG, "Failed to notify control-state-changed to vendor handler", e);
2167 }
2168 }
2169 return true;
2170 }
2171 }
2172
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002173 private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {
2174 HdmiMhlVendorCommandListenerRecord record =
2175 new HdmiMhlVendorCommandListenerRecord(listener);
Jungshik Jangf4249322014-08-21 14:17:05 +09002176 try {
2177 listener.asBinder().linkToDeath(record, 0);
2178 } catch (RemoteException e) {
2179 Slog.w(TAG, "Listener already died.");
2180 return;
2181 }
2182
2183 synchronized (mLock) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002184 mMhlVendorCommandListenerRecords.add(record);
Jungshik Jangf4249322014-08-21 14:17:05 +09002185 }
2186 }
2187
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002188 void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002189 synchronized (mLock) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002190 for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002191 try {
2192 record.mListener.onReceived(portId, offest, length, data);
2193 } catch (RemoteException e) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002194 Slog.e(TAG, "Failed to notify MHL vendor command", e);
Jungshik Jangf4249322014-08-21 14:17:05 +09002195 }
2196 }
2197 }
2198 }
2199
Jinsuk Kim4d43d932014-07-03 16:43:58 +09002200 boolean isProhibitMode() {
2201 synchronized (mLock) {
2202 return mProhibitMode;
2203 }
2204 }
2205
2206 void setProhibitMode(boolean enabled) {
2207 synchronized (mLock) {
2208 mProhibitMode = enabled;
2209 }
2210 }
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002211
2212 @ServiceThreadOnly
Jungshik Jang350e68d2014-08-19 18:56:21 +09002213 void setCecOption(int key, int value) {
Jinsuk Kim50084862014-08-07 13:11:40 +09002214 assertRunOnServiceThread();
2215 mCecController.setOption(key, value);
2216 }
2217
2218 @ServiceThreadOnly
2219 void setControlEnabled(boolean enabled) {
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002220 assertRunOnServiceThread();
2221
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002222 synchronized (mLock) {
2223 mHdmiControlEnabled = enabled;
2224 }
2225
2226 if (enabled) {
Yuncheol Heof1702482014-11-27 19:52:01 +09002227 enableHdmiControlService();
2228 return;
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002229 }
Yuncheol Heof1702482014-11-27 19:52:01 +09002230 // Call the vendor handler before the service is disabled.
2231 invokeVendorCommandListenersOnControlStateChanged(false,
2232 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING);
2233 // Post the remained tasks in the service thread again to give the vendor-issued-tasks
2234 // a chance to run.
2235 runOnServiceThread(new Runnable() {
2236 @Override
2237 public void run() {
2238 disableHdmiControlService();
2239 }
2240 });
2241 return;
2242 }
2243
2244 @ServiceThreadOnly
2245 private void enableHdmiControlService() {
2246 mCecController.setOption(OPTION_CEC_ENABLE, ENABLED);
2247 mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED);
2248
2249 initializeCec(INITIATED_BY_ENABLE_CEC);
2250 }
2251
2252 @ServiceThreadOnly
2253 private void disableHdmiControlService() {
2254 disableDevices(new PendingActionClearedCallback() {
2255 @Override
2256 public void onCleared(HdmiCecLocalDevice device) {
2257 assertRunOnServiceThread();
2258 mCecController.flush(new Runnable() {
2259 @Override
2260 public void run() {
2261 mCecController.setOption(OPTION_CEC_ENABLE, DISABLED);
2262 mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED);
2263 clearLocalDevices();
2264 }
2265 });
2266 }
2267 });
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002268 }
Jungshik Jang867b4e02014-08-12 13:41:30 +09002269
2270 @ServiceThreadOnly
2271 void setActivePortId(int portId) {
2272 assertRunOnServiceThread();
2273 mActivePortId = portId;
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002274
2275 // Resets last input for MHL, which stays valid only after the MHL device was selected,
2276 // and no further switching is done.
2277 setLastInputForMhl(Constants.INVALID_PORT_ID);
Jungshik Jang867b4e02014-08-12 13:41:30 +09002278 }
Yuncheol Heo08a1be82014-08-12 20:58:41 +09002279
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002280 @ServiceThreadOnly
2281 void setLastInputForMhl(int portId) {
2282 assertRunOnServiceThread();
2283 mLastInputMhl = portId;
2284 }
2285
2286 @ServiceThreadOnly
2287 int getLastInputForMhl() {
2288 assertRunOnServiceThread();
2289 return mLastInputMhl;
2290 }
2291
2292 /**
2293 * Performs input change, routing control for MHL device.
2294 *
2295 * @param portId MHL port, or the last port to go back to if {@code contentOn} is false
2296 * @param contentOn {@code true} if RAP data is content on; otherwise false
2297 */
2298 @ServiceThreadOnly
2299 void changeInputForMhl(int portId, boolean contentOn) {
2300 assertRunOnServiceThread();
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002301 if (tv() == null) return;
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002302 final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID;
Jinsuk Kimcb8661c2015-01-19 12:39:06 +09002303 if (portId != Constants.INVALID_PORT_ID) {
2304 tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() {
2305 @Override
2306 public void onComplete(int result) throws RemoteException {
2307 // Keep the last input to switch back later when RAP[ContentOff] is received.
2308 // This effectively sets the port to invalid one if the switching is for
2309 // RAP[ContentOff].
2310 setLastInputForMhl(lastInput);
2311 }
2312 });
2313 }
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002314 // MHL device is always directly connected to the port. Update the active port ID to avoid
2315 // unnecessary post-routing control task.
2316 tv().setActivePortId(portId);
2317
2318 // The port is either the MHL-enabled port where the mobile device is connected, or
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09002319 // the last port to go back to when turnoff command is received. Note that the last port
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002320 // may not be the MHL-enabled one. In this case the device info to be passed to
2321 // input change listener should be the one describing the corresponding HDMI port.
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09002322 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jinsuk Kimcb8661c2015-01-19 12:39:06 +09002323 HdmiDeviceInfo info = (device != null) ? device.getInfo()
2324 : mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002325 invokeInputChangeListener(info);
2326 }
2327
2328 void setMhlInputChangeEnabled(boolean enabled) {
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09002329 mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
Yuncheol Heo08a1be82014-08-12 20:58:41 +09002330
2331 synchronized (mLock) {
2332 mMhlInputChangeEnabled = enabled;
2333 }
2334 }
2335
2336 boolean isMhlInputChangeEnabled() {
2337 synchronized (mLock) {
2338 return mMhlInputChangeEnabled;
2339 }
2340 }
Jungshik Jang339227d2014-08-25 15:37:20 +09002341
2342 @ServiceThreadOnly
2343 void displayOsd(int messageId) {
2344 assertRunOnServiceThread();
2345 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2346 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2347 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2348 HdmiControlService.PERMISSION);
2349 }
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09002350
2351 @ServiceThreadOnly
2352 void displayOsd(int messageId, int extra) {
2353 assertRunOnServiceThread();
2354 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2355 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
Yuncheol Heo2b0da5c2014-10-22 14:32:27 +09002356 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra);
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09002357 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2358 HdmiControlService.PERMISSION);
2359 }
Jungshik Jang0792d372014-04-23 17:57:26 +09002360}