blob: 807b1b19f870541266e63f05b05fb2a71f8b0cec [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_MHL_ENABLE;
24import static com.android.server.hdmi.Constants.OPTION_MHL_INPUT_SWITCHING;
25import static com.android.server.hdmi.Constants.OPTION_MHL_POWER_CHARGE;
Jinsuk Kim5b8cb002015-01-19 07:30:12 +090026import static com.android.server.hdmi.Constants.OPTION_MHL_SERVICE_CONTROL;
Jinsuk Kim50084862014-08-07 13:11:40 +090027
Jungshik Jang0792d372014-04-23 17:57:26 +090028import android.annotation.Nullable;
Yuncheol Heo38db6292014-07-01 14:15:14 +090029import android.content.BroadcastReceiver;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +090030import android.content.ContentResolver;
Jungshik Jang0792d372014-04-23 17:57:26 +090031import android.content.Context;
Yuncheol Heo38db6292014-07-01 14:15:14 +090032import android.content.Intent;
33import android.content.IntentFilter;
Jinsuk Kim50084862014-08-07 13:11:40 +090034import android.database.ContentObserver;
Jinsuk Kimc0c20d02014-07-04 14:34:31 +090035import android.hardware.hdmi.HdmiControlManager;
Yuncheol Heo7d9acc72014-08-12 15:30:49 +090036import android.hardware.hdmi.HdmiDeviceInfo;
Jungshik Jang60cffce2014-06-12 18:03:04 +090037import android.hardware.hdmi.HdmiHotplugEvent;
Jinsuk Kim0340bbc2014-06-05 11:07:47 +090038import android.hardware.hdmi.HdmiPortInfo;
Jungshik Jangd643f762014-05-22 19:28:09 +090039import android.hardware.hdmi.IHdmiControlCallback;
40import android.hardware.hdmi.IHdmiControlService;
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +090041import android.hardware.hdmi.IHdmiDeviceEventListener;
Jungshik Jangd643f762014-05-22 19:28:09 +090042import android.hardware.hdmi.IHdmiHotplugEventListener;
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +090043import android.hardware.hdmi.IHdmiInputChangeListener;
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +090044import android.hardware.hdmi.IHdmiMhlVendorCommandListener;
Jungshik Jang12e5dce2014-07-24 15:27:44 +090045import android.hardware.hdmi.IHdmiRecordListener;
Jungshik Jangea67c182014-06-19 22:19:20 +090046import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
Jinsuk Kim119160a2014-07-07 18:48:10 +090047import android.hardware.hdmi.IHdmiVendorCommandListener;
Donghyun Chobc6e3722016-11-04 05:25:52 +090048import android.hardware.tv.cec.V1_0.OptionKey;
49import android.hardware.tv.cec.V1_0.SendMessageResult;
Jungshik Janga858d222014-06-23 17:17:47 +090050import android.media.AudioManager;
Jinsuk Kim7fa3a662014-11-07 15:20:24 +090051import android.media.tv.TvInputManager;
52import android.media.tv.TvInputManager.TvInputCallback;
Jinsuk Kim50084862014-08-07 13:11:40 +090053import android.net.Uri;
Jungshik Jang42c98002014-06-12 13:17:44 +090054import android.os.Build;
Jungshik Jang67ea5212014-05-15 14:05:24 +090055import android.os.Handler;
Jungshik Jang0792d372014-04-23 17:57:26 +090056import android.os.HandlerThread;
Jinsuk Kim78d695d2014-05-13 16:36:15 +090057import android.os.IBinder;
Jungshik Jange9c77c82014-04-24 20:30:09 +090058import android.os.Looper;
Yuncheol Heo38db6292014-07-01 14:15:14 +090059import android.os.PowerManager;
Jinsuk Kim78d695d2014-05-13 16:36:15 +090060import android.os.RemoteException;
Yuncheol Heo38db6292014-07-01 14:15:14 +090061import android.os.SystemClock;
Yuncheol Heo7d9acc72014-08-12 15:30:49 +090062import android.os.SystemProperties;
Jinsuk Kim50084862014-08-07 13:11:40 +090063import android.os.UserHandle;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +090064import android.provider.Settings.Global;
Yuncheol Heo7d9acc72014-08-12 15:30:49 +090065import android.text.TextUtils;
Jinsuk Kim2b152012014-07-25 08:22:26 +090066import android.util.ArraySet;
Jungshik Jang0792d372014-04-23 17:57:26 +090067import android.util.Slog;
Jungshik Jang3ee65722014-06-03 16:22:30 +090068import android.util.SparseArray;
Jungshik Jang8b308d92014-05-29 21:52:28 +090069import android.util.SparseIntArray;
Jinsuk Kim4893c7e2014-06-19 14:13:22 +090070import com.android.internal.annotations.GuardedBy;
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060071import com.android.internal.util.DumpUtils;
Terry Heo959d2db2014-08-28 16:45:41 +090072import com.android.internal.util.IndentingPrintWriter;
Jungshik Jang0792d372014-04-23 17:57:26 +090073import com.android.server.SystemService;
Jungshik Janga5b74142014-06-23 18:03:10 +090074import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
Jungshik Jang3ee65722014-06-03 16:22:30 +090075import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
Jinsuk Kim7e742062014-07-30 13:19:13 +090076import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
Jungshik Jang4fc1d102014-07-09 19:24:50 +090077import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback;
Terry Heo959d2db2014-08-28 16:45:41 +090078import java.io.FileDescriptor;
79import java.io.PrintWriter;
Jinsuk Kim78d695d2014-05-13 16:36:15 +090080import java.util.ArrayList;
Jinsuk Kimf4eb72d2014-07-25 13:02:51 +090081import java.util.Arrays;
Jinsuk Kim0340bbc2014-06-05 11:07:47 +090082import java.util.Collections;
Jungshik Jang02bb4262014-05-23 16:48:31 +090083import java.util.List;
Terry Heo1ca0a432014-08-18 10:30:32 +090084import java.util.Locale;
Donghyun Chobc6e3722016-11-04 05:25:52 +090085import libcore.util.EmptyArray;
Jungshik Janga1fa91f2014-05-08 20:56:41 +090086
Jungshik Jang0792d372014-04-23 17:57:26 +090087/**
88 * Provides a service for sending and processing HDMI control messages,
89 * HDMI-CEC and MHL control command, and providing the information on both standard.
90 */
91public final class HdmiControlService extends SystemService {
92 private static final String TAG = "HdmiControlService";
Jinsuk Kim5fe3a6c2014-10-29 07:02:06 +090093 private final Locale HONG_KONG = new Locale("zh", "HK");
94 private final Locale MACAU = new Locale("zh", "MO");
Jungshik Jang0792d372014-04-23 17:57:26 +090095
Jinsuk Kimc7eba0f2014-07-07 14:18:02 +090096 static final String PERMISSION = "android.permission.HDMI_CEC";
Jinsuk Kim78d695d2014-05-13 16:36:15 +090097
Yuncheol Heofc44e4e2014-08-04 19:41:09 +090098 // The reason code to initiate intializeCec().
99 static final int INITIATED_BY_ENABLE_CEC = 0;
100 static final int INITIATED_BY_BOOT_UP = 1;
101 static final int INITIATED_BY_SCREEN_ON = 2;
102 static final int INITIATED_BY_WAKE_UP_MESSAGE = 3;
Yuncheol Heob5021862014-09-02 10:36:04 +0900103 static final int INITIATED_BY_HOTPLUG = 4;
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900104
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900105 // The reason code representing the intent action that drives the standby
106 // procedure. The procedure starts either by Intent.ACTION_SCREEN_OFF or
107 // Intent.ACTION_SHUTDOWN.
108 static final int STANDBY_SCREEN_OFF = 0;
109 static final int STANDBY_SHUTDOWN = 1;
110
Jungshik Jangd643f762014-05-22 19:28:09 +0900111 /**
112 * Interface to report send result.
113 */
114 interface SendMessageCallback {
115 /**
116 * Called when {@link HdmiControlService#sendCecCommand} is completed.
117 *
Yuncheol Heoece603b2014-05-23 20:10:19 +0900118 * @param error result of send request.
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900119 * <ul>
Donghyun Chobc6e3722016-11-04 05:25:52 +0900120 * <li>{@link SendMessageResult#SUCCESS}
121 * <li>{@link SendMessageResult#NACK}
122 * <li>{@link SendMessageResult#BUSY}
123 * <li>{@link SendMessageResult#FAIL}
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900124 * </ul>
Jungshik Jangd643f762014-05-22 19:28:09 +0900125 */
Jungshik Jangd643f762014-05-22 19:28:09 +0900126 void onSendCompleted(int error);
127 }
128
Jungshik Jang02bb4262014-05-23 16:48:31 +0900129 /**
130 * Interface to get a list of available logical devices.
131 */
132 interface DevicePollingCallback {
133 /**
134 * Called when device polling is finished.
135 *
136 * @param ackedAddress a list of logical addresses of available devices
137 */
138 void onPollingFinished(List<Integer> ackedAddress);
139 }
140
Terry Heo1ca0a432014-08-18 10:30:32 +0900141 private class HdmiControlBroadcastReceiver extends BroadcastReceiver {
Jungshik Jangf67113f2014-08-22 16:27:19 +0900142 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +0900143 @Override
144 public void onReceive(Context context, Intent intent) {
Jungshik Jangf67113f2014-08-22 16:27:19 +0900145 assertRunOnServiceThread();
Yuncheol Heo38db6292014-07-01 14:15:14 +0900146 switch (intent.getAction()) {
147 case Intent.ACTION_SCREEN_OFF:
148 if (isPowerOnOrTransient()) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900149 onStandby(STANDBY_SCREEN_OFF);
Yuncheol Heo38db6292014-07-01 14:15:14 +0900150 }
151 break;
152 case Intent.ACTION_SCREEN_ON:
153 if (isPowerStandbyOrTransient()) {
154 onWakeUp();
155 }
156 break;
Terry Heo1ca0a432014-08-18 10:30:32 +0900157 case Intent.ACTION_CONFIGURATION_CHANGED:
Jinsuk Kim5fe3a6c2014-10-29 07:02:06 +0900158 String language = getMenuLanguage();
Terry Heo1ca0a432014-08-18 10:30:32 +0900159 if (!mLanguage.equals(language)) {
160 onLanguageChanged(language);
161 }
162 break;
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900163 case Intent.ACTION_SHUTDOWN:
164 if (isPowerOnOrTransient()) {
165 onStandby(STANDBY_SHUTDOWN);
166 }
167 break;
Yuncheol Heo38db6292014-07-01 14:15:14 +0900168 }
169 }
Jinsuk Kim5fe3a6c2014-10-29 07:02:06 +0900170
171 private String getMenuLanguage() {
172 Locale locale = Locale.getDefault();
173 if (locale.equals(Locale.TAIWAN) || locale.equals(HONG_KONG) || locale.equals(MACAU)) {
174 // Android always returns "zho" for all Chinese variants.
175 // Use "bibliographic" code defined in CEC639-2 for traditional
176 // Chinese used in Taiwan/Hong Kong/Macau.
177 return "chi";
178 } else {
179 return locale.getISO3Language();
180 }
181 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900182 }
183
Jungshik Jang0792d372014-04-23 17:57:26 +0900184 // A thread to handle synchronous IO of CEC and MHL control service.
185 // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
186 // and sparse call it shares a thread to handle IO operations.
187 private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
188
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900189 // Used to synchronize the access to the service.
190 private final Object mLock = new Object();
191
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900192 // Type of logical devices hosted in the system. Stored in the unmodifiable list.
193 private final List<Integer> mLocalDevices;
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900194
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900195 // List of records for hotplug event listener to handle the the caller killed in action.
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900196 @GuardedBy("mLock")
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900197 private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
198 new ArrayList<>();
199
Jungshik Jangf4249322014-08-21 14:17:05 +0900200 // List of records for device event listener to handle the caller killed in action.
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900201 @GuardedBy("mLock")
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +0900202 private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
203 new ArrayList<>();
204
Jungshik Jangf4249322014-08-21 14:17:05 +0900205 // List of records for vendor command listener to handle the caller killed in action.
Jinsuk Kim119160a2014-07-07 18:48:10 +0900206 @GuardedBy("mLock")
207 private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords =
208 new ArrayList<>();
209
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +0900210 @GuardedBy("mLock")
211 private InputChangeListenerRecord mInputChangeListenerRecord;
212
Jungshik Jangb6591b82014-07-23 16:10:23 +0900213 @GuardedBy("mLock")
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900214 private HdmiRecordListenerRecord mRecordListenerRecord;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900215
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900216 // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
217 // handling will be disabled and no request will be handled.
218 @GuardedBy("mLock")
219 private boolean mHdmiControlEnabled;
220
Jinsuk Kim4d43d932014-07-03 16:43:58 +0900221 // Set to true while the service is in normal mode. While set to false, no input change is
222 // allowed. Used for situations where input change can confuse users such as channel auto-scan,
223 // system upgrade, etc., a.k.a. "prohibit mode".
224 @GuardedBy("mLock")
225 private boolean mProhibitMode;
226
Jungshik Jangea67c182014-06-19 22:19:20 +0900227 // List of records for system audio mode change to handle the the caller killed in action.
228 private final ArrayList<SystemAudioModeChangeListenerRecord>
229 mSystemAudioModeChangeListenerRecords = new ArrayList<>();
230
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900231 // Handler used to run a task in service thread.
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900232 private final Handler mHandler = new Handler();
233
Jinsuk Kim50084862014-08-07 13:11:40 +0900234 private final SettingsObserver mSettingsObserver;
235
Jungshik Jangf4249322014-08-21 14:17:05 +0900236 private final HdmiControlBroadcastReceiver
237 mHdmiControlBroadcastReceiver = new HdmiControlBroadcastReceiver();
238
Jungshik Jang0792d372014-04-23 17:57:26 +0900239 @Nullable
240 private HdmiCecController mCecController;
241
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900242 // HDMI port information. Stored in the unmodifiable list to keep the static information
243 // from being modified.
244 private List<HdmiPortInfo> mPortInfo;
245
Jinsuk Kim2b152012014-07-25 08:22:26 +0900246 // Map from path(physical address) to port ID.
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900247 private UnmodifiableSparseIntArray mPortIdMap;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900248
249 // Map from port ID to HdmiPortInfo.
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900250 private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900251
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900252 // Map from port ID to HdmiDeviceInfo.
253 private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap;
254
Yuncheol Heo75a77e72014-07-09 18:27:53 +0900255 private HdmiCecMessageValidator mMessageValidator;
256
Yuncheol Heo38db6292014-07-01 14:15:14 +0900257 @ServiceThreadOnly
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900258 private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +0900259
260 @ServiceThreadOnly
Terry Heo1ca0a432014-08-18 10:30:32 +0900261 private String mLanguage = Locale.getDefault().getISO3Language();
262
263 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +0900264 private boolean mStandbyMessageReceived = false;
265
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900266 @ServiceThreadOnly
267 private boolean mWakeUpMessageReceived = false;
268
Jungshik Jang867b4e02014-08-12 13:41:30 +0900269 @ServiceThreadOnly
270 private int mActivePortId = Constants.INVALID_PORT_ID;
271
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900272 // Set to true while the input change by MHL is allowed.
273 @GuardedBy("mLock")
274 private boolean mMhlInputChangeEnabled;
275
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +0900276 // List of records for MHL Vendor command listener to handle the caller killed in action.
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900277 @GuardedBy("mLock")
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +0900278 private final ArrayList<HdmiMhlVendorCommandListenerRecord>
279 mMhlVendorCommandListenerRecords = new ArrayList<>();
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900280
281 @GuardedBy("mLock")
282 private List<HdmiDeviceInfo> mMhlDevices;
283
284 @Nullable
Jinsuk Kim78104122014-08-26 19:32:34 +0900285 private HdmiMhlControllerStub mMhlController;
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900286
Jinsuk Kim7fa3a662014-11-07 15:20:24 +0900287 @Nullable
288 private TvInputManager mTvInputManager;
289
Jinsuk Kime26d8332015-01-09 08:55:41 +0900290 @Nullable
291 private PowerManager mPowerManager;
292
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900293 // Last input port before switching to the MHL port. Should switch back to this port
294 // when the mobile device sends the request one touch play with off.
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900295 // Gets invalidated if we go to other port/input.
296 @ServiceThreadOnly
297 private int mLastInputMhl = Constants.INVALID_PORT_ID;
298
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900299 // Set to true if the logical address allocation is completed.
300 private boolean mAddressAllocated = false;
301
302 // Buffer for processing the incoming cec messages while allocating logical addresses.
303 private final class CecMessageBuffer {
304 private List<HdmiCecMessage> mBuffer = new ArrayList<>();
305
306 public void bufferMessage(HdmiCecMessage message) {
307 switch (message.getOpcode()) {
308 case Constants.MESSAGE_ACTIVE_SOURCE:
309 bufferActiveSource(message);
310 break;
311 case Constants.MESSAGE_IMAGE_VIEW_ON:
312 case Constants.MESSAGE_TEXT_VIEW_ON:
313 bufferImageOrTextViewOn(message);
314 break;
315 // Add here if new message that needs to buffer
316 default:
317 // Do not need to buffer messages other than above
318 break;
319 }
320 }
321
322 public void processMessages() {
323 for (final HdmiCecMessage message : mBuffer) {
324 runOnServiceThread(new Runnable() {
325 @Override
326 public void run() {
327 handleCecCommand(message);
328 }
329 });
330 }
331 mBuffer.clear();
332 }
333
334 private void bufferActiveSource(HdmiCecMessage message) {
335 if (!replaceMessageIfBuffered(message, Constants.MESSAGE_ACTIVE_SOURCE)) {
336 mBuffer.add(message);
337 }
338 }
339
340 private void bufferImageOrTextViewOn(HdmiCecMessage message) {
341 if (!replaceMessageIfBuffered(message, Constants.MESSAGE_IMAGE_VIEW_ON) &&
342 !replaceMessageIfBuffered(message, Constants.MESSAGE_TEXT_VIEW_ON)) {
343 mBuffer.add(message);
344 }
345 }
346
347 // Returns true if the message is replaced
348 private boolean replaceMessageIfBuffered(HdmiCecMessage message, int opcode) {
349 for (int i = 0; i < mBuffer.size(); i++) {
350 HdmiCecMessage bufferedMessage = mBuffer.get(i);
351 if (bufferedMessage.getOpcode() == opcode) {
352 mBuffer.set(i, message);
353 return true;
354 }
355 }
356 return false;
357 }
358 }
359
Jinsuk Kimf98b9e82015-10-05 14:24:48 +0900360 private final CecMessageBuffer mCecMessageBuffer = new CecMessageBuffer();
361
362 private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer();
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900363
Jungshik Jang0792d372014-04-23 17:57:26 +0900364 public HdmiControlService(Context context) {
365 super(context);
Yuncheol Heo7d9acc72014-08-12 15:30:49 +0900366 mLocalDevices = getIntList(SystemProperties.get(Constants.PROPERTY_DEVICE_TYPE));
Jinsuk Kim50084862014-08-07 13:11:40 +0900367 mSettingsObserver = new SettingsObserver(mHandler);
Jungshik Jang0792d372014-04-23 17:57:26 +0900368 }
369
Yuncheol Heo7d9acc72014-08-12 15:30:49 +0900370 private static List<Integer> getIntList(String string) {
371 ArrayList<Integer> list = new ArrayList<>();
372 TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
373 splitter.setString(string);
374 for (String item : splitter) {
375 try {
376 list.add(Integer.parseInt(item));
377 } catch (NumberFormatException e) {
378 Slog.w(TAG, "Can't parseInt: " + item);
379 }
380 }
381 return Collections.unmodifiableList(list);
382 }
383
Jungshik Jang0792d372014-04-23 17:57:26 +0900384 @Override
385 public void onStart() {
Jungshik Jang2f51aec2014-05-20 14:37:38 +0900386 mIoThread.start();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900387 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900388 mProhibitMode = false;
389 mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
Yuncheol Heo08a1be82014-08-12 20:58:41 +0900390 mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900391
Jungshik Janga9f10622014-07-11 15:36:39 +0900392 mCecController = HdmiCecController.create(this);
Jinsuk Kima8a5e502014-05-15 16:51:49 +0900393 if (mCecController != null) {
Jungshik Janga9f10622014-07-11 15:36:39 +0900394 if (mHdmiControlEnabled) {
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900395 initializeCec(INITIATED_BY_BOOT_UP);
Jungshik Janga9f10622014-07-11 15:36:39 +0900396 }
Jinsuk Kima8a5e502014-05-15 16:51:49 +0900397 } else {
Jungshik Jang0792d372014-04-23 17:57:26 +0900398 Slog.i(TAG, "Device does not support HDMI-CEC.");
Jinsuk Kim08f1ab02014-10-13 10:38:16 +0900399 return;
Jungshik Jang0792d372014-04-23 17:57:26 +0900400 }
401
Jinsuk Kim78104122014-08-26 19:32:34 +0900402 mMhlController = HdmiMhlControllerStub.create(this);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900403 if (!mMhlController.isReady()) {
Jungshik Jang0792d372014-04-23 17:57:26 +0900404 Slog.i(TAG, "Device does not support MHL-control.");
405 }
Jinsuk Kimed086452014-08-18 15:01:53 +0900406 mMhlDevices = Collections.emptyList();
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900407
408 initPortInfo();
Yuncheol Heo75a77e72014-07-09 18:27:53 +0900409 mMessageValidator = new HdmiCecMessageValidator(this);
Jinsuk Kim8692fc62014-05-29 07:39:22 +0900410 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900411
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900412 if (mCecController != null) {
Yuncheol Heo0608b932014-10-13 16:39:18 +0900413 // Register broadcast receiver for power state change.
Yuncheol Heo38db6292014-07-01 14:15:14 +0900414 IntentFilter filter = new IntentFilter();
415 filter.addAction(Intent.ACTION_SCREEN_OFF);
416 filter.addAction(Intent.ACTION_SCREEN_ON);
Rob McConnell30595562016-03-11 11:03:00 +0000417 filter.addAction(Intent.ACTION_SHUTDOWN);
Terry Heo1ca0a432014-08-18 10:30:32 +0900418 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
419 getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter);
Yuncheol Heo0608b932014-10-13 16:39:18 +0900420
421 // Register ContentObserver to monitor the settings change.
422 registerContentObserver();
Yuncheol Heo38db6292014-07-01 14:15:14 +0900423 }
Jinsuk Kim5b8cb002015-01-19 07:30:12 +0900424 mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED);
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900425 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900426
Jinsuk Kim7fa3a662014-11-07 15:20:24 +0900427 @Override
428 public void onBootPhase(int phase) {
429 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
430 mTvInputManager = (TvInputManager) getContext().getSystemService(
431 Context.TV_INPUT_SERVICE);
Jinsuk Kime26d8332015-01-09 08:55:41 +0900432 mPowerManager = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
Jinsuk Kim7fa3a662014-11-07 15:20:24 +0900433 }
434 }
435
436 TvInputManager getTvInputManager() {
437 return mTvInputManager;
438 }
439
440 void registerTvInputCallback(TvInputCallback callback) {
441 if (mTvInputManager == null) return;
442 mTvInputManager.registerCallback(callback, mHandler);
443 }
444
445 void unregisterTvInputCallback(TvInputCallback callback) {
446 if (mTvInputManager == null) return;
447 mTvInputManager.unregisterCallback(callback);
448 }
449
Jinsuk Kime26d8332015-01-09 08:55:41 +0900450 PowerManager getPowerManager() {
451 return mPowerManager;
452 }
453
Yuncheol Heo25c20292014-07-31 17:59:39 +0900454 /**
455 * Called when the initialization of local devices is complete.
456 */
Yuncheol Heo0608b932014-10-13 16:39:18 +0900457 private void onInitializeCecComplete(int initiatedBy) {
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900458 if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
459 mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
460 }
461 mWakeUpMessageReceived = false;
462
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900463 if (isTvDeviceEnabled()) {
Donghyun Chobc6e3722016-11-04 05:25:52 +0900464 mCecController.setOption(OptionKey.WAKEUP, tv().getAutoWakeup());
Yuncheol Heo0608b932014-10-13 16:39:18 +0900465 }
466 int reason = -1;
467 switch (initiatedBy) {
468 case INITIATED_BY_BOOT_UP:
469 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_START;
470 break;
471 case INITIATED_BY_ENABLE_CEC:
472 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING;
473 break;
474 case INITIATED_BY_SCREEN_ON:
475 case INITIATED_BY_WAKE_UP_MESSAGE:
476 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_WAKEUP;
477 break;
478 }
479 if (reason != -1) {
480 invokeVendorCommandListenersOnControlStateChanged(true, reason);
Yuncheol Heo25c20292014-07-31 17:59:39 +0900481 }
482 }
483
Jinsuk Kim50084862014-08-07 13:11:40 +0900484 private void registerContentObserver() {
485 ContentResolver resolver = getContext().getContentResolver();
486 String[] settings = new String[] {
487 Global.HDMI_CONTROL_ENABLED,
488 Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
489 Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
Donghyun Choc1fa9af2016-12-27 18:31:09 +0900490 Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED,
Jinsuk Kim50084862014-08-07 13:11:40 +0900491 Global.MHL_INPUT_SWITCHING_ENABLED,
492 Global.MHL_POWER_CHARGE_ENABLED
493 };
Jungshik Jang5691b2f2014-08-18 16:50:12 +0900494 for (String s : settings) {
Jinsuk Kim50084862014-08-07 13:11:40 +0900495 resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
496 UserHandle.USER_ALL);
497 }
498 }
499
500 private class SettingsObserver extends ContentObserver {
501 public SettingsObserver(Handler handler) {
502 super(handler);
503 }
504
Jungshik Jangf67113f2014-08-22 16:27:19 +0900505 // onChange is set up to run in service thread.
Jinsuk Kim50084862014-08-07 13:11:40 +0900506 @Override
507 public void onChange(boolean selfChange, Uri uri) {
508 String option = uri.getLastPathSegment();
509 boolean enabled = readBooleanSetting(option, true);
510 switch (option) {
511 case Global.HDMI_CONTROL_ENABLED:
512 setControlEnabled(enabled);
513 break;
514 case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900515 if (isTvDeviceEnabled()) {
516 tv().setAutoWakeup(enabled);
517 }
Donghyun Chobc6e3722016-11-04 05:25:52 +0900518 setCecOption(OptionKey.WAKEUP, enabled);
Jinsuk Kim50084862014-08-07 13:11:40 +0900519 break;
520 case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900521 for (int type : mLocalDevices) {
522 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
Terry Heodd371ec2015-12-10 15:31:05 +0900523 if (localDevice != null) {
524 localDevice.setAutoDeviceOff(enabled);
525 }
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900526 }
Jinsuk Kim50084862014-08-07 13:11:40 +0900527 // No need to propagate to HAL.
528 break;
Donghyun Choc1fa9af2016-12-27 18:31:09 +0900529 case Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED:
530 if (isTvDeviceEnabled()) {
531 tv().setSystemAudioControlFeatureEnabled(enabled);
Donghyun Cho2601f8d2016-03-25 20:18:06 +0900532 }
533 break;
Jinsuk Kim50084862014-08-07 13:11:40 +0900534 case Global.MHL_INPUT_SWITCHING_ENABLED:
Yuncheol Heo08a1be82014-08-12 20:58:41 +0900535 setMhlInputChangeEnabled(enabled);
Jinsuk Kim50084862014-08-07 13:11:40 +0900536 break;
537 case Global.MHL_POWER_CHARGE_ENABLED:
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900538 mMhlController.setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled));
Jinsuk Kim50084862014-08-07 13:11:40 +0900539 break;
540 }
541 }
542 }
543
544 private static int toInt(boolean enabled) {
545 return enabled ? ENABLED : DISABLED;
546 }
547
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900548 boolean readBooleanSetting(String key, boolean defVal) {
549 ContentResolver cr = getContext().getContentResolver();
Jinsuk Kim50084862014-08-07 13:11:40 +0900550 return Global.getInt(cr, key, toInt(defVal)) == ENABLED;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900551 }
552
553 void writeBooleanSetting(String key, boolean value) {
554 ContentResolver cr = getContext().getContentResolver();
Jinsuk Kim50084862014-08-07 13:11:40 +0900555 Global.putInt(cr, key, toInt(value));
556 }
557
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900558 private void initializeCec(int initiatedBy) {
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900559 mAddressAllocated = false;
Donghyun Chobc6e3722016-11-04 05:25:52 +0900560 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
561 mCecController.setLanguage(mLanguage);
Yuncheol Heob5021862014-09-02 10:36:04 +0900562 initializeLocalDevices(initiatedBy);
Jungshik Janga9f10622014-07-11 15:36:39 +0900563 }
564
Jungshik Janga5b74142014-06-23 18:03:10 +0900565 @ServiceThreadOnly
Yuncheol Heob5021862014-09-02 10:36:04 +0900566 private void initializeLocalDevices(final int initiatedBy) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900567 assertRunOnServiceThread();
Yuncheol Heob5021862014-09-02 10:36:04 +0900568 // A container for [Device type, Local device info].
569 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
Yuncheol Heob5021862014-09-02 10:36:04 +0900570 for (int type : mLocalDevices) {
Jinsuk Kim6f87b4e2014-10-10 14:40:29 +0900571 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
572 if (localDevice == null) {
573 localDevice = HdmiCecLocalDevice.create(this, type);
574 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900575 localDevice.init();
Yuncheol Heob5021862014-09-02 10:36:04 +0900576 localDevices.add(localDevice);
577 }
Jinsuk Kim6f87b4e2014-10-10 14:40:29 +0900578 // It's now safe to flush existing local devices from mCecController since they were
579 // already moved to 'localDevices'.
580 clearLocalDevices();
Yuncheol Heob5021862014-09-02 10:36:04 +0900581 allocateLogicalAddress(localDevices, initiatedBy);
582 }
583
584 @ServiceThreadOnly
585 private void allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices,
586 final int initiatedBy) {
587 assertRunOnServiceThread();
Yuncheol Heo89ec14e2014-09-16 15:53:59 +0900588 mCecController.clearLogicalAddress();
Yuncheol Heob5021862014-09-02 10:36:04 +0900589 final ArrayList<HdmiCecLocalDevice> allocatedDevices = new ArrayList<>();
590 final int[] finished = new int[1];
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900591 mAddressAllocated = allocatingDevices.isEmpty();
592
Jinsuk Kimf98b9e82015-10-05 14:24:48 +0900593 // For TV device, select request can be invoked while address allocation or device
594 // discovery is in progress. Initialize the request here at the start of allocation,
595 // and process the collected requests later when the allocation and device discovery
596 // is all completed.
597 mSelectRequestBuffer.clear();
598
Yuncheol Heob5021862014-09-02 10:36:04 +0900599 for (final HdmiCecLocalDevice localDevice : allocatingDevices) {
600 mCecController.allocateLogicalAddress(localDevice.getType(),
Jungshik Jang3ee65722014-06-03 16:22:30 +0900601 localDevice.getPreferredAddress(), new AllocateAddressCallback() {
602 @Override
603 public void onAllocated(int deviceType, int logicalAddress) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900604 if (logicalAddress == Constants.ADDR_UNREGISTERED) {
Jungshik Jang3ee65722014-06-03 16:22:30 +0900605 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
606 } else {
Jungshik Jang410ca9c2014-08-07 18:04:14 +0900607 // Set POWER_STATUS_ON to all local devices because they share lifetime
608 // with system.
609 HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
610 HdmiControlManager.POWER_STATUS_ON);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900611 localDevice.setDeviceInfo(deviceInfo);
612 mCecController.addLocalDevice(deviceType, localDevice);
613 mCecController.addLogicalAddress(logicalAddress);
Yuncheol Heob5021862014-09-02 10:36:04 +0900614 allocatedDevices.add(localDevice);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900615 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900616
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900617 // Address allocation completed for all devices. Notify each device.
Yuncheol Heob5021862014-09-02 10:36:04 +0900618 if (allocatingDevices.size() == ++finished[0]) {
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900619 mAddressAllocated = true;
Yuncheol Heob5021862014-09-02 10:36:04 +0900620 if (initiatedBy != INITIATED_BY_HOTPLUG) {
621 // In case of the hotplug we don't call onInitializeCecComplete()
622 // since we reallocate the logical address only.
Yuncheol Heo0608b932014-10-13 16:39:18 +0900623 onInitializeCecComplete(initiatedBy);
Yuncheol Heob5021862014-09-02 10:36:04 +0900624 }
625 notifyAddressAllocated(allocatedDevices, initiatedBy);
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900626 mCecMessageBuffer.processMessages();
Jungshik Jang3ee65722014-06-03 16:22:30 +0900627 }
628 }
629 });
630 }
631 }
632
Jungshik Janga5b74142014-06-23 18:03:10 +0900633 @ServiceThreadOnly
Yuncheol Heob5021862014-09-02 10:36:04 +0900634 private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900635 assertRunOnServiceThread();
Yuncheol Heob5021862014-09-02 10:36:04 +0900636 for (HdmiCecLocalDevice device : devices) {
637 int address = device.getDeviceInfo().getLogicalAddress();
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900638 device.handleAddressAllocated(address, initiatedBy);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900639 }
Jinsuk Kimf98b9e82015-10-05 14:24:48 +0900640 if (isTvDeviceEnabled()) {
641 tv().setSelectRequestBuffer(mSelectRequestBuffer);
642 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900643 }
644
Donghyun Chofc462b92016-05-13 21:06:02 +0900645 boolean isAddressAllocated() {
646 return mAddressAllocated;
647 }
648
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900649 // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
650 // keep them in one place.
Jungshik Janga5b74142014-06-23 18:03:10 +0900651 @ServiceThreadOnly
Jinsuk Kim2b152012014-07-25 08:22:26 +0900652 private void initPortInfo() {
Jungshik Janga5b74142014-06-23 18:03:10 +0900653 assertRunOnServiceThread();
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900654 HdmiPortInfo[] cecPortInfo = null;
655
656 // CEC HAL provides majority of the info while MHL does only MHL support flag for
657 // each port. Return empty array if CEC HAL didn't provide the info.
658 if (mCecController != null) {
659 cecPortInfo = mCecController.getPortInfos();
660 }
661 if (cecPortInfo == null) {
Jinsuk Kim2b152012014-07-25 08:22:26 +0900662 return;
663 }
664
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900665 SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
666 SparseIntArray portIdMap = new SparseIntArray();
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900667 SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>();
Jinsuk Kim2b152012014-07-25 08:22:26 +0900668 for (HdmiPortInfo info : cecPortInfo) {
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900669 portIdMap.put(info.getAddress(), info.getId());
670 portInfoMap.put(info.getId(), info);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900671 portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900672 }
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900673 mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
674 mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900675 mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900676
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900677 HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
678 ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
679 for (HdmiPortInfo info : mhlPortInfo) {
680 if (info.isMhlSupported()) {
681 mhlSupportedPorts.add(info.getId());
682 }
683 }
684
685 // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use
686 // cec port info if we do not have have port that supports MHL.
687 if (mhlSupportedPorts.isEmpty()) {
Jinsuk Kimf4eb72d2014-07-25 13:02:51 +0900688 mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo));
689 return;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900690 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900691 ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
692 for (HdmiPortInfo info : cecPortInfo) {
693 if (mhlSupportedPorts.contains(info.getId())) {
694 result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
695 info.isCecSupported(), true, info.isArcSupported()));
696 } else {
697 result.add(info);
698 }
699 }
700 mPortInfo = Collections.unmodifiableList(result);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900701 }
702
Jungshik Jang2738e2d2014-08-19 09:30:05 +0900703 List<HdmiPortInfo> getPortInfo() {
704 return mPortInfo;
705 }
706
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900707 /**
708 * Returns HDMI port information for the given port id.
709 *
710 * @param portId HDMI port id
711 * @return {@link HdmiPortInfo} for the given port
712 */
713 HdmiPortInfo getPortInfo(int portId) {
Jinsuk Kim2b152012014-07-25 08:22:26 +0900714 return mPortInfoMap.get(portId, null);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900715 }
716
Jungshik Jange9c77c82014-04-24 20:30:09 +0900717 /**
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900718 * Returns the routing path (physical address) of the HDMI port for the given
719 * port id.
720 */
721 int portIdToPath(int portId) {
722 HdmiPortInfo portInfo = getPortInfo(portId);
723 if (portInfo == null) {
724 Slog.e(TAG, "Cannot find the port info: " + portId);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900725 return Constants.INVALID_PHYSICAL_ADDRESS;
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900726 }
727 return portInfo.getAddress();
728 }
729
730 /**
731 * Returns the id of HDMI port located at the top of the hierarchy of
732 * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
733 * the port id to be returned is the ID associated with the port address
734 * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
735 */
736 int pathToPortId(int path) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900737 int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900738 return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900739 }
740
Jinsuk Kim09ffc842014-07-11 17:04:32 +0900741 boolean isValidPortId(int portId) {
Jinsuk Kim2b152012014-07-25 08:22:26 +0900742 return getPortInfo(portId) != null;
Jinsuk Kim09ffc842014-07-11 17:04:32 +0900743 }
744
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900745 /**
Jungshik Jange9c77c82014-04-24 20:30:09 +0900746 * Returns {@link Looper} for IO operation.
747 *
748 * <p>Declared as package-private.
749 */
750 Looper getIoLooper() {
751 return mIoThread.getLooper();
752 }
753
754 /**
755 * Returns {@link Looper} of main thread. Use this {@link Looper} instance
756 * for tasks that are running on main service thread.
757 *
758 * <p>Declared as package-private.
759 */
760 Looper getServiceLooper() {
Jungshik Jang67ea5212014-05-15 14:05:24 +0900761 return mHandler.getLooper();
Jungshik Jange9c77c82014-04-24 20:30:09 +0900762 }
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900763
764 /**
Jungshik Jang3ee65722014-06-03 16:22:30 +0900765 * Returns physical address of the device.
766 */
767 int getPhysicalAddress() {
768 return mCecController.getPhysicalAddress();
769 }
770
771 /**
772 * Returns vendor id of CEC service.
773 */
774 int getVendorId() {
775 return mCecController.getVendorId();
776 }
777
Jungshik Janga5b74142014-06-23 18:03:10 +0900778 @ServiceThreadOnly
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900779 HdmiDeviceInfo getDeviceInfo(int logicalAddress) {
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900780 assertRunOnServiceThread();
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900781 return tv() == null ? null : tv().getCecDeviceInfo(logicalAddress);
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900782 }
783
Jinsuk Kim6ad7cbd2015-01-06 11:30:56 +0900784 @ServiceThreadOnly
785 HdmiDeviceInfo getDeviceInfoByPort(int port) {
786 assertRunOnServiceThread();
787 HdmiMhlLocalDeviceStub info = mMhlController.getLocalDevice(port);
788 if (info != null) {
789 return info.getInfo();
790 }
791 return null;
792 }
793
Jungshik Jang3ee65722014-06-03 16:22:30 +0900794 /**
Jungshik Jang092b4452014-06-11 15:19:17 +0900795 * Returns version of CEC.
796 */
797 int getCecVersion() {
798 return mCecController.getVersion();
799 }
800
801 /**
Jungshik Jang60cffce2014-06-12 18:03:04 +0900802 * Whether a device of the specified physical address is connected to ARC enabled port.
803 */
804 boolean isConnectedToArcPort(int physicalAddress) {
Jungshik Jang339227d2014-08-25 15:37:20 +0900805 int portId = pathToPortId(physicalAddress);
Jinsuk Kim2b152012014-07-25 08:22:26 +0900806 if (portId != Constants.INVALID_PORT_ID) {
807 return mPortInfoMap.get(portId).isArcSupported();
Jungshik Jang60cffce2014-06-12 18:03:04 +0900808 }
809 return false;
810 }
811
Jinsuk Kim7b0cf642015-04-14 09:43:45 +0900812 @ServiceThreadOnly
813 boolean isConnected(int portId) {
814 assertRunOnServiceThread();
815 return mCecController.isConnected(portId);
816 }
817
Jungshik Jang79c58a42014-06-16 16:45:36 +0900818 void runOnServiceThread(Runnable runnable) {
Jungshik Jang67ea5212014-05-15 14:05:24 +0900819 mHandler.post(runnable);
820 }
821
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900822 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
823 mHandler.postAtFrontOfQueue(runnable);
824 }
825
826 private void assertRunOnServiceThread() {
827 if (Looper.myLooper() != mHandler.getLooper()) {
828 throw new IllegalStateException("Should run on service thread.");
829 }
830 }
831
Jungshik Jang67ea5212014-05-15 14:05:24 +0900832 /**
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900833 * Transmit a CEC command to CEC bus.
834 *
835 * @param command CEC command to send out
Jungshik Jangd643f762014-05-22 19:28:09 +0900836 * @param callback interface used to the result of send command
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900837 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900838 @ServiceThreadOnly
Jungshik Jangd643f762014-05-22 19:28:09 +0900839 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900840 assertRunOnServiceThread();
Yuncheol Heo4c212892014-09-12 14:32:46 +0900841 if (mMessageValidator.isValid(command) == HdmiCecMessageValidator.OK) {
Jungshik Jang5f75cbd2014-08-07 12:02:29 +0900842 mCecController.sendCommand(command, callback);
843 } else {
Jungshik Jang2e8f1b62014-09-03 08:28:02 +0900844 HdmiLogger.error("Invalid message type:" + command);
Jungshik Jang5f75cbd2014-08-07 12:02:29 +0900845 if (callback != null) {
Donghyun Chobc6e3722016-11-04 05:25:52 +0900846 callback.onSendCompleted(SendMessageResult.FAIL);
Jungshik Jang5f75cbd2014-08-07 12:02:29 +0900847 }
848 }
Jungshik Jangd643f762014-05-22 19:28:09 +0900849 }
850
Jungshik Janga5b74142014-06-23 18:03:10 +0900851 @ServiceThreadOnly
Jungshik Jangd643f762014-05-22 19:28:09 +0900852 void sendCecCommand(HdmiCecMessage command) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900853 assertRunOnServiceThread();
Jungshik Jang5f75cbd2014-08-07 12:02:29 +0900854 sendCecCommand(command, null);
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900855 }
856
Yuncheol Heo6aae6522014-08-05 14:48:37 +0900857 /**
858 * Send <Feature Abort> command on the given CEC message if possible.
859 * If the aborted message is invalid, then it wont send the message.
860 * @param command original command to be aborted
861 * @param reason reason of feature abort
862 */
863 @ServiceThreadOnly
864 void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) {
865 assertRunOnServiceThread();
866 mCecController.maySendFeatureAbortCommand(command, reason);
867 }
868
Jungshik Janga5b74142014-06-23 18:03:10 +0900869 @ServiceThreadOnly
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900870 boolean handleCecCommand(HdmiCecMessage message) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900871 assertRunOnServiceThread();
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900872 if (!mAddressAllocated) {
873 mCecMessageBuffer.bufferMessage(message);
874 return true;
875 }
Yuncheol Heo4c212892014-09-12 14:32:46 +0900876 int errorCode = mMessageValidator.isValid(message);
877 if (errorCode != HdmiCecMessageValidator.OK) {
Yuncheol Heoa95f1a92014-11-06 08:25:39 +0900878 // We'll not response on the messages with the invalid source or destination
879 // or with parameter length shorter than specified in the standard.
Yuncheol Heo4c212892014-09-12 14:32:46 +0900880 if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) {
881 maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
882 }
883 return true;
Yuncheol Heo75a77e72014-07-09 18:27:53 +0900884 }
Jungshik Jang092b4452014-06-11 15:19:17 +0900885 return dispatchMessageToLocalDevice(message);
886 }
887
Donghyun Chobc6e3722016-11-04 05:25:52 +0900888 void enableAudioReturnChannel(int portId, boolean enabled) {
889 mCecController.enableAudioReturnChannel(portId, enabled);
Jungshik Jang60cffce2014-06-12 18:03:04 +0900890 }
891
Jungshik Janga5b74142014-06-23 18:03:10 +0900892 @ServiceThreadOnly
Jungshik Jang092b4452014-06-11 15:19:17 +0900893 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900894 assertRunOnServiceThread();
Jungshik Jang092b4452014-06-11 15:19:17 +0900895 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900896 if (device.dispatchMessage(message)
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900897 && message.getDestination() != Constants.ADDR_BROADCAST) {
Jungshik Jang092b4452014-06-11 15:19:17 +0900898 return true;
899 }
900 }
Jungshik Jang60cffce2014-06-12 18:03:04 +0900901
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900902 if (message.getDestination() != Constants.ADDR_BROADCAST) {
Jungshik Jang2e8f1b62014-09-03 08:28:02 +0900903 HdmiLogger.warning("Unhandled cec command:" + message);
Jungshik Jang3a959fc2014-07-03 09:34:05 +0900904 }
Jungshik Jang092b4452014-06-11 15:19:17 +0900905 return false;
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900906 }
907
Jungshik Jang67ea5212014-05-15 14:05:24 +0900908 /**
909 * Called when a new hotplug event is issued.
910 *
Jinsuk Kimed086452014-08-18 15:01:53 +0900911 * @param portId hdmi port number where hot plug event issued.
Jungshik Jang67ea5212014-05-15 14:05:24 +0900912 * @param connected whether to be plugged in or not
913 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900914 @ServiceThreadOnly
Jinsuk Kimed086452014-08-18 15:01:53 +0900915 void onHotplug(int portId, boolean connected) {
Jungshik Jang60cffce2014-06-12 18:03:04 +0900916 assertRunOnServiceThread();
Yuncheol Heob5021862014-09-02 10:36:04 +0900917
Yuncheol Heob8d62e72014-09-22 19:53:41 +0900918 if (connected && !isTvDevice()) {
919 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
920 for (int type : mLocalDevices) {
921 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
922 if (localDevice == null) {
923 localDevice = HdmiCecLocalDevice.create(this, type);
924 localDevice.init();
925 }
926 localDevices.add(localDevice);
Yuncheol Heob5021862014-09-02 10:36:04 +0900927 }
Yuncheol Heob8d62e72014-09-22 19:53:41 +0900928 allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
Yuncheol Heob5021862014-09-02 10:36:04 +0900929 }
Yuncheol Heob5021862014-09-02 10:36:04 +0900930
Jungshik Jang79c58a42014-06-16 16:45:36 +0900931 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jinsuk Kimed086452014-08-18 15:01:53 +0900932 device.onHotplug(portId, connected);
Jungshik Jang60cffce2014-06-12 18:03:04 +0900933 }
Jinsuk Kimed086452014-08-18 15:01:53 +0900934 announceHotplugEvent(portId, connected);
Jungshik Jang67ea5212014-05-15 14:05:24 +0900935 }
936
Jungshik Jang02bb4262014-05-23 16:48:31 +0900937 /**
938 * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
939 * devices.
940 *
941 * @param callback an interface used to get a list of all remote devices' address
Jungshik Jang1de51422014-07-03 11:14:26 +0900942 * @param sourceAddress a logical address of source device where sends polling message
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900943 * @param pickStrategy strategy how to pick polling candidates
Jungshik Jang02bb4262014-05-23 16:48:31 +0900944 * @param retryCount the number of retry used to send polling message to remote devices
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900945 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
Jungshik Jang02bb4262014-05-23 16:48:31 +0900946 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900947 @ServiceThreadOnly
Jungshik Jang1de51422014-07-03 11:14:26 +0900948 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
949 int retryCount) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900950 assertRunOnServiceThread();
Jungshik Jang1de51422014-07-03 11:14:26 +0900951 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
952 retryCount);
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900953 }
954
955 private int checkPollStrategy(int pickStrategy) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900956 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900957 if (strategy == 0) {
958 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
959 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900960 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900961 if (iterationStrategy == 0) {
962 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
963 }
964 return strategy | iterationStrategy;
Jungshik Jang02bb4262014-05-23 16:48:31 +0900965 }
966
Jungshik Jang60cffce2014-06-12 18:03:04 +0900967 List<HdmiCecLocalDevice> getAllLocalDevices() {
968 assertRunOnServiceThread();
969 return mCecController.getLocalDeviceList();
970 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900971
Jungshik Jang79c58a42014-06-16 16:45:36 +0900972 Object getServiceLock() {
973 return mLock;
974 }
975
976 void setAudioStatus(boolean mute, int volume) {
Donghyun Cho65618452016-12-23 18:30:37 +0900977 if (!isTvDeviceEnabled() || !tv().isSystemAudioActivated()) {
978 return;
979 }
Jungshik Jangb69aafbf2014-07-11 16:29:06 +0900980 AudioManager audioManager = getAudioManager();
981 boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
982 if (mute) {
983 if (!muted) {
984 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
985 }
986 } else {
987 if (muted) {
988 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
989 }
990 // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
991 // volume change notification back to hdmi control service.
992 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
Jungshik Jang1a6be6e2014-09-16 11:04:54 +0900993 AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
Jungshik Jangb69aafbf2014-07-11 16:29:06 +0900994 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900995 }
996
Jungshik Jangea67c182014-06-19 22:19:20 +0900997 void announceSystemAudioModeChange(boolean enabled) {
Jungshik Jangf4249322014-08-21 14:17:05 +0900998 synchronized (mLock) {
999 for (SystemAudioModeChangeListenerRecord record :
1000 mSystemAudioModeChangeListenerRecords) {
1001 invokeSystemAudioModeChangeLocked(record.mListener, enabled);
1002 }
Jungshik Jangea67c182014-06-19 22:19:20 +09001003 }
1004 }
1005
Jungshik Jang410ca9c2014-08-07 18:04:14 +09001006 private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
Jungshik Jang42c98002014-06-12 13:17:44 +09001007 // TODO: find better name instead of model name.
1008 String displayName = Build.MODEL;
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001009 return new HdmiDeviceInfo(logicalAddress,
Jinsuk Kim2b152012014-07-25 08:22:26 +09001010 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
1011 getVendorId(), displayName);
Jungshik Jang3ee65722014-06-03 16:22:30 +09001012 }
1013
Jungshik Jang7df52862014-08-11 14:35:27 +09001014 @ServiceThreadOnly
Jungshik Jang7df52862014-08-11 14:35:27 +09001015 void handleMhlHotplugEvent(int portId, boolean connected) {
1016 assertRunOnServiceThread();
Jinsuk Kim93eed0c2014-10-14 11:52:22 +09001017 // Hotplug event is used to add/remove MHL devices as TV input.
Jungshik Jang7df52862014-08-11 14:35:27 +09001018 if (connected) {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001019 HdmiMhlLocalDeviceStub newDevice = new HdmiMhlLocalDeviceStub(this, portId);
1020 HdmiMhlLocalDeviceStub oldDevice = mMhlController.addLocalDevice(newDevice);
Jungshik Jang7df52862014-08-11 14:35:27 +09001021 if (oldDevice != null) {
1022 oldDevice.onDeviceRemoved();
1023 Slog.i(TAG, "Old device of port " + portId + " is removed");
1024 }
Jinsuk Kim93eed0c2014-10-14 11:52:22 +09001025 invokeDeviceEventListeners(newDevice.getInfo(), DEVICE_EVENT_ADD_DEVICE);
1026 updateSafeMhlInput();
Jungshik Jang7df52862014-08-11 14:35:27 +09001027 } else {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001028 HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001029 if (device != null) {
1030 device.onDeviceRemoved();
Jinsuk Kim93eed0c2014-10-14 11:52:22 +09001031 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE);
1032 updateSafeMhlInput();
Jungshik Jang7df52862014-08-11 14:35:27 +09001033 } else {
1034 Slog.w(TAG, "No device to remove:[portId=" + portId);
1035 }
1036 }
Jinsuk Kimed086452014-08-18 15:01:53 +09001037 announceHotplugEvent(portId, connected);
Jungshik Jang7df52862014-08-11 14:35:27 +09001038 }
1039
1040 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001041 void handleMhlBusModeChanged(int portId, int busmode) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001042 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001043 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001044 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001045 device.setBusMode(busmode);
Jungshik Jang7df52862014-08-11 14:35:27 +09001046 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001047 Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId +
1048 ", busmode:" + busmode + "]");
Jungshik Jang7df52862014-08-11 14:35:27 +09001049 }
1050 }
1051
1052 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001053 void handleMhlBusOvercurrent(int portId, boolean on) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001054 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001055 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001056 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001057 device.onBusOvercurrentDetected(on);
Jungshik Jang7df52862014-08-11 14:35:27 +09001058 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001059 Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]");
Jungshik Jang7df52862014-08-11 14:35:27 +09001060 }
1061 }
1062
1063 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001064 void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001065 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001066 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jinsuk Kimed086452014-08-18 15:01:53 +09001067
Jungshik Jang7df52862014-08-11 14:35:27 +09001068 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001069 device.setDeviceStatusChange(adopterId, deviceId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001070 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001071 Slog.w(TAG, "No mhl device exists for device status event[portId:"
Jungshik Jang7df52862014-08-11 14:35:27 +09001072 + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]");
1073 }
1074 }
1075
Jinsuk Kimed086452014-08-18 15:01:53 +09001076 @ServiceThreadOnly
1077 private void updateSafeMhlInput() {
1078 assertRunOnServiceThread();
1079 List<HdmiDeviceInfo> inputs = Collections.emptyList();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001080 SparseArray<HdmiMhlLocalDeviceStub> devices = mMhlController.getAllLocalDevices();
Jinsuk Kimed086452014-08-18 15:01:53 +09001081 for (int i = 0; i < devices.size(); ++i) {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001082 HdmiMhlLocalDeviceStub device = devices.valueAt(i);
Jinsuk Kimed086452014-08-18 15:01:53 +09001083 HdmiDeviceInfo info = device.getInfo();
1084 if (info != null) {
1085 if (inputs.isEmpty()) {
1086 inputs = new ArrayList<>();
1087 }
1088 inputs.add(device.getInfo());
1089 }
1090 }
1091 synchronized (mLock) {
1092 mMhlDevices = inputs;
1093 }
1094 }
1095
1096 private List<HdmiDeviceInfo> getMhlDevicesLocked() {
1097 return mMhlDevices;
1098 }
1099
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001100 private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient {
1101 private final IHdmiMhlVendorCommandListener mListener;
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001102
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001103 public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) {
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001104 mListener = listener;
1105 }
1106
1107 @Override
1108 public void binderDied() {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001109 mMhlVendorCommandListenerRecords.remove(this);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001110 }
1111 }
1112
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001113 // Record class that monitors the event of the caller of being killed. Used to clean up
1114 // the listener list and record list accordingly.
1115 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
1116 private final IHdmiHotplugEventListener mListener;
1117
1118 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
1119 mListener = listener;
1120 }
1121
1122 @Override
1123 public void binderDied() {
1124 synchronized (mLock) {
1125 mHotplugEventListenerRecords.remove(this);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001126 }
1127 }
Jinsuk Kim3cd30512014-12-04 11:05:09 +09001128
1129 @Override
1130 public boolean equals(Object obj) {
1131 if (!(obj instanceof HotplugEventListenerRecord)) return false;
1132 if (obj == this) return true;
1133 HotplugEventListenerRecord other = (HotplugEventListenerRecord) obj;
1134 return other.mListener == this.mListener;
1135 }
1136
1137 @Override
1138 public int hashCode() {
1139 return mListener.hashCode();
1140 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001141 }
1142
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001143 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
1144 private final IHdmiDeviceEventListener mListener;
1145
1146 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
1147 mListener = listener;
1148 }
1149
1150 @Override
Jungshik Jangea67c182014-06-19 22:19:20 +09001151 public void binderDied() {
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001152 synchronized (mLock) {
1153 mDeviceEventListenerRecords.remove(this);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001154 }
1155 }
1156 }
1157
Jungshik Jangea67c182014-06-19 22:19:20 +09001158 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
Yuncheol Heo38db6292014-07-01 14:15:14 +09001159 private final IHdmiSystemAudioModeChangeListener mListener;
Jungshik Jangea67c182014-06-19 22:19:20 +09001160
1161 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
1162 mListener = listener;
1163 }
1164
1165 @Override
1166 public void binderDied() {
1167 synchronized (mLock) {
1168 mSystemAudioModeChangeListenerRecords.remove(this);
Jungshik Jangea67c182014-06-19 22:19:20 +09001169 }
1170 }
1171 }
1172
Jinsuk Kim119160a2014-07-07 18:48:10 +09001173 class VendorCommandListenerRecord implements IBinder.DeathRecipient {
1174 private final IHdmiVendorCommandListener mListener;
1175 private final int mDeviceType;
1176
1177 public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
1178 mListener = listener;
1179 mDeviceType = deviceType;
1180 }
1181
1182 @Override
1183 public void binderDied() {
1184 synchronized (mLock) {
1185 mVendorCommandListenerRecords.remove(this);
1186 }
1187 }
1188 }
1189
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001190 private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
Jungshik Jangf4249322014-08-21 14:17:05 +09001191 private final IHdmiRecordListener mListener;
1192
1193 public HdmiRecordListenerRecord(IHdmiRecordListener listener) {
1194 mListener = listener;
1195 }
1196
Jungshik Jangb6591b82014-07-23 16:10:23 +09001197 @Override
1198 public void binderDied() {
1199 synchronized (mLock) {
Donghyun Chofbbeb3e2016-04-15 09:12:03 +09001200 if (mRecordListenerRecord == this) {
1201 mRecordListenerRecord = null;
1202 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09001203 }
1204 }
1205 }
1206
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001207 private void enforceAccessPermission() {
1208 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
1209 }
1210
1211 private final class BinderService extends IHdmiControlService.Stub {
1212 @Override
1213 public int[] getSupportedTypes() {
1214 enforceAccessPermission();
Jinsuk Kim0340bbc2014-06-05 11:07:47 +09001215 // mLocalDevices is an unmodifiable list - no lock necesary.
1216 int[] localDevices = new int[mLocalDevices.size()];
1217 for (int i = 0; i < localDevices.length; ++i) {
1218 localDevices[i] = mLocalDevices.get(i);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001219 }
Jinsuk Kim0340bbc2014-06-05 11:07:47 +09001220 return localDevices;
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001221 }
1222
1223 @Override
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001224 public HdmiDeviceInfo getActiveSource() {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001225 enforceAccessPermission();
Jinsuk Kim7e742062014-07-30 13:19:13 +09001226 HdmiCecLocalDeviceTv tv = tv();
1227 if (tv == null) {
1228 Slog.w(TAG, "Local tv device not available");
1229 return null;
1230 }
1231 ActiveSource activeSource = tv.getActiveSource();
1232 if (activeSource.isValid()) {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001233 return new HdmiDeviceInfo(activeSource.logicalAddress,
1234 activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID,
1235 HdmiDeviceInfo.DEVICE_INACTIVE, 0, "");
Jinsuk Kim7e742062014-07-30 13:19:13 +09001236 }
1237 int activePath = tv.getActivePath();
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001238 if (activePath != HdmiDeviceInfo.PATH_INVALID) {
Jinsuk Kim7640d982015-01-28 16:44:07 +09001239 HdmiDeviceInfo info = tv.getSafeDeviceInfoByPath(activePath);
Jinsuk Kimd47abef2015-01-17 07:38:24 +09001240 return (info != null) ? info : new HdmiDeviceInfo(activePath, tv.getActivePortId());
Jinsuk Kim7e742062014-07-30 13:19:13 +09001241 }
1242 return null;
1243 }
1244
1245 @Override
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001246 public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) {
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001247 enforceAccessPermission();
1248 runOnServiceThread(new Runnable() {
1249 @Override
1250 public void run() {
Jinsuk Kim72b7d732014-07-24 09:15:35 +09001251 if (callback == null) {
1252 Slog.e(TAG, "Callback cannot be null");
1253 return;
1254 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09001255 HdmiCecLocalDeviceTv tv = tv();
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001256 if (tv == null) {
Jinsuk Kimf98b9e82015-10-05 14:24:48 +09001257 if (!mAddressAllocated) {
1258 mSelectRequestBuffer.set(SelectRequestBuffer.newDeviceSelect(
1259 HdmiControlService.this, deviceId, callback));
1260 return;
1261 }
Jinsuk Kima062a932014-06-18 10:00:39 +09001262 Slog.w(TAG, "Local tv device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001263 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001264 return;
1265 }
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001266 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001267 if (device != null) {
1268 if (device.getPortId() == tv.getActivePortId()) {
1269 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
Jinsuk Kim87f22a22014-08-20 10:40:12 +09001270 return;
1271 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001272 // Upon selecting MHL device, we send RAP[Content On] to wake up
1273 // the connected mobile device, start routing control to switch ports.
1274 // callback is handled by MHL action.
1275 device.turnOn(callback);
Yuncheol Heo7c5d31e2014-09-03 16:28:54 +09001276 tv.doManualPortSwitching(device.getPortId(), null);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001277 return;
Jinsuk Kim87f22a22014-08-20 10:40:12 +09001278 }
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001279 tv.deviceSelect(deviceId, callback);
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001280 }
1281 });
1282 }
1283
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001284 @Override
Jinsuk Kima062a932014-06-18 10:00:39 +09001285 public void portSelect(final int portId, final IHdmiControlCallback callback) {
1286 enforceAccessPermission();
1287 runOnServiceThread(new Runnable() {
1288 @Override
1289 public void run() {
Jinsuk Kim72b7d732014-07-24 09:15:35 +09001290 if (callback == null) {
1291 Slog.e(TAG, "Callback cannot be null");
1292 return;
1293 }
Jinsuk Kima062a932014-06-18 10:00:39 +09001294 HdmiCecLocalDeviceTv tv = tv();
1295 if (tv == null) {
Jinsuk Kimf98b9e82015-10-05 14:24:48 +09001296 if (!mAddressAllocated) {
1297 mSelectRequestBuffer.set(SelectRequestBuffer.newPortSelect(
1298 HdmiControlService.this, portId, callback));
1299 return;
1300 }
Jinsuk Kima062a932014-06-18 10:00:39 +09001301 Slog.w(TAG, "Local tv device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001302 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kima062a932014-06-18 10:00:39 +09001303 return;
1304 }
Jinsuk Kim83335712014-06-24 07:57:00 +09001305 tv.doManualPortSwitching(portId, callback);
Jinsuk Kima062a932014-06-18 10:00:39 +09001306 }
1307 });
1308 }
1309
1310 @Override
Jinsuk Kimc068bb52014-07-07 16:59:20 +09001311 public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
Jinsuk Kima062a932014-06-18 10:00:39 +09001312 enforceAccessPermission();
1313 runOnServiceThread(new Runnable() {
1314 @Override
1315 public void run() {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001316 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001317 if (device != null) {
1318 device.sendKeyEvent(keyCode, isPressed);
1319 return;
Jinsuk Kima062a932014-06-18 10:00:39 +09001320 }
Jungshik Jang4612a6e2014-08-12 22:01:23 +09001321 if (mCecController != null) {
1322 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
1323 if (localDevice == null) {
1324 Slog.w(TAG, "Local device not available");
1325 return;
1326 }
1327 localDevice.sendKeyEvent(keyCode, isPressed);
1328 }
Jinsuk Kima062a932014-06-18 10:00:39 +09001329 }
1330 });
1331 }
1332
1333 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001334 public void oneTouchPlay(final IHdmiControlCallback callback) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001335 enforceAccessPermission();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001336 runOnServiceThread(new Runnable() {
1337 @Override
1338 public void run() {
1339 HdmiControlService.this.oneTouchPlay(callback);
1340 }
1341 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001342 }
1343
1344 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001345 public void queryDisplayStatus(final IHdmiControlCallback callback) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001346 enforceAccessPermission();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001347 runOnServiceThread(new Runnable() {
1348 @Override
1349 public void run() {
1350 HdmiControlService.this.queryDisplayStatus(callback);
1351 }
1352 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001353 }
1354
1355 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001356 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001357 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001358 HdmiControlService.this.addHotplugEventListener(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001359 }
1360
1361 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001362 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001363 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001364 HdmiControlService.this.removeHotplugEventListener(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001365 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001366
1367 @Override
1368 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
1369 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001370 HdmiControlService.this.addDeviceEventListener(listener);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001371 }
1372
1373 @Override
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001374 public List<HdmiPortInfo> getPortInfo() {
1375 enforceAccessPermission();
Jungshik Jang2738e2d2014-08-19 09:30:05 +09001376 return HdmiControlService.this.getPortInfo();
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001377 }
Jungshik Jangea67c182014-06-19 22:19:20 +09001378
1379 @Override
1380 public boolean canChangeSystemAudioMode() {
1381 enforceAccessPermission();
1382 HdmiCecLocalDeviceTv tv = tv();
1383 if (tv == null) {
1384 return false;
1385 }
Jungshik Jange9cf1582014-06-23 17:28:58 +09001386 return tv.hasSystemAudioDevice();
Jungshik Jangea67c182014-06-19 22:19:20 +09001387 }
1388
1389 @Override
1390 public boolean getSystemAudioMode() {
1391 enforceAccessPermission();
1392 HdmiCecLocalDeviceTv tv = tv();
1393 if (tv == null) {
1394 return false;
1395 }
Jungshik Jang377dcbd2014-07-15 15:49:02 +09001396 return tv.isSystemAudioActivated();
Jungshik Jangea67c182014-06-19 22:19:20 +09001397 }
1398
1399 @Override
1400 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
1401 enforceAccessPermission();
1402 runOnServiceThread(new Runnable() {
1403 @Override
1404 public void run() {
1405 HdmiCecLocalDeviceTv tv = tv();
1406 if (tv == null) {
1407 Slog.w(TAG, "Local tv device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001408 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jungshik Jangea67c182014-06-19 22:19:20 +09001409 return;
1410 }
1411 tv.changeSystemAudioMode(enabled, callback);
1412 }
1413 });
1414 }
1415
1416 @Override
1417 public void addSystemAudioModeChangeListener(
1418 final IHdmiSystemAudioModeChangeListener listener) {
1419 enforceAccessPermission();
1420 HdmiControlService.this.addSystemAudioModeChangeListner(listener);
1421 }
1422
1423 @Override
1424 public void removeSystemAudioModeChangeListener(
1425 final IHdmiSystemAudioModeChangeListener listener) {
1426 enforceAccessPermission();
1427 HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
1428 }
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001429
1430 @Override
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001431 public void setInputChangeListener(final IHdmiInputChangeListener listener) {
1432 enforceAccessPermission();
1433 HdmiControlService.this.setInputChangeListener(listener);
1434 }
1435
1436 @Override
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001437 public List<HdmiDeviceInfo> getInputDevices() {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001438 enforceAccessPermission();
1439 // No need to hold the lock for obtaining TV device as the local device instance
1440 // is preserved while the HDMI control is enabled.
1441 HdmiCecLocalDeviceTv tv = tv();
Jinsuk Kimed086452014-08-18 15:01:53 +09001442 synchronized (mLock) {
1443 List<HdmiDeviceInfo> cecDevices = (tv == null)
1444 ? Collections.<HdmiDeviceInfo>emptyList()
1445 : tv.getSafeExternalInputsLocked();
1446 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked());
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001447 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001448 }
1449
Jinsuk Kimbdf27fb2014-10-20 10:00:04 +09001450 // Returns all the CEC devices on the bus including system audio, switch,
1451 // even those of reserved type.
1452 @Override
1453 public List<HdmiDeviceInfo> getDeviceList() {
1454 enforceAccessPermission();
1455 HdmiCecLocalDeviceTv tv = tv();
1456 synchronized (mLock) {
1457 return (tv == null)
1458 ? Collections.<HdmiDeviceInfo>emptyList()
1459 : tv.getSafeCecDevicesLocked();
1460 }
1461 }
1462
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001463 @Override
Jungshik Jang41d97462014-06-30 22:26:29 +09001464 public void setSystemAudioVolume(final int oldIndex, final int newIndex,
1465 final int maxIndex) {
1466 enforceAccessPermission();
1467 runOnServiceThread(new Runnable() {
1468 @Override
1469 public void run() {
1470 HdmiCecLocalDeviceTv tv = tv();
1471 if (tv == null) {
1472 Slog.w(TAG, "Local tv device not available");
1473 return;
1474 }
1475 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
1476 }
1477 });
1478 }
1479
1480 @Override
1481 public void setSystemAudioMute(final boolean mute) {
1482 enforceAccessPermission();
1483 runOnServiceThread(new Runnable() {
1484 @Override
1485 public void run() {
1486 HdmiCecLocalDeviceTv tv = tv();
1487 if (tv == null) {
1488 Slog.w(TAG, "Local tv device not available");
1489 return;
1490 }
1491 tv.changeMute(mute);
1492 }
1493 });
1494 }
1495
1496 @Override
Jungshik Janga13da0d2014-06-30 16:26:06 +09001497 public void setArcMode(final boolean enabled) {
1498 enforceAccessPermission();
1499 runOnServiceThread(new Runnable() {
1500 @Override
1501 public void run() {
1502 HdmiCecLocalDeviceTv tv = tv();
1503 if (tv == null) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09001504 Slog.w(TAG, "Local tv device not available to change arc mode.");
Jungshik Janga13da0d2014-06-30 16:26:06 +09001505 return;
1506 }
1507 }
1508 });
1509 }
Jinsuk Kim160a6e52014-07-02 06:16:36 +09001510
1511 @Override
Jinsuk Kim4d43d932014-07-03 16:43:58 +09001512 public void setProhibitMode(final boolean enabled) {
1513 enforceAccessPermission();
1514 if (!isTvDevice()) {
1515 return;
1516 }
1517 HdmiControlService.this.setProhibitMode(enabled);
1518 }
Jinsuk Kim119160a2014-07-07 18:48:10 +09001519
1520 @Override
1521 public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
1522 final int deviceType) {
1523 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001524 HdmiControlService.this.addVendorCommandListener(listener, deviceType);
Jinsuk Kim119160a2014-07-07 18:48:10 +09001525 }
1526
1527 @Override
1528 public void sendVendorCommand(final int deviceType, final int targetAddress,
1529 final byte[] params, final boolean hasVendorId) {
1530 enforceAccessPermission();
1531 runOnServiceThread(new Runnable() {
1532 @Override
1533 public void run() {
1534 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1535 if (device == null) {
1536 Slog.w(TAG, "Local device not available");
1537 return;
1538 }
1539 if (hasVendorId) {
1540 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1541 device.getDeviceInfo().getLogicalAddress(), targetAddress,
1542 getVendorId(), params));
1543 } else {
1544 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1545 device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1546 }
1547 }
1548 });
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001549 }
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09001550
1551 @Override
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09001552 public void sendStandby(final int deviceType, final int deviceId) {
1553 enforceAccessPermission();
1554 runOnServiceThread(new Runnable() {
1555 @Override
1556 public void run() {
Jinsuk Kim61c94d12015-01-15 07:00:28 +09001557 HdmiMhlLocalDeviceStub mhlDevice = mMhlController.getLocalDeviceById(deviceId);
1558 if (mhlDevice != null) {
1559 mhlDevice.sendStandby();
1560 return;
1561 }
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09001562 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1563 if (device == null) {
1564 Slog.w(TAG, "Local device not available");
1565 return;
1566 }
1567 device.sendStandby(deviceId);
1568 }
1569 });
1570 }
1571
1572 @Override
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001573 public void setHdmiRecordListener(IHdmiRecordListener listener) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001574 enforceAccessPermission();
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001575 HdmiControlService.this.setHdmiRecordListener(listener);
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09001576 }
1577
1578 @Override
Jungshik Jangb6591b82014-07-23 16:10:23 +09001579 public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001580 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001581 runOnServiceThread(new Runnable() {
1582 @Override
1583 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001584 if (!isTvDeviceEnabled()) {
1585 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001586 return;
1587 }
1588 tv().startOneTouchRecord(recorderAddress, recordSource);
1589 }
1590 });
Jungshik Jangbffb0632014-07-22 16:56:52 +09001591 }
1592
1593 @Override
Jungshik Jangb6591b82014-07-23 16:10:23 +09001594 public void stopOneTouchRecord(final int recorderAddress) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001595 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001596 runOnServiceThread(new Runnable() {
1597 @Override
1598 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001599 if (!isTvDeviceEnabled()) {
1600 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001601 return;
1602 }
1603 tv().stopOneTouchRecord(recorderAddress);
1604 }
1605 });
1606 }
1607
1608 @Override
1609 public void startTimerRecording(final int recorderAddress, final int sourceType,
1610 final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001611 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001612 runOnServiceThread(new Runnable() {
1613 @Override
1614 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001615 if (!isTvDeviceEnabled()) {
1616 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001617 return;
1618 }
1619 tv().startTimerRecording(recorderAddress, sourceType, recordSource);
1620 }
1621 });
1622 }
1623
1624 @Override
1625 public void clearTimerRecording(final int recorderAddress, final int sourceType,
1626 final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001627 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001628 runOnServiceThread(new Runnable() {
1629 @Override
1630 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001631 if (!isTvDeviceEnabled()) {
1632 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001633 return;
1634 }
1635 tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
1636 }
1637 });
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09001638 }
Jungshik Jangf4249322014-08-21 14:17:05 +09001639
1640 @Override
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001641 public void sendMhlVendorCommand(final int portId, final int offset, final int length,
Jungshik Jangf4249322014-08-21 14:17:05 +09001642 final byte[] data) {
1643 enforceAccessPermission();
1644 runOnServiceThread(new Runnable() {
1645 @Override
1646 public void run() {
Jungshik Jangf4249322014-08-21 14:17:05 +09001647 if (!isControlEnabled()) {
1648 Slog.w(TAG, "Hdmi control is disabled.");
1649 return ;
1650 }
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001651 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jangf4249322014-08-21 14:17:05 +09001652 if (device == null) {
1653 Slog.w(TAG, "Invalid port id:" + portId);
1654 return;
1655 }
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001656 mMhlController.sendVendorCommand(portId, offset, length, data);
Jungshik Jangf4249322014-08-21 14:17:05 +09001657 }
1658 });
1659 }
1660
1661 @Override
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001662 public void addHdmiMhlVendorCommandListener(
1663 IHdmiMhlVendorCommandListener listener) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001664 enforceAccessPermission();
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001665 HdmiControlService.this.addHdmiMhlVendorCommandListener(listener);
Jungshik Jangf4249322014-08-21 14:17:05 +09001666 }
Terry Heo959d2db2014-08-28 16:45:41 +09001667
1668 @Override
Donghyun Chob3515642017-03-02 13:47:40 +09001669 public void setStandbyMode(final boolean isStandbyModeOn) {
1670 enforceAccessPermission();
1671 runOnServiceThread(new Runnable() {
1672 @Override
1673 public void run() {
1674 HdmiControlService.this.setStandbyMode(isStandbyModeOn);
1675 }
1676 });
1677 }
1678
1679 @Override
Terry Heo959d2db2014-08-28 16:45:41 +09001680 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -06001681 if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) return;
Terry Heo959d2db2014-08-28 16:45:41 +09001682 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
1683
1684 pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
1685 pw.println("mProhibitMode: " + mProhibitMode);
1686 if (mCecController != null) {
1687 pw.println("mCecController: ");
1688 pw.increaseIndent();
1689 mCecController.dump(pw);
1690 pw.decreaseIndent();
1691 }
Jinsuk Kim61c94d12015-01-15 07:00:28 +09001692
1693 pw.println("mMhlController: ");
1694 pw.increaseIndent();
1695 mMhlController.dump(pw);
1696 pw.decreaseIndent();
1697
Terry Heo959d2db2014-08-28 16:45:41 +09001698 pw.println("mPortInfo: ");
1699 pw.increaseIndent();
1700 for (HdmiPortInfo hdmiPortInfo : mPortInfo) {
1701 pw.println("- " + hdmiPortInfo);
1702 }
1703 pw.decreaseIndent();
1704 pw.println("mPowerStatus: " + mPowerStatus);
1705 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001706 }
1707
Jungshik Janga5b74142014-06-23 18:03:10 +09001708 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +09001709 private void oneTouchPlay(final IHdmiControlCallback callback) {
1710 assertRunOnServiceThread();
1711 HdmiCecLocalDevicePlayback source = playback();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001712 if (source == null) {
1713 Slog.w(TAG, "Local playback device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001714 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001715 return;
1716 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09001717 source.oneTouchPlay(callback);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001718 }
1719
Jungshik Janga5b74142014-06-23 18:03:10 +09001720 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +09001721 private void queryDisplayStatus(final IHdmiControlCallback callback) {
1722 assertRunOnServiceThread();
1723 HdmiCecLocalDevicePlayback source = playback();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001724 if (source == null) {
1725 Slog.w(TAG, "Local playback device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001726 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001727 return;
1728 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09001729 source.queryDisplayStatus(callback);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001730 }
1731
Jinsuk Kim3cd30512014-12-04 11:05:09 +09001732 private void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
1733 final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001734 try {
1735 listener.asBinder().linkToDeath(record, 0);
1736 } catch (RemoteException e) {
1737 Slog.w(TAG, "Listener already died");
1738 return;
1739 }
1740 synchronized (mLock) {
1741 mHotplugEventListenerRecords.add(record);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001742 }
Jinsuk Kim3cd30512014-12-04 11:05:09 +09001743
1744 // Inform the listener of the initial state of each HDMI port by generating
1745 // hotplug events.
1746 runOnServiceThread(new Runnable() {
1747 @Override
1748 public void run() {
1749 synchronized (mLock) {
1750 if (!mHotplugEventListenerRecords.contains(record)) return;
1751 }
1752 for (HdmiPortInfo port : mPortInfo) {
1753 HdmiHotplugEvent event = new HdmiHotplugEvent(port.getId(),
1754 mCecController.isConnected(port.getId()));
1755 synchronized (mLock) {
1756 invokeHotplugEventListenerLocked(listener, event);
1757 }
1758 }
1759 }
1760 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001761 }
1762
1763 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
1764 synchronized (mLock) {
1765 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1766 if (record.mListener.asBinder() == listener.asBinder()) {
1767 listener.asBinder().unlinkToDeath(record, 0);
1768 mHotplugEventListenerRecords.remove(record);
1769 break;
1770 }
1771 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001772 }
1773 }
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001774
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001775 private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001776 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
1777 try {
1778 listener.asBinder().linkToDeath(record, 0);
1779 } catch (RemoteException e) {
1780 Slog.w(TAG, "Listener already died");
1781 return;
1782 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001783 synchronized (mLock) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001784 mDeviceEventListenerRecords.add(record);
1785 }
1786 }
1787
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001788 void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001789 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001790 for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001791 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09001792 record.mListener.onStatusChanged(device, status);
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001793 } catch (RemoteException e) {
1794 Slog.e(TAG, "Failed to report device event:" + e);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001795 }
1796 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001797 }
1798 }
1799
Jungshik Jangea67c182014-06-19 22:19:20 +09001800 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
1801 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
1802 listener);
1803 try {
1804 listener.asBinder().linkToDeath(record, 0);
1805 } catch (RemoteException e) {
1806 Slog.w(TAG, "Listener already died");
1807 return;
1808 }
1809 synchronized (mLock) {
Jungshik Jangea67c182014-06-19 22:19:20 +09001810 mSystemAudioModeChangeListenerRecords.add(record);
1811 }
1812 }
1813
1814 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
1815 synchronized (mLock) {
1816 for (SystemAudioModeChangeListenerRecord record :
1817 mSystemAudioModeChangeListenerRecords) {
1818 if (record.mListener.asBinder() == listener) {
1819 listener.asBinder().unlinkToDeath(record, 0);
1820 mSystemAudioModeChangeListenerRecords.remove(record);
1821 break;
1822 }
1823 }
Jungshik Jangea67c182014-06-19 22:19:20 +09001824 }
1825 }
1826
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001827 private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
Jungshik Jangf4249322014-08-21 14:17:05 +09001828 private final IHdmiInputChangeListener mListener;
1829
1830 public InputChangeListenerRecord(IHdmiInputChangeListener listener) {
1831 mListener = listener;
1832 }
1833
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001834 @Override
1835 public void binderDied() {
1836 synchronized (mLock) {
Donghyun Chofbbeb3e2016-04-15 09:12:03 +09001837 if (mInputChangeListenerRecord == this) {
1838 mInputChangeListenerRecord = null;
1839 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001840 }
1841 }
1842 }
1843
1844 private void setInputChangeListener(IHdmiInputChangeListener listener) {
1845 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001846 mInputChangeListenerRecord = new InputChangeListenerRecord(listener);
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001847 try {
1848 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
1849 } catch (RemoteException e) {
1850 Slog.w(TAG, "Listener already died");
1851 return;
1852 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001853 }
1854 }
1855
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001856 void invokeInputChangeListener(HdmiDeviceInfo info) {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001857 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001858 if (mInputChangeListenerRecord != null) {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001859 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09001860 mInputChangeListenerRecord.mListener.onChanged(info);
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001861 } catch (RemoteException e) {
1862 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
1863 }
1864 }
1865 }
1866 }
1867
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001868 private void setHdmiRecordListener(IHdmiRecordListener listener) {
Jungshik Jangb6591b82014-07-23 16:10:23 +09001869 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001870 mRecordListenerRecord = new HdmiRecordListenerRecord(listener);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001871 try {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001872 listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001873 } catch (RemoteException e) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001874 Slog.w(TAG, "Listener already died.", e);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001875 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09001876 }
1877 }
1878
1879 byte[] invokeRecordRequestListener(int recorderAddress) {
1880 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001881 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001882 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09001883 return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001884 } catch (RemoteException e) {
1885 Slog.w(TAG, "Failed to start record.", e);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001886 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09001887 }
1888 return EmptyArray.BYTE;
1889 }
1890 }
1891
Jungshik Jang326aef02014-11-05 12:50:35 +09001892 void invokeOneTouchRecordResult(int recorderAddress, int result) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001893 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001894 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001895 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09001896 mRecordListenerRecord.mListener.onOneTouchRecordResult(recorderAddress, result);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001897 } catch (RemoteException e) {
1898 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
1899 }
1900 }
1901 }
1902 }
1903
Jungshik Jang326aef02014-11-05 12:50:35 +09001904 void invokeTimerRecordingResult(int recorderAddress, int result) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001905 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001906 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001907 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09001908 mRecordListenerRecord.mListener.onTimerRecordingResult(recorderAddress, result);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001909 } catch (RemoteException e) {
Jungshik Jange5a93372014-07-25 13:41:14 +09001910 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
1911 }
1912 }
1913 }
1914 }
1915
Jungshik Jang326aef02014-11-05 12:50:35 +09001916 void invokeClearTimerRecordingResult(int recorderAddress, int result) {
Jungshik Jange5a93372014-07-25 13:41:14 +09001917 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001918 if (mRecordListenerRecord != null) {
Jungshik Jange5a93372014-07-25 13:41:14 +09001919 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09001920 mRecordListenerRecord.mListener.onClearTimerRecordingResult(recorderAddress,
1921 result);
Jungshik Jange5a93372014-07-25 13:41:14 +09001922 } catch (RemoteException e) {
1923 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001924 }
1925 }
1926 }
1927 }
1928
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001929 private void invokeCallback(IHdmiControlCallback callback, int result) {
1930 try {
1931 callback.onComplete(result);
1932 } catch (RemoteException e) {
1933 Slog.e(TAG, "Invoking callback failed:" + e);
1934 }
1935 }
Yuncheol Heo63a2e062014-05-27 23:06:01 +09001936
Jungshik Jangf4249322014-08-21 14:17:05 +09001937 private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener,
Jungshik Jangea67c182014-06-19 22:19:20 +09001938 boolean enabled) {
1939 try {
1940 listener.onStatusChanged(enabled);
1941 } catch (RemoteException e) {
1942 Slog.e(TAG, "Invoking callback failed:" + e);
1943 }
1944 }
1945
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001946 private void announceHotplugEvent(int portId, boolean connected) {
1947 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
Jungshik Jang60cffce2014-06-12 18:03:04 +09001948 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001949 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1950 invokeHotplugEventListenerLocked(record.mListener, event);
Jungshik Jang60cffce2014-06-12 18:03:04 +09001951 }
1952 }
1953 }
1954
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09001955 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
Jungshik Jang60cffce2014-06-12 18:03:04 +09001956 HdmiHotplugEvent event) {
1957 try {
1958 listener.onReceived(event);
1959 } catch (RemoteException e) {
1960 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
1961 }
Jungshik Jange81e1082014-06-05 15:37:59 +09001962 }
1963
Jinsuk Kimf98b9e82015-10-05 14:24:48 +09001964 public HdmiCecLocalDeviceTv tv() {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001965 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
Jungshik Jang79c58a42014-06-16 16:45:36 +09001966 }
1967
Yuncheol Heoe946ed82014-07-25 14:05:19 +09001968 boolean isTvDevice() {
Yuncheol Heob8d62e72014-09-22 19:53:41 +09001969 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
Yuncheol Heoe946ed82014-07-25 14:05:19 +09001970 }
1971
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001972 boolean isTvDeviceEnabled() {
1973 return isTvDevice() && tv() != null;
1974 }
1975
Jungshik Jang79c58a42014-06-16 16:45:36 +09001976 private HdmiCecLocalDevicePlayback playback() {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001977 return (HdmiCecLocalDevicePlayback)
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001978 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
Jungshik Jang60cffce2014-06-12 18:03:04 +09001979 }
Jungshik Janga858d222014-06-23 17:17:47 +09001980
1981 AudioManager getAudioManager() {
1982 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
1983 }
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001984
1985 boolean isControlEnabled() {
1986 synchronized (mLock) {
1987 return mHdmiControlEnabled;
1988 }
1989 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09001990
Jungshik Jangf67113f2014-08-22 16:27:19 +09001991 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09001992 int getPowerStatus() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09001993 assertRunOnServiceThread();
Yuncheol Heo38db6292014-07-01 14:15:14 +09001994 return mPowerStatus;
1995 }
1996
Jungshik Jangf67113f2014-08-22 16:27:19 +09001997 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09001998 boolean isPowerOnOrTransient() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09001999 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002000 return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
2001 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002002 }
2003
Jungshik Jangf67113f2014-08-22 16:27:19 +09002004 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09002005 boolean isPowerStandbyOrTransient() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09002006 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002007 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
2008 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002009 }
2010
Jungshik Jangf67113f2014-08-22 16:27:19 +09002011 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09002012 boolean isPowerStandby() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09002013 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002014 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002015 }
2016
2017 @ServiceThreadOnly
2018 void wakeUp() {
2019 assertRunOnServiceThread();
Yuncheol Heofc44e4e2014-08-04 19:41:09 +09002020 mWakeUpMessageReceived = true;
Dianne Hackborn280a64e2015-07-13 14:48:08 -07002021 mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.server.hdmi:WAKE");
Yuncheol Heo38db6292014-07-01 14:15:14 +09002022 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
2023 // the intent, the sequence will continue at onWakeUp().
2024 }
2025
2026 @ServiceThreadOnly
2027 void standby() {
2028 assertRunOnServiceThread();
Donghyun Cho02920a02016-10-11 17:17:34 +09002029 if (!canGoToStandby()) {
2030 return;
2031 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002032 mStandbyMessageReceived = true;
Jinsuk Kime26d8332015-01-09 08:55:41 +09002033 mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
Yuncheol Heo38db6292014-07-01 14:15:14 +09002034 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
2035 // the intent, the sequence will continue at onStandby().
2036 }
2037
Donghyun Choafd26a22016-12-23 15:53:28 +09002038 boolean isWakeUpMessageReceived() {
2039 return mWakeUpMessageReceived;
2040 }
2041
Yuncheol Heo38db6292014-07-01 14:15:14 +09002042 @ServiceThreadOnly
2043 private void onWakeUp() {
2044 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002045 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002046 if (mCecController != null) {
Jungshik Janga9f10622014-07-11 15:36:39 +09002047 if (mHdmiControlEnabled) {
Yuncheol Heofc44e4e2014-08-04 19:41:09 +09002048 int startReason = INITIATED_BY_SCREEN_ON;
2049 if (mWakeUpMessageReceived) {
2050 startReason = INITIATED_BY_WAKE_UP_MESSAGE;
2051 }
2052 initializeCec(startReason);
Jungshik Janga9f10622014-07-11 15:36:39 +09002053 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002054 } else {
2055 Slog.i(TAG, "Device does not support HDMI-CEC.");
2056 }
2057 // TODO: Initialize MHL local devices.
2058 }
2059
2060 @ServiceThreadOnly
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +09002061 private void onStandby(final int standbyAction) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002062 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002063 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
Yuncheol Heo0608b932014-10-13 16:39:18 +09002064 invokeVendorCommandListenersOnControlStateChanged(false,
2065 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);
Donghyun Cho02920a02016-10-11 17:17:34 +09002066 if (!canGoToStandby()) {
2067 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
2068 return;
2069 }
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002070
2071 final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
2072 disableDevices(new PendingActionClearedCallback() {
2073 @Override
2074 public void onCleared(HdmiCecLocalDevice device) {
2075 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
2076 devices.remove(device);
2077 if (devices.isEmpty()) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +09002078 onStandbyCompleted(standbyAction);
Yuncheol Heo4b542712014-07-30 20:31:06 +09002079 // We will not clear local devices here, since some OEM/SOC will keep passing
2080 // the received packets until the application processor enters to the sleep
2081 // actually.
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002082 }
2083 }
2084 });
2085 }
2086
Jinsuk Kime26d8332015-01-09 08:55:41 +09002087 private boolean canGoToStandby() {
2088 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2089 if (!device.canGoToStandby()) return false;
2090 }
2091 return true;
2092 }
2093
Terry Heo1ca0a432014-08-18 10:30:32 +09002094 @ServiceThreadOnly
2095 private void onLanguageChanged(String language) {
2096 assertRunOnServiceThread();
2097 mLanguage = language;
2098
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002099 if (isTvDeviceEnabled()) {
Terry Heo1ca0a432014-08-18 10:30:32 +09002100 tv().broadcastMenuLanguage(language);
Donghyun Chobc6e3722016-11-04 05:25:52 +09002101 mCecController.setLanguage(language);
Terry Heo1ca0a432014-08-18 10:30:32 +09002102 }
2103 }
2104
Jungshik Jangf67113f2014-08-22 16:27:19 +09002105 @ServiceThreadOnly
2106 String getLanguage() {
2107 assertRunOnServiceThread();
2108 return mLanguage;
2109 }
2110
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002111 private void disableDevices(PendingActionClearedCallback callback) {
Jungshik Jang350e68d2014-08-19 18:56:21 +09002112 if (mCecController != null) {
2113 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2114 device.disableDevice(mStandbyMessageReceived, callback);
2115 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002116 }
Jungshik Jang350e68d2014-08-19 18:56:21 +09002117
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09002118 mMhlController.clearAllLocalDevices();
Yuncheol Heo38db6292014-07-01 14:15:14 +09002119 }
2120
Yuncheol Heo38db6292014-07-01 14:15:14 +09002121 @ServiceThreadOnly
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002122 private void clearLocalDevices() {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002123 assertRunOnServiceThread();
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002124 if (mCecController == null) {
2125 return;
2126 }
2127 mCecController.clearLogicalAddress();
2128 mCecController.clearLocalDevices();
2129 }
2130
2131 @ServiceThreadOnly
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +09002132 private void onStandbyCompleted(int standbyAction) {
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002133 assertRunOnServiceThread();
2134 Slog.v(TAG, "onStandbyCompleted");
2135
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002136 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002137 return;
2138 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002139 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002140 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +09002141 device.onStandby(mStandbyMessageReceived, standbyAction);
Yuncheol Heo38db6292014-07-01 14:15:14 +09002142 }
2143 mStandbyMessageReceived = false;
Donghyun Chobc6e3722016-11-04 05:25:52 +09002144 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false);
Jinsuk Kim5b8cb002015-01-19 07:30:12 +09002145 mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
Yuncheol Heo38db6292014-07-01 14:15:14 +09002146 }
Jinsuk Kim4d43d932014-07-03 16:43:58 +09002147
Jinsuk Kim119160a2014-07-07 18:48:10 +09002148 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
2149 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
2150 try {
2151 listener.asBinder().linkToDeath(record, 0);
2152 } catch (RemoteException e) {
2153 Slog.w(TAG, "Listener already died");
2154 return;
2155 }
2156 synchronized (mLock) {
2157 mVendorCommandListenerRecords.add(record);
2158 }
2159 }
2160
Yuncheol Heo0608b932014-10-13 16:39:18 +09002161 boolean invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress,
2162 byte[] params, boolean hasVendorId) {
Jinsuk Kim119160a2014-07-07 18:48:10 +09002163 synchronized (mLock) {
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09002164 if (mVendorCommandListenerRecords.isEmpty()) {
2165 return false;
2166 }
Jinsuk Kim119160a2014-07-07 18:48:10 +09002167 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2168 if (record.mDeviceType != deviceType) {
2169 continue;
2170 }
2171 try {
Yuncheol Heo0608b932014-10-13 16:39:18 +09002172 record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId);
Jinsuk Kim119160a2014-07-07 18:48:10 +09002173 } catch (RemoteException e) {
2174 Slog.e(TAG, "Failed to notify vendor command reception", e);
2175 }
2176 }
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09002177 return true;
Jinsuk Kim119160a2014-07-07 18:48:10 +09002178 }
2179 }
2180
Yuncheol Heo0608b932014-10-13 16:39:18 +09002181 boolean invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason) {
2182 synchronized (mLock) {
2183 if (mVendorCommandListenerRecords.isEmpty()) {
2184 return false;
2185 }
2186 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2187 try {
2188 record.mListener.onControlStateChanged(enabled, reason);
2189 } catch (RemoteException e) {
2190 Slog.e(TAG, "Failed to notify control-state-changed to vendor handler", e);
2191 }
2192 }
2193 return true;
2194 }
2195 }
2196
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002197 private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {
2198 HdmiMhlVendorCommandListenerRecord record =
2199 new HdmiMhlVendorCommandListenerRecord(listener);
Jungshik Jangf4249322014-08-21 14:17:05 +09002200 try {
2201 listener.asBinder().linkToDeath(record, 0);
2202 } catch (RemoteException e) {
2203 Slog.w(TAG, "Listener already died.");
2204 return;
2205 }
2206
2207 synchronized (mLock) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002208 mMhlVendorCommandListenerRecords.add(record);
Jungshik Jangf4249322014-08-21 14:17:05 +09002209 }
2210 }
2211
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002212 void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002213 synchronized (mLock) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002214 for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002215 try {
2216 record.mListener.onReceived(portId, offest, length, data);
2217 } catch (RemoteException e) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002218 Slog.e(TAG, "Failed to notify MHL vendor command", e);
Jungshik Jangf4249322014-08-21 14:17:05 +09002219 }
2220 }
2221 }
2222 }
2223
Donghyun Chob3515642017-03-02 13:47:40 +09002224 void setStandbyMode(boolean isStandbyModeOn) {
2225 assertRunOnServiceThread();
2226 if (isPowerOnOrTransient() && isStandbyModeOn) {
2227 mPowerManager.goToSleep(SystemClock.uptimeMillis(),
2228 PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
2229 if (playback() != null) {
2230 playback().sendStandby(0 /* unused */);
2231 }
2232 } else if (isPowerStandbyOrTransient() && !isStandbyModeOn) {
2233 mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.server.hdmi:WAKE");
2234 if (playback() != null) {
2235 oneTouchPlay(new IHdmiControlCallback.Stub() {
2236 @Override
2237 public void onComplete(int result) {
2238 if (result != HdmiControlManager.RESULT_SUCCESS) {
2239 Slog.w(TAG, "Failed to complete 'one touch play'. result=" + result);
2240 }
2241 }
2242 });
2243 }
2244 }
2245 }
2246
Jinsuk Kim4d43d932014-07-03 16:43:58 +09002247 boolean isProhibitMode() {
2248 synchronized (mLock) {
2249 return mProhibitMode;
2250 }
2251 }
2252
2253 void setProhibitMode(boolean enabled) {
2254 synchronized (mLock) {
2255 mProhibitMode = enabled;
2256 }
2257 }
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002258
2259 @ServiceThreadOnly
Donghyun Chobc6e3722016-11-04 05:25:52 +09002260 void setCecOption(int key, boolean value) {
Jinsuk Kim50084862014-08-07 13:11:40 +09002261 assertRunOnServiceThread();
2262 mCecController.setOption(key, value);
2263 }
2264
2265 @ServiceThreadOnly
2266 void setControlEnabled(boolean enabled) {
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002267 assertRunOnServiceThread();
2268
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002269 synchronized (mLock) {
2270 mHdmiControlEnabled = enabled;
2271 }
2272
2273 if (enabled) {
Yuncheol Heof1702482014-11-27 19:52:01 +09002274 enableHdmiControlService();
2275 return;
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002276 }
Yuncheol Heof1702482014-11-27 19:52:01 +09002277 // Call the vendor handler before the service is disabled.
2278 invokeVendorCommandListenersOnControlStateChanged(false,
2279 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING);
2280 // Post the remained tasks in the service thread again to give the vendor-issued-tasks
2281 // a chance to run.
2282 runOnServiceThread(new Runnable() {
2283 @Override
2284 public void run() {
2285 disableHdmiControlService();
2286 }
2287 });
2288 return;
2289 }
2290
2291 @ServiceThreadOnly
2292 private void enableHdmiControlService() {
Donghyun Chobc6e3722016-11-04 05:25:52 +09002293 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
Yuncheol Heof1702482014-11-27 19:52:01 +09002294 mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED);
2295
2296 initializeCec(INITIATED_BY_ENABLE_CEC);
2297 }
2298
2299 @ServiceThreadOnly
2300 private void disableHdmiControlService() {
2301 disableDevices(new PendingActionClearedCallback() {
2302 @Override
2303 public void onCleared(HdmiCecLocalDevice device) {
2304 assertRunOnServiceThread();
2305 mCecController.flush(new Runnable() {
2306 @Override
2307 public void run() {
Donghyun Chobc6e3722016-11-04 05:25:52 +09002308 mCecController.setOption(OptionKey.ENABLE_CEC, false);
Yuncheol Heof1702482014-11-27 19:52:01 +09002309 mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED);
2310 clearLocalDevices();
2311 }
2312 });
2313 }
2314 });
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002315 }
Jungshik Jang867b4e02014-08-12 13:41:30 +09002316
2317 @ServiceThreadOnly
2318 void setActivePortId(int portId) {
2319 assertRunOnServiceThread();
2320 mActivePortId = portId;
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002321
2322 // Resets last input for MHL, which stays valid only after the MHL device was selected,
2323 // and no further switching is done.
2324 setLastInputForMhl(Constants.INVALID_PORT_ID);
Jungshik Jang867b4e02014-08-12 13:41:30 +09002325 }
Yuncheol Heo08a1be82014-08-12 20:58:41 +09002326
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002327 @ServiceThreadOnly
2328 void setLastInputForMhl(int portId) {
2329 assertRunOnServiceThread();
2330 mLastInputMhl = portId;
2331 }
2332
2333 @ServiceThreadOnly
2334 int getLastInputForMhl() {
2335 assertRunOnServiceThread();
2336 return mLastInputMhl;
2337 }
2338
2339 /**
2340 * Performs input change, routing control for MHL device.
2341 *
2342 * @param portId MHL port, or the last port to go back to if {@code contentOn} is false
2343 * @param contentOn {@code true} if RAP data is content on; otherwise false
2344 */
2345 @ServiceThreadOnly
2346 void changeInputForMhl(int portId, boolean contentOn) {
2347 assertRunOnServiceThread();
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002348 if (tv() == null) return;
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002349 final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID;
Jinsuk Kimcb8661c2015-01-19 12:39:06 +09002350 if (portId != Constants.INVALID_PORT_ID) {
2351 tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() {
2352 @Override
2353 public void onComplete(int result) throws RemoteException {
2354 // Keep the last input to switch back later when RAP[ContentOff] is received.
2355 // This effectively sets the port to invalid one if the switching is for
2356 // RAP[ContentOff].
2357 setLastInputForMhl(lastInput);
2358 }
2359 });
2360 }
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002361 // MHL device is always directly connected to the port. Update the active port ID to avoid
2362 // unnecessary post-routing control task.
2363 tv().setActivePortId(portId);
2364
2365 // The port is either the MHL-enabled port where the mobile device is connected, or
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09002366 // the last port to go back to when turnoff command is received. Note that the last port
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002367 // may not be the MHL-enabled one. In this case the device info to be passed to
2368 // input change listener should be the one describing the corresponding HDMI port.
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09002369 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jinsuk Kimcb8661c2015-01-19 12:39:06 +09002370 HdmiDeviceInfo info = (device != null) ? device.getInfo()
2371 : mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002372 invokeInputChangeListener(info);
2373 }
2374
2375 void setMhlInputChangeEnabled(boolean enabled) {
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09002376 mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
Yuncheol Heo08a1be82014-08-12 20:58:41 +09002377
2378 synchronized (mLock) {
2379 mMhlInputChangeEnabled = enabled;
2380 }
2381 }
2382
2383 boolean isMhlInputChangeEnabled() {
2384 synchronized (mLock) {
2385 return mMhlInputChangeEnabled;
2386 }
2387 }
Jungshik Jang339227d2014-08-25 15:37:20 +09002388
2389 @ServiceThreadOnly
2390 void displayOsd(int messageId) {
2391 assertRunOnServiceThread();
2392 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2393 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2394 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2395 HdmiControlService.PERMISSION);
2396 }
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09002397
2398 @ServiceThreadOnly
2399 void displayOsd(int messageId, int extra) {
2400 assertRunOnServiceThread();
2401 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2402 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
Yuncheol Heo2b0da5c2014-10-22 14:32:27 +09002403 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra);
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09002404 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2405 HdmiControlService.PERMISSION);
2406 }
Jungshik Jang0792d372014-04-23 17:57:26 +09002407}