blob: f3a1e46bc1e439c4eda44a89ffcafcfecf0c6a57 [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;
Shubang Lu00b976a2018-08-01 18:11:46 -070021
Amy17ee20f2018-10-11 11:08:23 -070022import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH;
Amy6f031af2018-10-30 16:38:33 -070023import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED;
Jinsuk Kim50084862014-08-07 13:11:40 +090024import static com.android.server.hdmi.Constants.DISABLED;
25import static com.android.server.hdmi.Constants.ENABLED;
Jinsuk Kim50084862014-08-07 13:11:40 +090026import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE;
27import static com.android.server.hdmi.Constants.OPTION_MHL_INPUT_SWITCHING;
28import static com.android.server.hdmi.Constants.OPTION_MHL_POWER_CHARGE;
Jinsuk Kim5b8cb002015-01-19 07:30:12 +090029import static com.android.server.hdmi.Constants.OPTION_MHL_SERVICE_CONTROL;
Amy3288f8a2018-10-23 18:55:34 -070030import static com.android.server.power.ShutdownThread.SHUTDOWN_ACTION_PROPERTY;
Jinsuk Kim50084862014-08-07 13:11:40 +090031
Jungshik Jang0792d372014-04-23 17:57:26 +090032import android.annotation.Nullable;
Yuncheol Heo38db6292014-07-01 14:15:14 +090033import android.content.BroadcastReceiver;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +090034import android.content.ContentResolver;
Jungshik Jang0792d372014-04-23 17:57:26 +090035import android.content.Context;
Yuncheol Heo38db6292014-07-01 14:15:14 +090036import android.content.Intent;
37import android.content.IntentFilter;
Jinsuk Kim50084862014-08-07 13:11:40 +090038import android.database.ContentObserver;
Jinsuk Kimc0c20d02014-07-04 14:34:31 +090039import android.hardware.hdmi.HdmiControlManager;
Yuncheol Heo7d9acc72014-08-12 15:30:49 +090040import android.hardware.hdmi.HdmiDeviceInfo;
Jungshik Jang60cffce2014-06-12 18:03:04 +090041import android.hardware.hdmi.HdmiHotplugEvent;
Jinsuk Kim0340bbc2014-06-05 11:07:47 +090042import android.hardware.hdmi.HdmiPortInfo;
Jungshik Jangd643f762014-05-22 19:28:09 +090043import android.hardware.hdmi.IHdmiControlCallback;
44import android.hardware.hdmi.IHdmiControlService;
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +090045import android.hardware.hdmi.IHdmiDeviceEventListener;
Jungshik Jangd643f762014-05-22 19:28:09 +090046import android.hardware.hdmi.IHdmiHotplugEventListener;
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +090047import android.hardware.hdmi.IHdmiInputChangeListener;
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +090048import android.hardware.hdmi.IHdmiMhlVendorCommandListener;
Jungshik Jang12e5dce2014-07-24 15:27:44 +090049import android.hardware.hdmi.IHdmiRecordListener;
Jungshik Jangea67c182014-06-19 22:19:20 +090050import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
Jinsuk Kim119160a2014-07-07 18:48:10 +090051import android.hardware.hdmi.IHdmiVendorCommandListener;
Donghyun Chobc6e3722016-11-04 05:25:52 +090052import android.hardware.tv.cec.V1_0.OptionKey;
53import android.hardware.tv.cec.V1_0.SendMessageResult;
Jungshik Janga858d222014-06-23 17:17:47 +090054import android.media.AudioManager;
Jinsuk Kim7fa3a662014-11-07 15:20:24 +090055import android.media.tv.TvInputManager;
56import android.media.tv.TvInputManager.TvInputCallback;
Jinsuk Kim50084862014-08-07 13:11:40 +090057import android.net.Uri;
Jungshik Jang42c98002014-06-12 13:17:44 +090058import android.os.Build;
Jungshik Jang67ea5212014-05-15 14:05:24 +090059import android.os.Handler;
Jungshik Jang0792d372014-04-23 17:57:26 +090060import android.os.HandlerThread;
Jinsuk Kim78d695d2014-05-13 16:36:15 +090061import android.os.IBinder;
Jungshik Jange9c77c82014-04-24 20:30:09 +090062import android.os.Looper;
Yuncheol Heo38db6292014-07-01 14:15:14 +090063import android.os.PowerManager;
Jinsuk Kim78d695d2014-05-13 16:36:15 +090064import android.os.RemoteException;
Yuncheol Heo38db6292014-07-01 14:15:14 +090065import android.os.SystemClock;
Yuncheol Heo7d9acc72014-08-12 15:30:49 +090066import android.os.SystemProperties;
Jinsuk Kim50084862014-08-07 13:11:40 +090067import android.os.UserHandle;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +090068import android.provider.Settings.Global;
Yuncheol Heo7d9acc72014-08-12 15:30:49 +090069import android.text.TextUtils;
Jinsuk Kim2b152012014-07-25 08:22:26 +090070import android.util.ArraySet;
Jungshik Jang0792d372014-04-23 17:57:26 +090071import android.util.Slog;
Jungshik Jang3ee65722014-06-03 16:22:30 +090072import android.util.SparseArray;
Jungshik Jang8b308d92014-05-29 21:52:28 +090073import android.util.SparseIntArray;
Shubang Lu00b976a2018-08-01 18:11:46 -070074
Jinsuk Kim4893c7e2014-06-19 14:13:22 +090075import com.android.internal.annotations.GuardedBy;
Amy1d0b1372018-05-24 14:36:25 -070076import com.android.internal.annotations.VisibleForTesting;
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060077import com.android.internal.util.DumpUtils;
Terry Heo959d2db2014-08-28 16:45:41 +090078import com.android.internal.util.IndentingPrintWriter;
Jungshik Jang0792d372014-04-23 17:57:26 +090079import com.android.server.SystemService;
Jungshik Janga5b74142014-06-23 18:03:10 +090080import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
Jungshik Jang3ee65722014-06-03 16:22:30 +090081import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
Jinsuk Kim7e742062014-07-30 13:19:13 +090082import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
Jungshik Jang4fc1d102014-07-09 19:24:50 +090083import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback;
Shubang Lu00b976a2018-08-01 18:11:46 -070084
85import libcore.util.EmptyArray;
86
Terry Heo959d2db2014-08-28 16:45:41 +090087import java.io.FileDescriptor;
88import java.io.PrintWriter;
Jinsuk Kim78d695d2014-05-13 16:36:15 +090089import java.util.ArrayList;
Jinsuk Kimf4eb72d2014-07-25 13:02:51 +090090import java.util.Arrays;
Jinsuk Kim0340bbc2014-06-05 11:07:47 +090091import java.util.Collections;
Kyeongkab.Namec0aac92018-10-10 13:47:29 +090092import java.util.HashMap;
Jungshik Jang02bb4262014-05-23 16:48:31 +090093import java.util.List;
Terry Heo1ca0a432014-08-18 10:30:32 +090094import java.util.Locale;
Kyeongkab.Namec0aac92018-10-10 13:47:29 +090095import java.util.Map;
Jungshik Janga1fa91f2014-05-08 20:56:41 +090096
Jungshik Jang0792d372014-04-23 17:57:26 +090097/**
98 * Provides a service for sending and processing HDMI control messages,
99 * HDMI-CEC and MHL control command, and providing the information on both standard.
100 */
Amyd4f98992018-05-11 17:59:06 -0700101public class HdmiControlService extends SystemService {
Jungshik Jang0792d372014-04-23 17:57:26 +0900102 private static final String TAG = "HdmiControlService";
Jinsuk Kim5fe3a6c2014-10-29 07:02:06 +0900103 private final Locale HONG_KONG = new Locale("zh", "HK");
104 private final Locale MACAU = new Locale("zh", "MO");
Jungshik Jang0792d372014-04-23 17:57:26 +0900105
Kyeongkab.Namec0aac92018-10-10 13:47:29 +0900106 private static final Map<String, String> mTerminologyToBibliographicMap;
107 static {
108 mTerminologyToBibliographicMap = new HashMap<>();
109 // NOTE: (TERMINOLOGY_CODE, BIBLIOGRAPHIC_CODE)
110 mTerminologyToBibliographicMap.put("sqi", "alb"); // Albanian
111 mTerminologyToBibliographicMap.put("hye", "arm"); // Armenian
112 mTerminologyToBibliographicMap.put("eus", "baq"); // Basque
113 mTerminologyToBibliographicMap.put("mya", "bur"); // Burmese
114 mTerminologyToBibliographicMap.put("ces", "cze"); // Czech
115 mTerminologyToBibliographicMap.put("nld", "dut"); // Dutch
116 mTerminologyToBibliographicMap.put("kat", "geo"); // Georgian
117 mTerminologyToBibliographicMap.put("deu", "ger"); // German
118 mTerminologyToBibliographicMap.put("ell", "gre"); // Greek
119 mTerminologyToBibliographicMap.put("fra", "fre"); // French
120 mTerminologyToBibliographicMap.put("isl", "ice"); // Icelandic
121 mTerminologyToBibliographicMap.put("mkd", "mac"); // Macedonian
122 mTerminologyToBibliographicMap.put("mri", "mao"); // Maori
123 mTerminologyToBibliographicMap.put("msa", "may"); // Malay
124 mTerminologyToBibliographicMap.put("fas", "per"); // Persian
125 mTerminologyToBibliographicMap.put("ron", "rum"); // Romanian
126 mTerminologyToBibliographicMap.put("slk", "slo"); // Slovak
127 mTerminologyToBibliographicMap.put("bod", "tib"); // Tibetan
128 mTerminologyToBibliographicMap.put("cym", "wel"); // Welsh
129 }
130
Jinsuk Kimc7eba0f2014-07-07 14:18:02 +0900131 static final String PERMISSION = "android.permission.HDMI_CEC";
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900132
Shubanga3f59502018-06-05 16:32:53 -0700133 // The reason code to initiate initializeCec().
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900134 static final int INITIATED_BY_ENABLE_CEC = 0;
135 static final int INITIATED_BY_BOOT_UP = 1;
136 static final int INITIATED_BY_SCREEN_ON = 2;
137 static final int INITIATED_BY_WAKE_UP_MESSAGE = 3;
Yuncheol Heob5021862014-09-02 10:36:04 +0900138 static final int INITIATED_BY_HOTPLUG = 4;
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900139
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900140 // The reason code representing the intent action that drives the standby
141 // procedure. The procedure starts either by Intent.ACTION_SCREEN_OFF or
142 // Intent.ACTION_SHUTDOWN.
143 static final int STANDBY_SCREEN_OFF = 0;
144 static final int STANDBY_SHUTDOWN = 1;
145
Amy123ec402018-09-25 10:56:31 -0700146 // Logical address of the active source.
147 @GuardedBy("mLock")
148 protected final ActiveSource mActiveSource = new ActiveSource();
149
Amy02f31152018-08-28 15:05:42 -0700150 private static final boolean isHdmiCecNeverClaimPlaybackLogicAddr =
151 SystemProperties.getBoolean(
152 Constants.PROPERTY_HDMI_CEC_NEVER_CLAIM_PLAYBACK_LOGICAL_ADDRESS, false);
153
Jungshik Jangd643f762014-05-22 19:28:09 +0900154 /**
155 * Interface to report send result.
156 */
157 interface SendMessageCallback {
158 /**
159 * Called when {@link HdmiControlService#sendCecCommand} is completed.
160 *
Yuncheol Heoece603b2014-05-23 20:10:19 +0900161 * @param error result of send request.
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900162 * <ul>
Donghyun Chobc6e3722016-11-04 05:25:52 +0900163 * <li>{@link SendMessageResult#SUCCESS}
164 * <li>{@link SendMessageResult#NACK}
165 * <li>{@link SendMessageResult#BUSY}
166 * <li>{@link SendMessageResult#FAIL}
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900167 * </ul>
Jungshik Jangd643f762014-05-22 19:28:09 +0900168 */
Jungshik Jangd643f762014-05-22 19:28:09 +0900169 void onSendCompleted(int error);
170 }
171
Jungshik Jang02bb4262014-05-23 16:48:31 +0900172 /**
173 * Interface to get a list of available logical devices.
174 */
175 interface DevicePollingCallback {
176 /**
177 * Called when device polling is finished.
178 *
179 * @param ackedAddress a list of logical addresses of available devices
180 */
181 void onPollingFinished(List<Integer> ackedAddress);
182 }
183
Terry Heo1ca0a432014-08-18 10:30:32 +0900184 private class HdmiControlBroadcastReceiver extends BroadcastReceiver {
Jungshik Jangf67113f2014-08-22 16:27:19 +0900185 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +0900186 @Override
187 public void onReceive(Context context, Intent intent) {
Jungshik Jangf67113f2014-08-22 16:27:19 +0900188 assertRunOnServiceThread();
Amy3288f8a2018-10-23 18:55:34 -0700189 boolean isReboot = SystemProperties.get(SHUTDOWN_ACTION_PROPERTY).contains("1");
Yuncheol Heo38db6292014-07-01 14:15:14 +0900190 switch (intent.getAction()) {
191 case Intent.ACTION_SCREEN_OFF:
Amy3288f8a2018-10-23 18:55:34 -0700192 if (isPowerOnOrTransient() && !isReboot) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900193 onStandby(STANDBY_SCREEN_OFF);
Yuncheol Heo38db6292014-07-01 14:15:14 +0900194 }
195 break;
196 case Intent.ACTION_SCREEN_ON:
197 if (isPowerStandbyOrTransient()) {
198 onWakeUp();
199 }
200 break;
Terry Heo1ca0a432014-08-18 10:30:32 +0900201 case Intent.ACTION_CONFIGURATION_CHANGED:
Jinsuk Kim5fe3a6c2014-10-29 07:02:06 +0900202 String language = getMenuLanguage();
Terry Heo1ca0a432014-08-18 10:30:32 +0900203 if (!mLanguage.equals(language)) {
204 onLanguageChanged(language);
205 }
206 break;
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900207 case Intent.ACTION_SHUTDOWN:
Amy3288f8a2018-10-23 18:55:34 -0700208 if (isPowerOnOrTransient() && !isReboot) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900209 onStandby(STANDBY_SHUTDOWN);
210 }
211 break;
Yuncheol Heo38db6292014-07-01 14:15:14 +0900212 }
213 }
Jinsuk Kim5fe3a6c2014-10-29 07:02:06 +0900214
215 private String getMenuLanguage() {
216 Locale locale = Locale.getDefault();
217 if (locale.equals(Locale.TAIWAN) || locale.equals(HONG_KONG) || locale.equals(MACAU)) {
218 // Android always returns "zho" for all Chinese variants.
219 // Use "bibliographic" code defined in CEC639-2 for traditional
220 // Chinese used in Taiwan/Hong Kong/Macau.
221 return "chi";
222 } else {
Kyeongkab.Namec0aac92018-10-10 13:47:29 +0900223 String language = locale.getISO3Language();
224
225 // locale.getISO3Language() returns terminology code and need to
226 // send it as bibliographic code instead since the Bibliographic
227 // codes of ISO/FDIS 639-2 shall be used.
228 // NOTE: Chinese also has terminology/bibliographic code "zho" and "chi"
229 // But, as it depends on the locale, is not handled here.
230 if (mTerminologyToBibliographicMap.containsKey(language)) {
231 language = mTerminologyToBibliographicMap.get(language);
232 }
233
234 return language;
Jinsuk Kim5fe3a6c2014-10-29 07:02:06 +0900235 }
236 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900237 }
238
Jungshik Jang0792d372014-04-23 17:57:26 +0900239 // A thread to handle synchronous IO of CEC and MHL control service.
240 // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
241 // and sparse call it shares a thread to handle IO operations.
242 private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
243
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900244 // Used to synchronize the access to the service.
245 private final Object mLock = new Object();
246
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900247 // Type of logical devices hosted in the system. Stored in the unmodifiable list.
248 private final List<Integer> mLocalDevices;
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900249
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900250 // List of records for hotplug event listener to handle the the caller killed in action.
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900251 @GuardedBy("mLock")
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900252 private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
253 new ArrayList<>();
254
Jungshik Jangf4249322014-08-21 14:17:05 +0900255 // List of records for device event listener to handle the caller killed in action.
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900256 @GuardedBy("mLock")
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +0900257 private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
258 new ArrayList<>();
259
Jungshik Jangf4249322014-08-21 14:17:05 +0900260 // List of records for vendor command listener to handle the caller killed in action.
Jinsuk Kim119160a2014-07-07 18:48:10 +0900261 @GuardedBy("mLock")
262 private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords =
263 new ArrayList<>();
264
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +0900265 @GuardedBy("mLock")
266 private InputChangeListenerRecord mInputChangeListenerRecord;
267
Jungshik Jangb6591b82014-07-23 16:10:23 +0900268 @GuardedBy("mLock")
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900269 private HdmiRecordListenerRecord mRecordListenerRecord;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900270
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900271 // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
272 // handling will be disabled and no request will be handled.
273 @GuardedBy("mLock")
274 private boolean mHdmiControlEnabled;
275
Jinsuk Kim4d43d932014-07-03 16:43:58 +0900276 // Set to true while the service is in normal mode. While set to false, no input change is
277 // allowed. Used for situations where input change can confuse users such as channel auto-scan,
278 // system upgrade, etc., a.k.a. "prohibit mode".
279 @GuardedBy("mLock")
280 private boolean mProhibitMode;
281
Jungshik Jangea67c182014-06-19 22:19:20 +0900282 // List of records for system audio mode change to handle the the caller killed in action.
283 private final ArrayList<SystemAudioModeChangeListenerRecord>
284 mSystemAudioModeChangeListenerRecords = new ArrayList<>();
285
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900286 // Handler used to run a task in service thread.
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900287 private final Handler mHandler = new Handler();
288
Jinsuk Kim50084862014-08-07 13:11:40 +0900289 private final SettingsObserver mSettingsObserver;
290
Jungshik Jangf4249322014-08-21 14:17:05 +0900291 private final HdmiControlBroadcastReceiver
292 mHdmiControlBroadcastReceiver = new HdmiControlBroadcastReceiver();
293
Jungshik Jang0792d372014-04-23 17:57:26 +0900294 @Nullable
295 private HdmiCecController mCecController;
296
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900297 // HDMI port information. Stored in the unmodifiable list to keep the static information
298 // from being modified.
299 private List<HdmiPortInfo> mPortInfo;
300
Jinsuk Kim2b152012014-07-25 08:22:26 +0900301 // Map from path(physical address) to port ID.
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900302 private UnmodifiableSparseIntArray mPortIdMap;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900303
304 // Map from port ID to HdmiPortInfo.
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900305 private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900306
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900307 // Map from port ID to HdmiDeviceInfo.
308 private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap;
309
Yuncheol Heo75a77e72014-07-09 18:27:53 +0900310 private HdmiCecMessageValidator mMessageValidator;
311
Yuncheol Heo38db6292014-07-01 14:15:14 +0900312 @ServiceThreadOnly
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900313 private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +0900314
315 @ServiceThreadOnly
Terry Heo1ca0a432014-08-18 10:30:32 +0900316 private String mLanguage = Locale.getDefault().getISO3Language();
317
318 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +0900319 private boolean mStandbyMessageReceived = false;
320
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900321 @ServiceThreadOnly
322 private boolean mWakeUpMessageReceived = false;
323
Jungshik Jang867b4e02014-08-12 13:41:30 +0900324 @ServiceThreadOnly
325 private int mActivePortId = Constants.INVALID_PORT_ID;
326
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900327 // Set to true while the input change by MHL is allowed.
328 @GuardedBy("mLock")
329 private boolean mMhlInputChangeEnabled;
330
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +0900331 // List of records for MHL Vendor command listener to handle the caller killed in action.
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900332 @GuardedBy("mLock")
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +0900333 private final ArrayList<HdmiMhlVendorCommandListenerRecord>
334 mMhlVendorCommandListenerRecords = new ArrayList<>();
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900335
336 @GuardedBy("mLock")
337 private List<HdmiDeviceInfo> mMhlDevices;
338
339 @Nullable
Jinsuk Kim78104122014-08-26 19:32:34 +0900340 private HdmiMhlControllerStub mMhlController;
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900341
Jinsuk Kim7fa3a662014-11-07 15:20:24 +0900342 @Nullable
343 private TvInputManager mTvInputManager;
344
Jinsuk Kime26d8332015-01-09 08:55:41 +0900345 @Nullable
346 private PowerManager mPowerManager;
347
Amy1d0b1372018-05-24 14:36:25 -0700348 @Nullable
349 private Looper mIoLooper;
350
Amyd58d0aa2018-10-18 14:08:57 -0700351 // Thread safe physical address
352 @GuardedBy("mLock")
353 private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
354
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900355 // Last input port before switching to the MHL port. Should switch back to this port
356 // when the mobile device sends the request one touch play with off.
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900357 // Gets invalidated if we go to other port/input.
358 @ServiceThreadOnly
359 private int mLastInputMhl = Constants.INVALID_PORT_ID;
360
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900361 // Set to true if the logical address allocation is completed.
362 private boolean mAddressAllocated = false;
363
364 // Buffer for processing the incoming cec messages while allocating logical addresses.
365 private final class CecMessageBuffer {
366 private List<HdmiCecMessage> mBuffer = new ArrayList<>();
367
Kyeongkab.Nama15539e2018-09-14 13:55:55 +0900368 public boolean bufferMessage(HdmiCecMessage message) {
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900369 switch (message.getOpcode()) {
370 case Constants.MESSAGE_ACTIVE_SOURCE:
371 bufferActiveSource(message);
Kyeongkab.Nama15539e2018-09-14 13:55:55 +0900372 return true;
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900373 case Constants.MESSAGE_IMAGE_VIEW_ON:
374 case Constants.MESSAGE_TEXT_VIEW_ON:
375 bufferImageOrTextViewOn(message);
Kyeongkab.Nama15539e2018-09-14 13:55:55 +0900376 return true;
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900377 // Add here if new message that needs to buffer
378 default:
379 // Do not need to buffer messages other than above
Kyeongkab.Nama15539e2018-09-14 13:55:55 +0900380 return false;
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900381 }
382 }
383
384 public void processMessages() {
385 for (final HdmiCecMessage message : mBuffer) {
386 runOnServiceThread(new Runnable() {
387 @Override
388 public void run() {
389 handleCecCommand(message);
390 }
391 });
392 }
393 mBuffer.clear();
394 }
395
396 private void bufferActiveSource(HdmiCecMessage message) {
397 if (!replaceMessageIfBuffered(message, Constants.MESSAGE_ACTIVE_SOURCE)) {
398 mBuffer.add(message);
399 }
400 }
401
402 private void bufferImageOrTextViewOn(HdmiCecMessage message) {
403 if (!replaceMessageIfBuffered(message, Constants.MESSAGE_IMAGE_VIEW_ON) &&
404 !replaceMessageIfBuffered(message, Constants.MESSAGE_TEXT_VIEW_ON)) {
405 mBuffer.add(message);
406 }
407 }
408
409 // Returns true if the message is replaced
410 private boolean replaceMessageIfBuffered(HdmiCecMessage message, int opcode) {
411 for (int i = 0; i < mBuffer.size(); i++) {
412 HdmiCecMessage bufferedMessage = mBuffer.get(i);
413 if (bufferedMessage.getOpcode() == opcode) {
414 mBuffer.set(i, message);
415 return true;
416 }
417 }
418 return false;
419 }
420 }
421
Jinsuk Kimf98b9e82015-10-05 14:24:48 +0900422 private final CecMessageBuffer mCecMessageBuffer = new CecMessageBuffer();
423
424 private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer();
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900425
Jungshik Jang0792d372014-04-23 17:57:26 +0900426 public HdmiControlService(Context context) {
427 super(context);
Yuncheol Heo7d9acc72014-08-12 15:30:49 +0900428 mLocalDevices = getIntList(SystemProperties.get(Constants.PROPERTY_DEVICE_TYPE));
Jinsuk Kim50084862014-08-07 13:11:40 +0900429 mSettingsObserver = new SettingsObserver(mHandler);
Jungshik Jang0792d372014-04-23 17:57:26 +0900430 }
431
Amy8027c942018-09-18 10:23:20 -0700432 protected static List<Integer> getIntList(String string) {
Yuncheol Heo7d9acc72014-08-12 15:30:49 +0900433 ArrayList<Integer> list = new ArrayList<>();
434 TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
435 splitter.setString(string);
436 for (String item : splitter) {
437 try {
438 list.add(Integer.parseInt(item));
439 } catch (NumberFormatException e) {
440 Slog.w(TAG, "Can't parseInt: " + item);
441 }
442 }
443 return Collections.unmodifiableList(list);
444 }
445
Jungshik Jang0792d372014-04-23 17:57:26 +0900446 @Override
447 public void onStart() {
Amy1d0b1372018-05-24 14:36:25 -0700448 if (mIoLooper == null) {
449 mIoThread.start();
450 mIoLooper = mIoThread.getLooper();
451 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900452 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900453 mProhibitMode = false;
454 mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
Yuncheol Heo08a1be82014-08-12 20:58:41 +0900455 mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900456
Amy1d0b1372018-05-24 14:36:25 -0700457 if (mCecController == null) {
458 mCecController = HdmiCecController.create(this);
459 }
Jinsuk Kima8a5e502014-05-15 16:51:49 +0900460 if (mCecController != null) {
Jungshik Janga9f10622014-07-11 15:36:39 +0900461 if (mHdmiControlEnabled) {
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900462 initializeCec(INITIATED_BY_BOOT_UP);
Amy718e41e2018-08-17 17:23:37 -0700463 } else {
464 mCecController.setOption(OptionKey.ENABLE_CEC, false);
Jungshik Janga9f10622014-07-11 15:36:39 +0900465 }
Jinsuk Kima8a5e502014-05-15 16:51:49 +0900466 } else {
Jungshik Jang0792d372014-04-23 17:57:26 +0900467 Slog.i(TAG, "Device does not support HDMI-CEC.");
Jinsuk Kim08f1ab02014-10-13 10:38:16 +0900468 return;
Jungshik Jang0792d372014-04-23 17:57:26 +0900469 }
Amy4e7ff1a2018-06-07 16:24:31 -0700470 if (mMhlController == null) {
471 mMhlController = HdmiMhlControllerStub.create(this);
472 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900473 if (!mMhlController.isReady()) {
Jungshik Jang0792d372014-04-23 17:57:26 +0900474 Slog.i(TAG, "Device does not support MHL-control.");
475 }
Jinsuk Kimed086452014-08-18 15:01:53 +0900476 mMhlDevices = Collections.emptyList();
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900477
478 initPortInfo();
Amy1d0b1372018-05-24 14:36:25 -0700479 if (mMessageValidator == null) {
480 mMessageValidator = new HdmiCecMessageValidator(this);
481 }
Jinsuk Kim8692fc62014-05-29 07:39:22 +0900482 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900483
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900484 if (mCecController != null) {
Yuncheol Heo0608b932014-10-13 16:39:18 +0900485 // Register broadcast receiver for power state change.
Yuncheol Heo38db6292014-07-01 14:15:14 +0900486 IntentFilter filter = new IntentFilter();
487 filter.addAction(Intent.ACTION_SCREEN_OFF);
488 filter.addAction(Intent.ACTION_SCREEN_ON);
Rob McConnell30595562016-03-11 11:03:00 +0000489 filter.addAction(Intent.ACTION_SHUTDOWN);
Terry Heo1ca0a432014-08-18 10:30:32 +0900490 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
491 getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter);
Yuncheol Heo0608b932014-10-13 16:39:18 +0900492
493 // Register ContentObserver to monitor the settings change.
494 registerContentObserver();
Yuncheol Heo38db6292014-07-01 14:15:14 +0900495 }
Jinsuk Kim5b8cb002015-01-19 07:30:12 +0900496 mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED);
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900497 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900498
Amy1d0b1372018-05-24 14:36:25 -0700499 @VisibleForTesting
500 void setCecController(HdmiCecController cecController) {
501 mCecController = cecController;
502 }
503
Amy4e7ff1a2018-06-07 16:24:31 -0700504 @VisibleForTesting
505 void setHdmiMhlController(HdmiMhlControllerStub hdmiMhlController) {
506 mMhlController = hdmiMhlController;
507 }
508
Jinsuk Kim7fa3a662014-11-07 15:20:24 +0900509 @Override
510 public void onBootPhase(int phase) {
511 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
512 mTvInputManager = (TvInputManager) getContext().getSystemService(
513 Context.TV_INPUT_SERVICE);
Jinsuk Kime26d8332015-01-09 08:55:41 +0900514 mPowerManager = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
Jinsuk Kim7fa3a662014-11-07 15:20:24 +0900515 }
516 }
517
518 TvInputManager getTvInputManager() {
519 return mTvInputManager;
520 }
521
522 void registerTvInputCallback(TvInputCallback callback) {
523 if (mTvInputManager == null) return;
524 mTvInputManager.registerCallback(callback, mHandler);
525 }
526
527 void unregisterTvInputCallback(TvInputCallback callback) {
528 if (mTvInputManager == null) return;
529 mTvInputManager.unregisterCallback(callback);
530 }
531
Jinsuk Kime26d8332015-01-09 08:55:41 +0900532 PowerManager getPowerManager() {
533 return mPowerManager;
534 }
535
Yuncheol Heo25c20292014-07-31 17:59:39 +0900536 /**
537 * Called when the initialization of local devices is complete.
538 */
Yuncheol Heo0608b932014-10-13 16:39:18 +0900539 private void onInitializeCecComplete(int initiatedBy) {
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900540 if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
541 mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
542 }
543 mWakeUpMessageReceived = false;
544
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900545 if (isTvDeviceEnabled()) {
Donghyun Chobc6e3722016-11-04 05:25:52 +0900546 mCecController.setOption(OptionKey.WAKEUP, tv().getAutoWakeup());
Yuncheol Heo0608b932014-10-13 16:39:18 +0900547 }
548 int reason = -1;
549 switch (initiatedBy) {
550 case INITIATED_BY_BOOT_UP:
551 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_START;
552 break;
553 case INITIATED_BY_ENABLE_CEC:
554 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING;
555 break;
556 case INITIATED_BY_SCREEN_ON:
557 case INITIATED_BY_WAKE_UP_MESSAGE:
558 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_WAKEUP;
559 break;
560 }
561 if (reason != -1) {
562 invokeVendorCommandListenersOnControlStateChanged(true, reason);
Yuncheol Heo25c20292014-07-31 17:59:39 +0900563 }
564 }
565
Jinsuk Kim50084862014-08-07 13:11:40 +0900566 private void registerContentObserver() {
567 ContentResolver resolver = getContext().getContentResolver();
568 String[] settings = new String[] {
569 Global.HDMI_CONTROL_ENABLED,
570 Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
571 Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
Donghyun Choc1fa9af2016-12-27 18:31:09 +0900572 Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED,
Jinsuk Kim50084862014-08-07 13:11:40 +0900573 Global.MHL_INPUT_SWITCHING_ENABLED,
Amy0c2e29f2018-10-23 12:17:52 -0700574 Global.MHL_POWER_CHARGE_ENABLED,
575 Global.HDMI_CEC_SWITCH_ENABLED
Jinsuk Kim50084862014-08-07 13:11:40 +0900576 };
Jungshik Jang5691b2f2014-08-18 16:50:12 +0900577 for (String s : settings) {
Jinsuk Kim50084862014-08-07 13:11:40 +0900578 resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
579 UserHandle.USER_ALL);
580 }
581 }
582
583 private class SettingsObserver extends ContentObserver {
584 public SettingsObserver(Handler handler) {
585 super(handler);
586 }
587
Jungshik Jangf67113f2014-08-22 16:27:19 +0900588 // onChange is set up to run in service thread.
Jinsuk Kim50084862014-08-07 13:11:40 +0900589 @Override
590 public void onChange(boolean selfChange, Uri uri) {
591 String option = uri.getLastPathSegment();
592 boolean enabled = readBooleanSetting(option, true);
593 switch (option) {
594 case Global.HDMI_CONTROL_ENABLED:
595 setControlEnabled(enabled);
596 break;
597 case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900598 if (isTvDeviceEnabled()) {
599 tv().setAutoWakeup(enabled);
600 }
Donghyun Chobc6e3722016-11-04 05:25:52 +0900601 setCecOption(OptionKey.WAKEUP, enabled);
Jinsuk Kim50084862014-08-07 13:11:40 +0900602 break;
603 case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900604 for (int type : mLocalDevices) {
605 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
Terry Heodd371ec2015-12-10 15:31:05 +0900606 if (localDevice != null) {
607 localDevice.setAutoDeviceOff(enabled);
608 }
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900609 }
Jinsuk Kim50084862014-08-07 13:11:40 +0900610 // No need to propagate to HAL.
611 break;
Donghyun Choc1fa9af2016-12-27 18:31:09 +0900612 case Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED:
613 if (isTvDeviceEnabled()) {
614 tv().setSystemAudioControlFeatureEnabled(enabled);
Donghyun Cho2601f8d2016-03-25 20:18:06 +0900615 }
Amy79db52f2018-10-23 12:45:17 -0700616 if (isAudioSystemDevice()) {
Amy8ba147c2018-11-16 10:59:18 -0800617 if (audioSystem() == null) {
618 Slog.e(TAG, "Audio System device has not registered yet."
619 + " Can't turn system audio mode on.");
620 break;
621 }
Amy79db52f2018-10-23 12:45:17 -0700622 audioSystem().onSystemAduioControlFeatureSupportChanged(enabled);
623 }
Donghyun Cho2601f8d2016-03-25 20:18:06 +0900624 break;
Amy0c2e29f2018-10-23 12:17:52 -0700625 case Global.HDMI_CEC_SWITCH_ENABLED:
626 if (isAudioSystemDevice()) {
Amy7f69d672018-11-09 15:46:47 -0800627 if (audioSystem() == null) {
628 Slog.w(TAG, "Switch device has not registered yet."
629 + " Can't turn routing on.");
630 break;
631 }
Amy0c2e29f2018-10-23 12:17:52 -0700632 audioSystem().setRoutingControlFeatureEnables(enabled);
633 }
634 break;
Jinsuk Kim50084862014-08-07 13:11:40 +0900635 case Global.MHL_INPUT_SWITCHING_ENABLED:
Yuncheol Heo08a1be82014-08-12 20:58:41 +0900636 setMhlInputChangeEnabled(enabled);
Jinsuk Kim50084862014-08-07 13:11:40 +0900637 break;
638 case Global.MHL_POWER_CHARGE_ENABLED:
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900639 mMhlController.setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled));
Jinsuk Kim50084862014-08-07 13:11:40 +0900640 break;
641 }
642 }
643 }
644
645 private static int toInt(boolean enabled) {
646 return enabled ? ENABLED : DISABLED;
647 }
648
Amyaa8ae682018-12-12 16:11:50 -0800649 @VisibleForTesting
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900650 boolean readBooleanSetting(String key, boolean defVal) {
651 ContentResolver cr = getContext().getContentResolver();
Jinsuk Kim50084862014-08-07 13:11:40 +0900652 return Global.getInt(cr, key, toInt(defVal)) == ENABLED;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900653 }
654
655 void writeBooleanSetting(String key, boolean value) {
656 ContentResolver cr = getContext().getContentResolver();
Jinsuk Kim50084862014-08-07 13:11:40 +0900657 Global.putInt(cr, key, toInt(value));
658 }
659
Amy59c06c12019-01-18 15:35:15 -0800660 void writeStringSystemProperty(String key, String value) {
661 SystemProperties.set(key, value);
662 }
663
664 @VisibleForTesting
665 boolean readBooleanSystemProperty(String key, boolean defVal) {
666 return SystemProperties.getBoolean(key, defVal);
Amyaa8ae682018-12-12 16:11:50 -0800667 }
668
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900669 private void initializeCec(int initiatedBy) {
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900670 mAddressAllocated = false;
Donghyun Chobc6e3722016-11-04 05:25:52 +0900671 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
672 mCecController.setLanguage(mLanguage);
Yuncheol Heob5021862014-09-02 10:36:04 +0900673 initializeLocalDevices(initiatedBy);
Jungshik Janga9f10622014-07-11 15:36:39 +0900674 }
675
Jungshik Janga5b74142014-06-23 18:03:10 +0900676 @ServiceThreadOnly
Yuncheol Heob5021862014-09-02 10:36:04 +0900677 private void initializeLocalDevices(final int initiatedBy) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900678 assertRunOnServiceThread();
Yuncheol Heob5021862014-09-02 10:36:04 +0900679 // A container for [Device type, Local device info].
680 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
Yuncheol Heob5021862014-09-02 10:36:04 +0900681 for (int type : mLocalDevices) {
Amy02f31152018-08-28 15:05:42 -0700682 if (type == HdmiDeviceInfo.DEVICE_PLAYBACK
683 && isHdmiCecNeverClaimPlaybackLogicAddr) {
684 continue;
685 }
Jinsuk Kim6f87b4e2014-10-10 14:40:29 +0900686 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
687 if (localDevice == null) {
688 localDevice = HdmiCecLocalDevice.create(this, type);
689 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900690 localDevice.init();
Yuncheol Heob5021862014-09-02 10:36:04 +0900691 localDevices.add(localDevice);
692 }
Jinsuk Kim6f87b4e2014-10-10 14:40:29 +0900693 // It's now safe to flush existing local devices from mCecController since they were
694 // already moved to 'localDevices'.
695 clearLocalDevices();
Yuncheol Heob5021862014-09-02 10:36:04 +0900696 allocateLogicalAddress(localDevices, initiatedBy);
697 }
698
699 @ServiceThreadOnly
Amy4e7ff1a2018-06-07 16:24:31 -0700700 @VisibleForTesting
701 protected void allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices,
Yuncheol Heob5021862014-09-02 10:36:04 +0900702 final int initiatedBy) {
703 assertRunOnServiceThread();
Yuncheol Heo89ec14e2014-09-16 15:53:59 +0900704 mCecController.clearLogicalAddress();
Yuncheol Heob5021862014-09-02 10:36:04 +0900705 final ArrayList<HdmiCecLocalDevice> allocatedDevices = new ArrayList<>();
706 final int[] finished = new int[1];
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900707 mAddressAllocated = allocatingDevices.isEmpty();
708
Jinsuk Kimf98b9e82015-10-05 14:24:48 +0900709 // For TV device, select request can be invoked while address allocation or device
710 // discovery is in progress. Initialize the request here at the start of allocation,
711 // and process the collected requests later when the allocation and device discovery
712 // is all completed.
713 mSelectRequestBuffer.clear();
714
Yuncheol Heob5021862014-09-02 10:36:04 +0900715 for (final HdmiCecLocalDevice localDevice : allocatingDevices) {
716 mCecController.allocateLogicalAddress(localDevice.getType(),
Jungshik Jang3ee65722014-06-03 16:22:30 +0900717 localDevice.getPreferredAddress(), new AllocateAddressCallback() {
718 @Override
719 public void onAllocated(int deviceType, int logicalAddress) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900720 if (logicalAddress == Constants.ADDR_UNREGISTERED) {
Jungshik Jang3ee65722014-06-03 16:22:30 +0900721 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
722 } else {
Jungshik Jang410ca9c2014-08-07 18:04:14 +0900723 // Set POWER_STATUS_ON to all local devices because they share lifetime
724 // with system.
725 HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
726 HdmiControlManager.POWER_STATUS_ON);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900727 localDevice.setDeviceInfo(deviceInfo);
728 mCecController.addLocalDevice(deviceType, localDevice);
729 mCecController.addLogicalAddress(logicalAddress);
Yuncheol Heob5021862014-09-02 10:36:04 +0900730 allocatedDevices.add(localDevice);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900731 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900732
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900733 // Address allocation completed for all devices. Notify each device.
Yuncheol Heob5021862014-09-02 10:36:04 +0900734 if (allocatingDevices.size() == ++finished[0]) {
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900735 mAddressAllocated = true;
Yuncheol Heob5021862014-09-02 10:36:04 +0900736 if (initiatedBy != INITIATED_BY_HOTPLUG) {
737 // In case of the hotplug we don't call onInitializeCecComplete()
738 // since we reallocate the logical address only.
Yuncheol Heo0608b932014-10-13 16:39:18 +0900739 onInitializeCecComplete(initiatedBy);
Yuncheol Heob5021862014-09-02 10:36:04 +0900740 }
741 notifyAddressAllocated(allocatedDevices, initiatedBy);
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900742 mCecMessageBuffer.processMessages();
Jungshik Jang3ee65722014-06-03 16:22:30 +0900743 }
744 }
745 });
746 }
747 }
748
Jungshik Janga5b74142014-06-23 18:03:10 +0900749 @ServiceThreadOnly
Yuncheol Heob5021862014-09-02 10:36:04 +0900750 private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900751 assertRunOnServiceThread();
Yuncheol Heob5021862014-09-02 10:36:04 +0900752 for (HdmiCecLocalDevice device : devices) {
753 int address = device.getDeviceInfo().getLogicalAddress();
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900754 device.handleAddressAllocated(address, initiatedBy);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900755 }
Jinsuk Kimf98b9e82015-10-05 14:24:48 +0900756 if (isTvDeviceEnabled()) {
757 tv().setSelectRequestBuffer(mSelectRequestBuffer);
758 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900759 }
760
Donghyun Chofc462b92016-05-13 21:06:02 +0900761 boolean isAddressAllocated() {
762 return mAddressAllocated;
763 }
764
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900765 // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
766 // keep them in one place.
Jungshik Janga5b74142014-06-23 18:03:10 +0900767 @ServiceThreadOnly
Amy4e7ff1a2018-06-07 16:24:31 -0700768 @VisibleForTesting
769 protected void initPortInfo() {
Jungshik Janga5b74142014-06-23 18:03:10 +0900770 assertRunOnServiceThread();
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900771 HdmiPortInfo[] cecPortInfo = null;
772
Amyd58d0aa2018-10-18 14:08:57 -0700773 synchronized (mLock) {
774 mPhysicalAddress = getPhysicalAddress();
775 }
776
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900777 // CEC HAL provides majority of the info while MHL does only MHL support flag for
778 // each port. Return empty array if CEC HAL didn't provide the info.
779 if (mCecController != null) {
780 cecPortInfo = mCecController.getPortInfos();
781 }
782 if (cecPortInfo == null) {
Jinsuk Kim2b152012014-07-25 08:22:26 +0900783 return;
784 }
785
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900786 SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
787 SparseIntArray portIdMap = new SparseIntArray();
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900788 SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>();
Jinsuk Kim2b152012014-07-25 08:22:26 +0900789 for (HdmiPortInfo info : cecPortInfo) {
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900790 portIdMap.put(info.getAddress(), info.getId());
791 portInfoMap.put(info.getId(), info);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900792 portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900793 }
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900794 mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
795 mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900796 mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900797
Amya00e1182018-10-17 16:45:03 -0700798 if (mMhlController == null) {
799 return;
800 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900801 HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
802 ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
803 for (HdmiPortInfo info : mhlPortInfo) {
804 if (info.isMhlSupported()) {
805 mhlSupportedPorts.add(info.getId());
806 }
807 }
808
809 // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use
810 // cec port info if we do not have have port that supports MHL.
811 if (mhlSupportedPorts.isEmpty()) {
Jinsuk Kimf4eb72d2014-07-25 13:02:51 +0900812 mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo));
813 return;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900814 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900815 ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
816 for (HdmiPortInfo info : cecPortInfo) {
817 if (mhlSupportedPorts.contains(info.getId())) {
818 result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
819 info.isCecSupported(), true, info.isArcSupported()));
820 } else {
821 result.add(info);
822 }
823 }
824 mPortInfo = Collections.unmodifiableList(result);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900825 }
826
Jungshik Jang2738e2d2014-08-19 09:30:05 +0900827 List<HdmiPortInfo> getPortInfo() {
828 return mPortInfo;
829 }
830
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900831 /**
832 * Returns HDMI port information for the given port id.
833 *
834 * @param portId HDMI port id
835 * @return {@link HdmiPortInfo} for the given port
836 */
837 HdmiPortInfo getPortInfo(int portId) {
Jinsuk Kim2b152012014-07-25 08:22:26 +0900838 return mPortInfoMap.get(portId, null);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900839 }
840
Jungshik Jange9c77c82014-04-24 20:30:09 +0900841 /**
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900842 * Returns the routing path (physical address) of the HDMI port for the given
843 * port id.
844 */
845 int portIdToPath(int portId) {
846 HdmiPortInfo portInfo = getPortInfo(portId);
847 if (portInfo == null) {
848 Slog.e(TAG, "Cannot find the port info: " + portId);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900849 return Constants.INVALID_PHYSICAL_ADDRESS;
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900850 }
851 return portInfo.getAddress();
852 }
853
854 /**
Amya00e1182018-10-17 16:45:03 -0700855 * Returns the id of HDMI port located at the current device that runs this method.
856 *
857 * For TV with physical address 0x0000, target device 0x1120, we want port physical address
858 * 0x1000 to get the correct port id from {@link #mPortIdMap}. For device with Physical Address
859 * 0x2000, target device 0x2420, we want port address 0x24000 to get the port id.
860 *
861 * <p>Return {@link Constants#INVALID_PORT_ID} if target device does not connect to.
862 *
863 * @param path the target device's physical address.
864 * @return the id of the port that the target device eventually connects to
865 * on the current device.
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900866 */
867 int pathToPortId(int path) {
Amya00e1182018-10-17 16:45:03 -0700868 int mask = 0xF000;
869 int finalMask = 0xF000;
Amyf7782b32018-11-16 11:21:03 -0800870 int physicalAddress;
871 synchronized (mLock) {
872 physicalAddress = mPhysicalAddress;
873 }
Amy3eaa85f2018-10-18 14:17:47 -0700874 int maskedAddress = physicalAddress;
875
876 while (maskedAddress != 0) {
877 maskedAddress = physicalAddress & mask;
Amya00e1182018-10-17 16:45:03 -0700878 finalMask |= mask;
Amy3eaa85f2018-10-18 14:17:47 -0700879 mask >>= 4;
Amya00e1182018-10-17 16:45:03 -0700880 }
Amy3eaa85f2018-10-18 14:17:47 -0700881
Amya00e1182018-10-17 16:45:03 -0700882 int portAddress = path & finalMask;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900883 return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900884 }
885
Jinsuk Kim09ffc842014-07-11 17:04:32 +0900886 boolean isValidPortId(int portId) {
Jinsuk Kim2b152012014-07-25 08:22:26 +0900887 return getPortInfo(portId) != null;
Jinsuk Kim09ffc842014-07-11 17:04:32 +0900888 }
889
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900890 /**
Jungshik Jange9c77c82014-04-24 20:30:09 +0900891 * Returns {@link Looper} for IO operation.
892 *
893 * <p>Declared as package-private.
894 */
Amy1d0b1372018-05-24 14:36:25 -0700895 @Nullable
Jungshik Jange9c77c82014-04-24 20:30:09 +0900896 Looper getIoLooper() {
Amy1d0b1372018-05-24 14:36:25 -0700897 return mIoLooper;
898 }
899
900 @VisibleForTesting
901 void setIoLooper(Looper ioLooper) {
902 mIoLooper = ioLooper;
903 }
904
905 @VisibleForTesting
906 void setMessageValidator(HdmiCecMessageValidator messageValidator) {
907 mMessageValidator = messageValidator;
Jungshik Jange9c77c82014-04-24 20:30:09 +0900908 }
909
910 /**
911 * Returns {@link Looper} of main thread. Use this {@link Looper} instance
912 * for tasks that are running on main service thread.
913 *
914 * <p>Declared as package-private.
915 */
916 Looper getServiceLooper() {
Jungshik Jang67ea5212014-05-15 14:05:24 +0900917 return mHandler.getLooper();
Jungshik Jange9c77c82014-04-24 20:30:09 +0900918 }
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900919
920 /**
Jungshik Jang3ee65722014-06-03 16:22:30 +0900921 * Returns physical address of the device.
922 */
923 int getPhysicalAddress() {
924 return mCecController.getPhysicalAddress();
925 }
926
927 /**
928 * Returns vendor id of CEC service.
929 */
930 int getVendorId() {
931 return mCecController.getVendorId();
932 }
933
Jungshik Janga5b74142014-06-23 18:03:10 +0900934 @ServiceThreadOnly
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900935 HdmiDeviceInfo getDeviceInfo(int logicalAddress) {
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900936 assertRunOnServiceThread();
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900937 return tv() == null ? null : tv().getCecDeviceInfo(logicalAddress);
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900938 }
939
Jinsuk Kim6ad7cbd2015-01-06 11:30:56 +0900940 @ServiceThreadOnly
941 HdmiDeviceInfo getDeviceInfoByPort(int port) {
942 assertRunOnServiceThread();
943 HdmiMhlLocalDeviceStub info = mMhlController.getLocalDevice(port);
944 if (info != null) {
945 return info.getInfo();
946 }
947 return null;
948 }
949
Jungshik Jang3ee65722014-06-03 16:22:30 +0900950 /**
Jungshik Jang092b4452014-06-11 15:19:17 +0900951 * Returns version of CEC.
952 */
953 int getCecVersion() {
954 return mCecController.getVersion();
955 }
956
957 /**
Jungshik Jang60cffce2014-06-12 18:03:04 +0900958 * Whether a device of the specified physical address is connected to ARC enabled port.
959 */
960 boolean isConnectedToArcPort(int physicalAddress) {
Jungshik Jang339227d2014-08-25 15:37:20 +0900961 int portId = pathToPortId(physicalAddress);
Jinsuk Kim2b152012014-07-25 08:22:26 +0900962 if (portId != Constants.INVALID_PORT_ID) {
963 return mPortInfoMap.get(portId).isArcSupported();
Jungshik Jang60cffce2014-06-12 18:03:04 +0900964 }
965 return false;
966 }
967
Jinsuk Kim7b0cf642015-04-14 09:43:45 +0900968 @ServiceThreadOnly
969 boolean isConnected(int portId) {
970 assertRunOnServiceThread();
971 return mCecController.isConnected(portId);
972 }
973
Jungshik Jang79c58a42014-06-16 16:45:36 +0900974 void runOnServiceThread(Runnable runnable) {
Jungshik Jang67ea5212014-05-15 14:05:24 +0900975 mHandler.post(runnable);
976 }
977
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900978 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
979 mHandler.postAtFrontOfQueue(runnable);
980 }
981
982 private void assertRunOnServiceThread() {
983 if (Looper.myLooper() != mHandler.getLooper()) {
984 throw new IllegalStateException("Should run on service thread.");
985 }
986 }
987
Jungshik Jang67ea5212014-05-15 14:05:24 +0900988 /**
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900989 * Transmit a CEC command to CEC bus.
990 *
991 * @param command CEC command to send out
Jungshik Jangd643f762014-05-22 19:28:09 +0900992 * @param callback interface used to the result of send command
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900993 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900994 @ServiceThreadOnly
Jungshik Jangd643f762014-05-22 19:28:09 +0900995 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900996 assertRunOnServiceThread();
Yuncheol Heo4c212892014-09-12 14:32:46 +0900997 if (mMessageValidator.isValid(command) == HdmiCecMessageValidator.OK) {
Jungshik Jang5f75cbd2014-08-07 12:02:29 +0900998 mCecController.sendCommand(command, callback);
999 } else {
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09001000 HdmiLogger.error("Invalid message type:" + command);
Jungshik Jang5f75cbd2014-08-07 12:02:29 +09001001 if (callback != null) {
Donghyun Chobc6e3722016-11-04 05:25:52 +09001002 callback.onSendCompleted(SendMessageResult.FAIL);
Jungshik Jang5f75cbd2014-08-07 12:02:29 +09001003 }
1004 }
Jungshik Jangd643f762014-05-22 19:28:09 +09001005 }
1006
Jungshik Janga5b74142014-06-23 18:03:10 +09001007 @ServiceThreadOnly
Jungshik Jangd643f762014-05-22 19:28:09 +09001008 void sendCecCommand(HdmiCecMessage command) {
Jungshik Janga5b74142014-06-23 18:03:10 +09001009 assertRunOnServiceThread();
Jungshik Jang5f75cbd2014-08-07 12:02:29 +09001010 sendCecCommand(command, null);
Jinsuk Kimc70d2292014-04-30 15:43:16 +09001011 }
1012
Yuncheol Heo6aae6522014-08-05 14:48:37 +09001013 /**
1014 * Send <Feature Abort> command on the given CEC message if possible.
1015 * If the aborted message is invalid, then it wont send the message.
1016 * @param command original command to be aborted
1017 * @param reason reason of feature abort
1018 */
1019 @ServiceThreadOnly
1020 void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) {
1021 assertRunOnServiceThread();
1022 mCecController.maySendFeatureAbortCommand(command, reason);
1023 }
1024
Jungshik Janga5b74142014-06-23 18:03:10 +09001025 @ServiceThreadOnly
Jungshik Janga1fa91f2014-05-08 20:56:41 +09001026 boolean handleCecCommand(HdmiCecMessage message) {
Jungshik Janga5b74142014-06-23 18:03:10 +09001027 assertRunOnServiceThread();
Yuncheol Heo4c212892014-09-12 14:32:46 +09001028 int errorCode = mMessageValidator.isValid(message);
1029 if (errorCode != HdmiCecMessageValidator.OK) {
Yuncheol Heoa95f1a92014-11-06 08:25:39 +09001030 // We'll not response on the messages with the invalid source or destination
1031 // or with parameter length shorter than specified in the standard.
Yuncheol Heo4c212892014-09-12 14:32:46 +09001032 if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) {
1033 maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
1034 }
1035 return true;
Yuncheol Heo75a77e72014-07-09 18:27:53 +09001036 }
Kyeongkab.Nama15539e2018-09-14 13:55:55 +09001037
1038 if (dispatchMessageToLocalDevice(message)) {
1039 return true;
1040 }
1041
1042 return (!mAddressAllocated) ? mCecMessageBuffer.bufferMessage(message) : false;
Jungshik Jang092b4452014-06-11 15:19:17 +09001043 }
1044
Donghyun Chobc6e3722016-11-04 05:25:52 +09001045 void enableAudioReturnChannel(int portId, boolean enabled) {
1046 mCecController.enableAudioReturnChannel(portId, enabled);
Jungshik Jang60cffce2014-06-12 18:03:04 +09001047 }
1048
Jungshik Janga5b74142014-06-23 18:03:10 +09001049 @ServiceThreadOnly
Jungshik Jang092b4452014-06-11 15:19:17 +09001050 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
Jungshik Janga5b74142014-06-23 18:03:10 +09001051 assertRunOnServiceThread();
Jungshik Jang092b4452014-06-11 15:19:17 +09001052 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jungshik Jang79c58a42014-06-16 16:45:36 +09001053 if (device.dispatchMessage(message)
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001054 && message.getDestination() != Constants.ADDR_BROADCAST) {
Jungshik Jang092b4452014-06-11 15:19:17 +09001055 return true;
1056 }
1057 }
Jungshik Jang60cffce2014-06-12 18:03:04 +09001058
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001059 if (message.getDestination() != Constants.ADDR_BROADCAST) {
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09001060 HdmiLogger.warning("Unhandled cec command:" + message);
Jungshik Jang3a959fc2014-07-03 09:34:05 +09001061 }
Jungshik Jang092b4452014-06-11 15:19:17 +09001062 return false;
Jungshik Janga1fa91f2014-05-08 20:56:41 +09001063 }
1064
Jungshik Jang67ea5212014-05-15 14:05:24 +09001065 /**
1066 * Called when a new hotplug event is issued.
1067 *
Jinsuk Kimed086452014-08-18 15:01:53 +09001068 * @param portId hdmi port number where hot plug event issued.
Jungshik Jang67ea5212014-05-15 14:05:24 +09001069 * @param connected whether to be plugged in or not
1070 */
Jungshik Janga5b74142014-06-23 18:03:10 +09001071 @ServiceThreadOnly
Jinsuk Kimed086452014-08-18 15:01:53 +09001072 void onHotplug(int portId, boolean connected) {
Jungshik Jang60cffce2014-06-12 18:03:04 +09001073 assertRunOnServiceThread();
Yuncheol Heob5021862014-09-02 10:36:04 +09001074
Amy59176da2018-10-12 16:30:54 -07001075 if (connected && !isTvDevice()
1076 && getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
1077 if (isSwitchDevice()) {
Amy066db152018-10-04 09:54:51 -07001078 initPortInfo();
1079 HdmiLogger.debug("initPortInfo for switch device when onHotplug from tx.");
1080 }
Yuncheol Heob8d62e72014-09-22 19:53:41 +09001081 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
1082 for (int type : mLocalDevices) {
Amy02f31152018-08-28 15:05:42 -07001083 if (type == HdmiDeviceInfo.DEVICE_PLAYBACK
1084 && isHdmiCecNeverClaimPlaybackLogicAddr) {
1085 continue;
1086 }
Yuncheol Heob8d62e72014-09-22 19:53:41 +09001087 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
1088 if (localDevice == null) {
1089 localDevice = HdmiCecLocalDevice.create(this, type);
1090 localDevice.init();
1091 }
1092 localDevices.add(localDevice);
Yuncheol Heob5021862014-09-02 10:36:04 +09001093 }
Yuncheol Heob8d62e72014-09-22 19:53:41 +09001094 allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
Yuncheol Heob5021862014-09-02 10:36:04 +09001095 }
Yuncheol Heob5021862014-09-02 10:36:04 +09001096
Jungshik Jang79c58a42014-06-16 16:45:36 +09001097 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jinsuk Kimed086452014-08-18 15:01:53 +09001098 device.onHotplug(portId, connected);
Jungshik Jang60cffce2014-06-12 18:03:04 +09001099 }
Jinsuk Kimed086452014-08-18 15:01:53 +09001100 announceHotplugEvent(portId, connected);
Jungshik Jang67ea5212014-05-15 14:05:24 +09001101 }
1102
Jungshik Jang02bb4262014-05-23 16:48:31 +09001103 /**
1104 * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
1105 * devices.
1106 *
1107 * @param callback an interface used to get a list of all remote devices' address
Jungshik Jang1de51422014-07-03 11:14:26 +09001108 * @param sourceAddress a logical address of source device where sends polling message
Jungshik Jang0f8b4b72014-05-28 17:58:58 +09001109 * @param pickStrategy strategy how to pick polling candidates
Jungshik Jang02bb4262014-05-23 16:48:31 +09001110 * @param retryCount the number of retry used to send polling message to remote devices
Jakub Pawlowskib0b3a642018-12-05 10:55:57 +01001111 * @throws IllegalArgumentException if {@code pickStrategy} is invalid value
Jungshik Jang02bb4262014-05-23 16:48:31 +09001112 */
Jungshik Janga5b74142014-06-23 18:03:10 +09001113 @ServiceThreadOnly
Jungshik Jang1de51422014-07-03 11:14:26 +09001114 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
1115 int retryCount) {
Jungshik Janga5b74142014-06-23 18:03:10 +09001116 assertRunOnServiceThread();
Jungshik Jang1de51422014-07-03 11:14:26 +09001117 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
1118 retryCount);
Jungshik Jang0f8b4b72014-05-28 17:58:58 +09001119 }
1120
1121 private int checkPollStrategy(int pickStrategy) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001122 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
Jungshik Jang0f8b4b72014-05-28 17:58:58 +09001123 if (strategy == 0) {
1124 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
1125 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001126 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
Jungshik Jang0f8b4b72014-05-28 17:58:58 +09001127 if (iterationStrategy == 0) {
1128 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
1129 }
1130 return strategy | iterationStrategy;
Jungshik Jang02bb4262014-05-23 16:48:31 +09001131 }
1132
Jungshik Jang60cffce2014-06-12 18:03:04 +09001133 List<HdmiCecLocalDevice> getAllLocalDevices() {
1134 assertRunOnServiceThread();
1135 return mCecController.getLocalDeviceList();
1136 }
Jungshik Jang3ee65722014-06-03 16:22:30 +09001137
Jungshik Jang79c58a42014-06-16 16:45:36 +09001138 Object getServiceLock() {
1139 return mLock;
1140 }
1141
1142 void setAudioStatus(boolean mute, int volume) {
Donghyun Cho65618452016-12-23 18:30:37 +09001143 if (!isTvDeviceEnabled() || !tv().isSystemAudioActivated()) {
1144 return;
1145 }
Jungshik Jangb69aafbf2014-07-11 16:29:06 +09001146 AudioManager audioManager = getAudioManager();
1147 boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
1148 if (mute) {
1149 if (!muted) {
1150 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
1151 }
1152 } else {
1153 if (muted) {
1154 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
1155 }
1156 // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
1157 // volume change notification back to hdmi control service.
Shuichi.Noguchifbb50bc2017-12-06 11:12:33 +09001158 int flag = AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME;
1159 if (0 <= volume && volume <= 100) {
1160 Slog.i(TAG, "volume: " + volume);
1161 flag |= AudioManager.FLAG_SHOW_UI;
1162 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, flag);
1163 }
Jungshik Jangb69aafbf2014-07-11 16:29:06 +09001164 }
Jungshik Jang3ee65722014-06-03 16:22:30 +09001165 }
1166
Jungshik Jangea67c182014-06-19 22:19:20 +09001167 void announceSystemAudioModeChange(boolean enabled) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001168 synchronized (mLock) {
1169 for (SystemAudioModeChangeListenerRecord record :
1170 mSystemAudioModeChangeListenerRecords) {
1171 invokeSystemAudioModeChangeLocked(record.mListener, enabled);
1172 }
Jungshik Jangea67c182014-06-19 22:19:20 +09001173 }
1174 }
1175
Jungshik Jang410ca9c2014-08-07 18:04:14 +09001176 private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
Jungshik Jang42c98002014-06-12 13:17:44 +09001177 // TODO: find better name instead of model name.
1178 String displayName = Build.MODEL;
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001179 return new HdmiDeviceInfo(logicalAddress,
Jinsuk Kim2b152012014-07-25 08:22:26 +09001180 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
Amy0361dd72018-11-07 16:54:25 -08001181 getVendorId(), displayName, powerStatus);
Jungshik Jang3ee65722014-06-03 16:22:30 +09001182 }
1183
Jungshik Jang7df52862014-08-11 14:35:27 +09001184 @ServiceThreadOnly
Jungshik Jang7df52862014-08-11 14:35:27 +09001185 void handleMhlHotplugEvent(int portId, boolean connected) {
1186 assertRunOnServiceThread();
Jinsuk Kim93eed0c2014-10-14 11:52:22 +09001187 // Hotplug event is used to add/remove MHL devices as TV input.
Jungshik Jang7df52862014-08-11 14:35:27 +09001188 if (connected) {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001189 HdmiMhlLocalDeviceStub newDevice = new HdmiMhlLocalDeviceStub(this, portId);
1190 HdmiMhlLocalDeviceStub oldDevice = mMhlController.addLocalDevice(newDevice);
Jungshik Jang7df52862014-08-11 14:35:27 +09001191 if (oldDevice != null) {
1192 oldDevice.onDeviceRemoved();
1193 Slog.i(TAG, "Old device of port " + portId + " is removed");
1194 }
Jinsuk Kim93eed0c2014-10-14 11:52:22 +09001195 invokeDeviceEventListeners(newDevice.getInfo(), DEVICE_EVENT_ADD_DEVICE);
1196 updateSafeMhlInput();
Jungshik Jang7df52862014-08-11 14:35:27 +09001197 } else {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001198 HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001199 if (device != null) {
1200 device.onDeviceRemoved();
Jinsuk Kim93eed0c2014-10-14 11:52:22 +09001201 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE);
1202 updateSafeMhlInput();
Jungshik Jang7df52862014-08-11 14:35:27 +09001203 } else {
1204 Slog.w(TAG, "No device to remove:[portId=" + portId);
1205 }
1206 }
Jinsuk Kimed086452014-08-18 15:01:53 +09001207 announceHotplugEvent(portId, connected);
Jungshik Jang7df52862014-08-11 14:35:27 +09001208 }
1209
1210 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001211 void handleMhlBusModeChanged(int portId, int busmode) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001212 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001213 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001214 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001215 device.setBusMode(busmode);
Jungshik Jang7df52862014-08-11 14:35:27 +09001216 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001217 Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId +
1218 ", busmode:" + busmode + "]");
Jungshik Jang7df52862014-08-11 14:35:27 +09001219 }
1220 }
1221
1222 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001223 void handleMhlBusOvercurrent(int portId, boolean on) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001224 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001225 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001226 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001227 device.onBusOvercurrentDetected(on);
Jungshik Jang7df52862014-08-11 14:35:27 +09001228 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001229 Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]");
Jungshik Jang7df52862014-08-11 14:35:27 +09001230 }
1231 }
1232
1233 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001234 void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001235 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001236 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jinsuk Kimed086452014-08-18 15:01:53 +09001237
Jungshik Jang7df52862014-08-11 14:35:27 +09001238 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001239 device.setDeviceStatusChange(adopterId, deviceId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001240 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001241 Slog.w(TAG, "No mhl device exists for device status event[portId:"
Jungshik Jang7df52862014-08-11 14:35:27 +09001242 + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]");
1243 }
1244 }
1245
Jinsuk Kimed086452014-08-18 15:01:53 +09001246 @ServiceThreadOnly
1247 private void updateSafeMhlInput() {
1248 assertRunOnServiceThread();
1249 List<HdmiDeviceInfo> inputs = Collections.emptyList();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001250 SparseArray<HdmiMhlLocalDeviceStub> devices = mMhlController.getAllLocalDevices();
Jinsuk Kimed086452014-08-18 15:01:53 +09001251 for (int i = 0; i < devices.size(); ++i) {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001252 HdmiMhlLocalDeviceStub device = devices.valueAt(i);
Jinsuk Kimed086452014-08-18 15:01:53 +09001253 HdmiDeviceInfo info = device.getInfo();
1254 if (info != null) {
1255 if (inputs.isEmpty()) {
1256 inputs = new ArrayList<>();
1257 }
1258 inputs.add(device.getInfo());
1259 }
1260 }
1261 synchronized (mLock) {
1262 mMhlDevices = inputs;
1263 }
1264 }
1265
Andreas Gampea36dc622018-02-05 17:19:22 -08001266 @GuardedBy("mLock")
Jinsuk Kimed086452014-08-18 15:01:53 +09001267 private List<HdmiDeviceInfo> getMhlDevicesLocked() {
1268 return mMhlDevices;
1269 }
1270
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001271 private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient {
1272 private final IHdmiMhlVendorCommandListener mListener;
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001273
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001274 public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) {
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001275 mListener = listener;
1276 }
1277
1278 @Override
1279 public void binderDied() {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001280 mMhlVendorCommandListenerRecords.remove(this);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001281 }
1282 }
1283
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001284 // Record class that monitors the event of the caller of being killed. Used to clean up
1285 // the listener list and record list accordingly.
1286 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
1287 private final IHdmiHotplugEventListener mListener;
1288
1289 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
1290 mListener = listener;
1291 }
1292
1293 @Override
1294 public void binderDied() {
1295 synchronized (mLock) {
1296 mHotplugEventListenerRecords.remove(this);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001297 }
1298 }
Jinsuk Kim3cd30512014-12-04 11:05:09 +09001299
1300 @Override
1301 public boolean equals(Object obj) {
1302 if (!(obj instanceof HotplugEventListenerRecord)) return false;
1303 if (obj == this) return true;
1304 HotplugEventListenerRecord other = (HotplugEventListenerRecord) obj;
1305 return other.mListener == this.mListener;
1306 }
1307
1308 @Override
1309 public int hashCode() {
1310 return mListener.hashCode();
1311 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001312 }
1313
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001314 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
1315 private final IHdmiDeviceEventListener mListener;
1316
1317 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
1318 mListener = listener;
1319 }
1320
1321 @Override
Jungshik Jangea67c182014-06-19 22:19:20 +09001322 public void binderDied() {
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001323 synchronized (mLock) {
1324 mDeviceEventListenerRecords.remove(this);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001325 }
1326 }
1327 }
1328
Jungshik Jangea67c182014-06-19 22:19:20 +09001329 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
Yuncheol Heo38db6292014-07-01 14:15:14 +09001330 private final IHdmiSystemAudioModeChangeListener mListener;
Jungshik Jangea67c182014-06-19 22:19:20 +09001331
1332 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
1333 mListener = listener;
1334 }
1335
1336 @Override
1337 public void binderDied() {
1338 synchronized (mLock) {
1339 mSystemAudioModeChangeListenerRecords.remove(this);
Jungshik Jangea67c182014-06-19 22:19:20 +09001340 }
1341 }
1342 }
1343
Jinsuk Kim119160a2014-07-07 18:48:10 +09001344 class VendorCommandListenerRecord implements IBinder.DeathRecipient {
1345 private final IHdmiVendorCommandListener mListener;
1346 private final int mDeviceType;
1347
1348 public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
1349 mListener = listener;
1350 mDeviceType = deviceType;
1351 }
1352
1353 @Override
1354 public void binderDied() {
1355 synchronized (mLock) {
1356 mVendorCommandListenerRecords.remove(this);
1357 }
1358 }
1359 }
1360
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001361 private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
Jungshik Jangf4249322014-08-21 14:17:05 +09001362 private final IHdmiRecordListener mListener;
1363
1364 public HdmiRecordListenerRecord(IHdmiRecordListener listener) {
1365 mListener = listener;
1366 }
1367
Jungshik Jangb6591b82014-07-23 16:10:23 +09001368 @Override
1369 public void binderDied() {
1370 synchronized (mLock) {
Donghyun Chofbbeb3e2016-04-15 09:12:03 +09001371 if (mRecordListenerRecord == this) {
1372 mRecordListenerRecord = null;
1373 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09001374 }
1375 }
1376 }
1377
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001378 private void enforceAccessPermission() {
1379 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
1380 }
1381
1382 private final class BinderService extends IHdmiControlService.Stub {
1383 @Override
1384 public int[] getSupportedTypes() {
1385 enforceAccessPermission();
Jinsuk Kim0340bbc2014-06-05 11:07:47 +09001386 // mLocalDevices is an unmodifiable list - no lock necesary.
1387 int[] localDevices = new int[mLocalDevices.size()];
1388 for (int i = 0; i < localDevices.length; ++i) {
1389 localDevices[i] = mLocalDevices.get(i);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001390 }
Jinsuk Kim0340bbc2014-06-05 11:07:47 +09001391 return localDevices;
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001392 }
1393
1394 @Override
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001395 public HdmiDeviceInfo getActiveSource() {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001396 enforceAccessPermission();
Jinsuk Kim7e742062014-07-30 13:19:13 +09001397 HdmiCecLocalDeviceTv tv = tv();
1398 if (tv == null) {
1399 Slog.w(TAG, "Local tv device not available");
Amy0361dd72018-11-07 16:54:25 -08001400 if (isPlaybackDevice()) {
1401 // if playback device itself is the active source,
1402 // return its own device info.
1403 if (playback() != null && playback().mIsActiveSource) {
1404 return playback().getDeviceInfo();
1405 }
1406 // Otherwise get the active source and look for it from the device list
1407 ActiveSource activeSource = mActiveSource;
1408 // If the active source is not set yet, return null
1409 if (!activeSource.isValid()) {
1410 return null;
1411 }
1412 if (audioSystem() != null) {
1413 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
1414 for (HdmiDeviceInfo info : audioSystem.getSafeCecDevicesLocked()) {
1415 if (info.getLogicalAddress() == activeSource.logicalAddress) {
1416 return info;
1417 }
1418 }
1419 }
1420 // If the device info is not in the list yet, return a device info with minimum
1421 // information from mActiveSource.
1422 return new HdmiDeviceInfo(activeSource.logicalAddress,
1423 activeSource.physicalAddress, pathToPortId(activeSource.physicalAddress),
1424 HdmiUtils.getTypeFromAddress(activeSource.logicalAddress), 0,
1425 HdmiUtils.getDefaultDeviceName(activeSource.logicalAddress));
1426 }
Jinsuk Kim7e742062014-07-30 13:19:13 +09001427 return null;
1428 }
1429 ActiveSource activeSource = tv.getActiveSource();
1430 if (activeSource.isValid()) {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001431 return new HdmiDeviceInfo(activeSource.logicalAddress,
1432 activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID,
1433 HdmiDeviceInfo.DEVICE_INACTIVE, 0, "");
Jinsuk Kim7e742062014-07-30 13:19:13 +09001434 }
1435 int activePath = tv.getActivePath();
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001436 if (activePath != HdmiDeviceInfo.PATH_INVALID) {
Jinsuk Kim7640d982015-01-28 16:44:07 +09001437 HdmiDeviceInfo info = tv.getSafeDeviceInfoByPath(activePath);
Jinsuk Kimd47abef2015-01-17 07:38:24 +09001438 return (info != null) ? info : new HdmiDeviceInfo(activePath, tv.getActivePortId());
Jinsuk Kim7e742062014-07-30 13:19:13 +09001439 }
1440 return null;
1441 }
1442
1443 @Override
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001444 public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) {
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001445 enforceAccessPermission();
1446 runOnServiceThread(new Runnable() {
1447 @Override
1448 public void run() {
Jinsuk Kim72b7d732014-07-24 09:15:35 +09001449 if (callback == null) {
1450 Slog.e(TAG, "Callback cannot be null");
1451 return;
1452 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09001453 HdmiCecLocalDeviceTv tv = tv();
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001454 if (tv == null) {
Jinsuk Kimf98b9e82015-10-05 14:24:48 +09001455 if (!mAddressAllocated) {
1456 mSelectRequestBuffer.set(SelectRequestBuffer.newDeviceSelect(
1457 HdmiControlService.this, deviceId, callback));
1458 return;
1459 }
Jinsuk Kima062a932014-06-18 10:00:39 +09001460 Slog.w(TAG, "Local tv device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001461 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001462 return;
1463 }
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001464 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001465 if (device != null) {
1466 if (device.getPortId() == tv.getActivePortId()) {
1467 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
Jinsuk Kim87f22a22014-08-20 10:40:12 +09001468 return;
1469 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001470 // Upon selecting MHL device, we send RAP[Content On] to wake up
1471 // the connected mobile device, start routing control to switch ports.
1472 // callback is handled by MHL action.
1473 device.turnOn(callback);
Yuncheol Heo7c5d31e2014-09-03 16:28:54 +09001474 tv.doManualPortSwitching(device.getPortId(), null);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001475 return;
Jinsuk Kim87f22a22014-08-20 10:40:12 +09001476 }
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001477 tv.deviceSelect(deviceId, callback);
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001478 }
1479 });
1480 }
1481
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001482 @Override
Jinsuk Kima062a932014-06-18 10:00:39 +09001483 public void portSelect(final int portId, final IHdmiControlCallback callback) {
1484 enforceAccessPermission();
1485 runOnServiceThread(new Runnable() {
1486 @Override
1487 public void run() {
Jinsuk Kim72b7d732014-07-24 09:15:35 +09001488 if (callback == null) {
1489 Slog.e(TAG, "Callback cannot be null");
1490 return;
1491 }
Jinsuk Kima062a932014-06-18 10:00:39 +09001492 HdmiCecLocalDeviceTv tv = tv();
shubangd932cb52018-09-14 17:55:16 -07001493 if (tv != null) {
1494 tv.doManualPortSwitching(portId, callback);
Jinsuk Kima062a932014-06-18 10:00:39 +09001495 return;
1496 }
shubangd932cb52018-09-14 17:55:16 -07001497 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
1498 if (audioSystem != null) {
1499 audioSystem.doManualPortSwitching(portId, callback);
1500 return;
1501 }
1502
1503 if (!mAddressAllocated) {
1504 mSelectRequestBuffer.set(SelectRequestBuffer.newPortSelect(
1505 HdmiControlService.this, portId, callback));
1506 return;
1507 }
1508 Slog.w(TAG, "Local device not available");
1509 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1510 return;
Jinsuk Kima062a932014-06-18 10:00:39 +09001511 }
1512 });
1513 }
1514
1515 @Override
Jinsuk Kimc068bb52014-07-07 16:59:20 +09001516 public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
Jinsuk Kima062a932014-06-18 10:00:39 +09001517 enforceAccessPermission();
1518 runOnServiceThread(new Runnable() {
1519 @Override
1520 public void run() {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001521 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001522 if (device != null) {
1523 device.sendKeyEvent(keyCode, isPressed);
1524 return;
Jinsuk Kima062a932014-06-18 10:00:39 +09001525 }
Jungshik Jang4612a6e2014-08-12 22:01:23 +09001526 if (mCecController != null) {
1527 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
1528 if (localDevice == null) {
1529 Slog.w(TAG, "Local device not available");
1530 return;
1531 }
1532 localDevice.sendKeyEvent(keyCode, isPressed);
1533 }
Jinsuk Kima062a932014-06-18 10:00:39 +09001534 }
1535 });
1536 }
1537
1538 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001539 public void oneTouchPlay(final IHdmiControlCallback callback) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001540 enforceAccessPermission();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001541 runOnServiceThread(new Runnable() {
1542 @Override
1543 public void run() {
1544 HdmiControlService.this.oneTouchPlay(callback);
1545 }
1546 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001547 }
1548
1549 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001550 public void queryDisplayStatus(final IHdmiControlCallback callback) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001551 enforceAccessPermission();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001552 runOnServiceThread(new Runnable() {
1553 @Override
1554 public void run() {
1555 HdmiControlService.this.queryDisplayStatus(callback);
1556 }
1557 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001558 }
1559
1560 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001561 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001562 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001563 HdmiControlService.this.addHotplugEventListener(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001564 }
1565
1566 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001567 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001568 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001569 HdmiControlService.this.removeHotplugEventListener(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001570 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001571
1572 @Override
1573 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
1574 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001575 HdmiControlService.this.addDeviceEventListener(listener);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001576 }
1577
1578 @Override
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001579 public List<HdmiPortInfo> getPortInfo() {
1580 enforceAccessPermission();
Jungshik Jang2738e2d2014-08-19 09:30:05 +09001581 return HdmiControlService.this.getPortInfo();
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001582 }
Jungshik Jangea67c182014-06-19 22:19:20 +09001583
1584 @Override
1585 public boolean canChangeSystemAudioMode() {
1586 enforceAccessPermission();
1587 HdmiCecLocalDeviceTv tv = tv();
1588 if (tv == null) {
1589 return false;
1590 }
Jungshik Jange9cf1582014-06-23 17:28:58 +09001591 return tv.hasSystemAudioDevice();
Jungshik Jangea67c182014-06-19 22:19:20 +09001592 }
1593
1594 @Override
1595 public boolean getSystemAudioMode() {
Shubang Lu00b976a2018-08-01 18:11:46 -07001596 // TODO(shubang): handle getSystemAudioMode() for all device types
Jungshik Jangea67c182014-06-19 22:19:20 +09001597 enforceAccessPermission();
1598 HdmiCecLocalDeviceTv tv = tv();
Shubang Lu00b976a2018-08-01 18:11:46 -07001599 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
1600 return (tv != null && tv.isSystemAudioActivated())
1601 || (audioSystem != null && audioSystem.isSystemAudioActivated());
Jungshik Jangea67c182014-06-19 22:19:20 +09001602 }
1603
1604 @Override
Amyd58d0aa2018-10-18 14:08:57 -07001605 public int getPhysicalAddress() {
1606 enforceAccessPermission();
1607 synchronized (mLock) {
1608 return mPhysicalAddress;
1609 }
1610 }
1611
1612 @Override
Jungshik Jangea67c182014-06-19 22:19:20 +09001613 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
1614 enforceAccessPermission();
1615 runOnServiceThread(new Runnable() {
1616 @Override
1617 public void run() {
1618 HdmiCecLocalDeviceTv tv = tv();
1619 if (tv == null) {
1620 Slog.w(TAG, "Local tv device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001621 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jungshik Jangea67c182014-06-19 22:19:20 +09001622 return;
1623 }
1624 tv.changeSystemAudioMode(enabled, callback);
1625 }
1626 });
1627 }
1628
1629 @Override
1630 public void addSystemAudioModeChangeListener(
1631 final IHdmiSystemAudioModeChangeListener listener) {
1632 enforceAccessPermission();
1633 HdmiControlService.this.addSystemAudioModeChangeListner(listener);
1634 }
1635
1636 @Override
1637 public void removeSystemAudioModeChangeListener(
1638 final IHdmiSystemAudioModeChangeListener listener) {
1639 enforceAccessPermission();
1640 HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
1641 }
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001642
1643 @Override
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001644 public void setInputChangeListener(final IHdmiInputChangeListener listener) {
1645 enforceAccessPermission();
1646 HdmiControlService.this.setInputChangeListener(listener);
1647 }
1648
1649 @Override
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001650 public List<HdmiDeviceInfo> getInputDevices() {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001651 enforceAccessPermission();
1652 // No need to hold the lock for obtaining TV device as the local device instance
1653 // is preserved while the HDMI control is enabled.
1654 HdmiCecLocalDeviceTv tv = tv();
Jinsuk Kimed086452014-08-18 15:01:53 +09001655 synchronized (mLock) {
1656 List<HdmiDeviceInfo> cecDevices = (tv == null)
1657 ? Collections.<HdmiDeviceInfo>emptyList()
1658 : tv.getSafeExternalInputsLocked();
1659 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked());
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001660 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001661 }
1662
Jinsuk Kimbdf27fb2014-10-20 10:00:04 +09001663 // Returns all the CEC devices on the bus including system audio, switch,
1664 // even those of reserved type.
1665 @Override
1666 public List<HdmiDeviceInfo> getDeviceList() {
1667 enforceAccessPermission();
1668 HdmiCecLocalDeviceTv tv = tv();
Amy6f031af2018-10-30 16:38:33 -07001669 if (tv != null) {
1670 synchronized (mLock) {
1671 return tv.getSafeCecDevicesLocked();
1672 }
1673 } else {
1674 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
1675 synchronized (mLock) {
1676 return (audioSystem == null)
Jinsuk Kimbdf27fb2014-10-20 10:00:04 +09001677 ? Collections.<HdmiDeviceInfo>emptyList()
Amy6f031af2018-10-30 16:38:33 -07001678 : audioSystem.getSafeCecDevicesLocked();
1679 }
Jinsuk Kimbdf27fb2014-10-20 10:00:04 +09001680 }
1681 }
1682
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001683 @Override
Amy6f031af2018-10-30 16:38:33 -07001684 public void powerOffRemoteDevice(int logicalAddress, int powerStatus) {
1685 enforceAccessPermission();
1686 runOnServiceThread(new Runnable() {
1687 @Override
1688 public void run() {
Amy4879d5c2018-11-13 16:06:15 -08001689 Slog.w(TAG, "Device "
1690 + logicalAddress + " power status is " + powerStatus
1691 + " before standby command sent out");
1692 sendCecCommand(HdmiCecMessageBuilder.buildStandby(
1693 getRemoteControlSourceAddress(), logicalAddress));
Amy6f031af2018-10-30 16:38:33 -07001694 }
1695 });
1696 }
1697
1698 @Override
1699 public void powerOnRemoteDevice(int logicalAddress, int powerStatus) {
1700 // TODO(amyjojo): implement the method
1701 }
1702
1703 @Override
1704 // TODO(AMYJOJO): add a result callback
1705 public void askRemoteDeviceToBecomeActiveSource(int physicalAddress) {
1706 enforceAccessPermission();
1707 runOnServiceThread(new Runnable() {
1708 @Override
1709 public void run() {
1710 HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(
1711 getRemoteControlSourceAddress(), physicalAddress);
1712 if (pathToPortId(physicalAddress) != Constants.INVALID_PORT_ID) {
1713 if (getSwitchDevice() != null) {
1714 getSwitchDevice().handleSetStreamPath(setStreamPath);
1715 } else {
1716 Slog.e(TAG, "Can't get the correct local device to handle routing.");
1717 }
Amy6f031af2018-10-30 16:38:33 -07001718 }
Amycdd25e92018-11-13 12:22:16 -08001719 sendCecCommand(setStreamPath);
Amy6f031af2018-10-30 16:38:33 -07001720 }
1721 });
1722 }
1723
1724 @Override
Jungshik Jang41d97462014-06-30 22:26:29 +09001725 public void setSystemAudioVolume(final int oldIndex, final int newIndex,
1726 final int maxIndex) {
1727 enforceAccessPermission();
1728 runOnServiceThread(new Runnable() {
1729 @Override
1730 public void run() {
1731 HdmiCecLocalDeviceTv tv = tv();
1732 if (tv == null) {
1733 Slog.w(TAG, "Local tv device not available");
1734 return;
1735 }
1736 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
1737 }
1738 });
1739 }
1740
1741 @Override
1742 public void setSystemAudioMute(final boolean mute) {
1743 enforceAccessPermission();
1744 runOnServiceThread(new Runnable() {
1745 @Override
1746 public void run() {
1747 HdmiCecLocalDeviceTv tv = tv();
1748 if (tv == null) {
1749 Slog.w(TAG, "Local tv device not available");
1750 return;
1751 }
1752 tv.changeMute(mute);
1753 }
1754 });
1755 }
1756
1757 @Override
Jungshik Janga13da0d2014-06-30 16:26:06 +09001758 public void setArcMode(final boolean enabled) {
1759 enforceAccessPermission();
1760 runOnServiceThread(new Runnable() {
1761 @Override
1762 public void run() {
1763 HdmiCecLocalDeviceTv tv = tv();
1764 if (tv == null) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09001765 Slog.w(TAG, "Local tv device not available to change arc mode.");
Jungshik Janga13da0d2014-06-30 16:26:06 +09001766 return;
1767 }
1768 }
1769 });
1770 }
Jinsuk Kim160a6e52014-07-02 06:16:36 +09001771
1772 @Override
Jinsuk Kim4d43d932014-07-03 16:43:58 +09001773 public void setProhibitMode(final boolean enabled) {
1774 enforceAccessPermission();
1775 if (!isTvDevice()) {
1776 return;
1777 }
1778 HdmiControlService.this.setProhibitMode(enabled);
1779 }
Jinsuk Kim119160a2014-07-07 18:48:10 +09001780
1781 @Override
1782 public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
1783 final int deviceType) {
1784 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001785 HdmiControlService.this.addVendorCommandListener(listener, deviceType);
Jinsuk Kim119160a2014-07-07 18:48:10 +09001786 }
1787
1788 @Override
1789 public void sendVendorCommand(final int deviceType, final int targetAddress,
1790 final byte[] params, final boolean hasVendorId) {
1791 enforceAccessPermission();
1792 runOnServiceThread(new Runnable() {
1793 @Override
1794 public void run() {
1795 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1796 if (device == null) {
1797 Slog.w(TAG, "Local device not available");
1798 return;
1799 }
1800 if (hasVendorId) {
1801 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1802 device.getDeviceInfo().getLogicalAddress(), targetAddress,
1803 getVendorId(), params));
1804 } else {
1805 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1806 device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1807 }
1808 }
1809 });
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001810 }
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09001811
1812 @Override
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09001813 public void sendStandby(final int deviceType, final int deviceId) {
1814 enforceAccessPermission();
1815 runOnServiceThread(new Runnable() {
1816 @Override
1817 public void run() {
Jinsuk Kim61c94d12015-01-15 07:00:28 +09001818 HdmiMhlLocalDeviceStub mhlDevice = mMhlController.getLocalDeviceById(deviceId);
1819 if (mhlDevice != null) {
1820 mhlDevice.sendStandby();
1821 return;
1822 }
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09001823 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1824 if (device == null) {
Amy777abd72018-09-10 16:11:33 -07001825 device = audioSystem();
1826 }
1827 if (device == null) {
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09001828 Slog.w(TAG, "Local device not available");
1829 return;
1830 }
1831 device.sendStandby(deviceId);
1832 }
1833 });
1834 }
1835
1836 @Override
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001837 public void setHdmiRecordListener(IHdmiRecordListener listener) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001838 enforceAccessPermission();
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001839 HdmiControlService.this.setHdmiRecordListener(listener);
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09001840 }
1841
1842 @Override
Jungshik Jangb6591b82014-07-23 16:10:23 +09001843 public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001844 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001845 runOnServiceThread(new Runnable() {
1846 @Override
1847 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001848 if (!isTvDeviceEnabled()) {
1849 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001850 return;
1851 }
1852 tv().startOneTouchRecord(recorderAddress, recordSource);
1853 }
1854 });
Jungshik Jangbffb0632014-07-22 16:56:52 +09001855 }
1856
1857 @Override
Jungshik Jangb6591b82014-07-23 16:10:23 +09001858 public void stopOneTouchRecord(final int recorderAddress) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001859 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001860 runOnServiceThread(new Runnable() {
1861 @Override
1862 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001863 if (!isTvDeviceEnabled()) {
1864 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001865 return;
1866 }
1867 tv().stopOneTouchRecord(recorderAddress);
1868 }
1869 });
1870 }
1871
1872 @Override
1873 public void startTimerRecording(final int recorderAddress, final int sourceType,
1874 final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001875 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001876 runOnServiceThread(new Runnable() {
1877 @Override
1878 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001879 if (!isTvDeviceEnabled()) {
1880 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001881 return;
1882 }
1883 tv().startTimerRecording(recorderAddress, sourceType, recordSource);
1884 }
1885 });
1886 }
1887
1888 @Override
1889 public void clearTimerRecording(final int recorderAddress, final int sourceType,
1890 final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001891 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001892 runOnServiceThread(new Runnable() {
1893 @Override
1894 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001895 if (!isTvDeviceEnabled()) {
1896 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001897 return;
1898 }
1899 tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
1900 }
1901 });
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09001902 }
Jungshik Jangf4249322014-08-21 14:17:05 +09001903
1904 @Override
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001905 public void sendMhlVendorCommand(final int portId, final int offset, final int length,
Jungshik Jangf4249322014-08-21 14:17:05 +09001906 final byte[] data) {
1907 enforceAccessPermission();
1908 runOnServiceThread(new Runnable() {
1909 @Override
1910 public void run() {
Jungshik Jangf4249322014-08-21 14:17:05 +09001911 if (!isControlEnabled()) {
1912 Slog.w(TAG, "Hdmi control is disabled.");
1913 return ;
1914 }
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001915 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jangf4249322014-08-21 14:17:05 +09001916 if (device == null) {
1917 Slog.w(TAG, "Invalid port id:" + portId);
1918 return;
1919 }
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001920 mMhlController.sendVendorCommand(portId, offset, length, data);
Jungshik Jangf4249322014-08-21 14:17:05 +09001921 }
1922 });
1923 }
1924
1925 @Override
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001926 public void addHdmiMhlVendorCommandListener(
1927 IHdmiMhlVendorCommandListener listener) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001928 enforceAccessPermission();
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001929 HdmiControlService.this.addHdmiMhlVendorCommandListener(listener);
Jungshik Jangf4249322014-08-21 14:17:05 +09001930 }
Terry Heo959d2db2014-08-28 16:45:41 +09001931
1932 @Override
Donghyun Chob3515642017-03-02 13:47:40 +09001933 public void setStandbyMode(final boolean isStandbyModeOn) {
1934 enforceAccessPermission();
1935 runOnServiceThread(new Runnable() {
1936 @Override
1937 public void run() {
1938 HdmiControlService.this.setStandbyMode(isStandbyModeOn);
1939 }
1940 });
1941 }
1942
1943 @Override
Shubangc480a712018-06-11 18:02:42 -07001944 public void reportAudioStatus(final int deviceType, final int volume, final int maxVolume,
1945 final boolean isMute) {
1946 enforceAccessPermission();
1947 runOnServiceThread(new Runnable() {
1948 @Override
1949 public void run() {
1950 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1951 if (device == null) {
1952 Slog.w(TAG, "Local device not available");
1953 return;
1954 }
1955 if (audioSystem() == null) {
1956 Slog.w(TAG, "audio system is not available");
1957 return;
1958 }
Shubang17b29342018-07-19 17:58:48 -07001959 if (!audioSystem().isSystemAudioActivated()) {
Shubangc480a712018-06-11 18:02:42 -07001960 Slog.w(TAG, "audio system is not in system audio mode");
1961 return;
1962 }
Nick Chalko01b979c2018-10-19 14:54:30 -07001963 audioSystem().reportAudioStatus(Constants.ADDR_TV);
Shubangc480a712018-06-11 18:02:42 -07001964 }
1965 });
1966 }
1967
1968 @Override
Amy4ad4e782018-10-17 17:48:49 -07001969 public void setSystemAudioModeOnForAudioOnlySource() {
1970 enforceAccessPermission();
1971 runOnServiceThread(new Runnable() {
1972 @Override
1973 public void run() {
1974 if (!isAudioSystemDevice()) {
1975 Slog.e(TAG, "Not an audio system device. Won't set system audio mode on");
1976 return;
1977 }
Amy7e3b6d92018-11-21 11:52:07 -08001978 if (audioSystem() == null) {
1979 Slog.e(TAG, "Audio System local device is not registered");
1980 return;
1981 }
Amy4ad4e782018-10-17 17:48:49 -07001982 if (!audioSystem().checkSupportAndSetSystemAudioMode(true)) {
1983 Slog.e(TAG, "System Audio Mode is not supported.");
1984 return;
1985 }
1986 sendCecCommand(
1987 HdmiCecMessageBuilder.buildSetSystemAudioMode(
1988 audioSystem().mAddress, Constants.ADDR_BROADCAST, true));
1989 }
1990 });
1991 }
1992
1993 @Override
Terry Heo959d2db2014-08-28 16:45:41 +09001994 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -06001995 if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) return;
Terry Heo959d2db2014-08-28 16:45:41 +09001996 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
1997
Terry Heo959d2db2014-08-28 16:45:41 +09001998 pw.println("mProhibitMode: " + mProhibitMode);
Nick Chalkob9e48e22018-10-23 06:59:39 -07001999 pw.println("mPowerStatus: " + mPowerStatus);
2000
2001 // System settings
2002 pw.println("System_settings:");
2003 pw.increaseIndent();
2004 pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
2005 pw.println("mMhlInputChangeEnabled: " + mMhlInputChangeEnabled);
2006 pw.decreaseIndent();
Jinsuk Kim61c94d12015-01-15 07:00:28 +09002007
2008 pw.println("mMhlController: ");
2009 pw.increaseIndent();
2010 mMhlController.dump(pw);
2011 pw.decreaseIndent();
2012
Nick Chalkob9e48e22018-10-23 06:59:39 -07002013 HdmiUtils.dumpIterable(pw, "mPortInfo:", mPortInfo);
2014 if (mCecController != null) {
2015 pw.println("mCecController: ");
2016 pw.increaseIndent();
2017 mCecController.dump(pw);
2018 pw.decreaseIndent();
Terry Heo959d2db2014-08-28 16:45:41 +09002019 }
Terry Heo959d2db2014-08-28 16:45:41 +09002020 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002021 }
2022
Amy6f031af2018-10-30 16:38:33 -07002023 // Get the source address to send out commands to devices connected to the current device
2024 // when other services interact with HdmiControlService.
2025 private int getRemoteControlSourceAddress() {
2026 if (isAudioSystemDevice()) {
2027 return audioSystem().getDeviceInfo().getLogicalAddress();
2028 } else if (isPlaybackDevice()) {
2029 return playback().getDeviceInfo().getLogicalAddress();
2030 }
2031 return ADDR_UNREGISTERED;
2032 }
2033
2034 // Get the switch device to do CEC routing control
2035 @Nullable
2036 private HdmiCecLocalDeviceSource getSwitchDevice() {
2037 if (isAudioSystemDevice()) {
2038 return audioSystem();
2039 }
2040 if (isPlaybackDevice()) {
2041 return playback();
2042 }
2043 return null;
2044 }
2045
Jungshik Janga5b74142014-06-23 18:03:10 +09002046 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +09002047 private void oneTouchPlay(final IHdmiControlCallback callback) {
2048 assertRunOnServiceThread();
Amy848a9f22018-08-27 17:21:26 -07002049 HdmiCecLocalDeviceSource source = playback();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002050 if (source == null) {
Amy848a9f22018-08-27 17:21:26 -07002051 source = audioSystem();
2052 }
2053
2054 if (source == null) {
2055 Slog.w(TAG, "Local source device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002056 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002057 return;
2058 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09002059 source.oneTouchPlay(callback);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002060 }
2061
Jungshik Janga5b74142014-06-23 18:03:10 +09002062 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +09002063 private void queryDisplayStatus(final IHdmiControlCallback callback) {
2064 assertRunOnServiceThread();
2065 HdmiCecLocalDevicePlayback source = playback();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002066 if (source == null) {
2067 Slog.w(TAG, "Local playback device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002068 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002069 return;
2070 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09002071 source.queryDisplayStatus(callback);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002072 }
2073
Jinsuk Kim3cd30512014-12-04 11:05:09 +09002074 private void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
2075 final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002076 try {
2077 listener.asBinder().linkToDeath(record, 0);
2078 } catch (RemoteException e) {
2079 Slog.w(TAG, "Listener already died");
2080 return;
2081 }
2082 synchronized (mLock) {
2083 mHotplugEventListenerRecords.add(record);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002084 }
Jinsuk Kim3cd30512014-12-04 11:05:09 +09002085
2086 // Inform the listener of the initial state of each HDMI port by generating
2087 // hotplug events.
2088 runOnServiceThread(new Runnable() {
2089 @Override
2090 public void run() {
2091 synchronized (mLock) {
2092 if (!mHotplugEventListenerRecords.contains(record)) return;
2093 }
2094 for (HdmiPortInfo port : mPortInfo) {
2095 HdmiHotplugEvent event = new HdmiHotplugEvent(port.getId(),
2096 mCecController.isConnected(port.getId()));
2097 synchronized (mLock) {
2098 invokeHotplugEventListenerLocked(listener, event);
2099 }
2100 }
2101 }
2102 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002103 }
2104
2105 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
2106 synchronized (mLock) {
2107 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
2108 if (record.mListener.asBinder() == listener.asBinder()) {
2109 listener.asBinder().unlinkToDeath(record, 0);
2110 mHotplugEventListenerRecords.remove(record);
2111 break;
2112 }
2113 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002114 }
2115 }
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002116
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09002117 private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002118 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
2119 try {
2120 listener.asBinder().linkToDeath(record, 0);
2121 } catch (RemoteException e) {
2122 Slog.w(TAG, "Listener already died");
2123 return;
2124 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09002125 synchronized (mLock) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002126 mDeviceEventListenerRecords.add(record);
2127 }
2128 }
2129
Jungshik Jang61daf6b2014-08-08 11:38:28 +09002130 void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002131 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002132 for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002133 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09002134 record.mListener.onStatusChanged(device, status);
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002135 } catch (RemoteException e) {
2136 Slog.e(TAG, "Failed to report device event:" + e);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09002137 }
2138 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09002139 }
2140 }
2141
Jungshik Jangea67c182014-06-19 22:19:20 +09002142 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
2143 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
2144 listener);
2145 try {
2146 listener.asBinder().linkToDeath(record, 0);
2147 } catch (RemoteException e) {
2148 Slog.w(TAG, "Listener already died");
2149 return;
2150 }
2151 synchronized (mLock) {
Jungshik Jangea67c182014-06-19 22:19:20 +09002152 mSystemAudioModeChangeListenerRecords.add(record);
2153 }
2154 }
2155
2156 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
2157 synchronized (mLock) {
2158 for (SystemAudioModeChangeListenerRecord record :
2159 mSystemAudioModeChangeListenerRecords) {
2160 if (record.mListener.asBinder() == listener) {
2161 listener.asBinder().unlinkToDeath(record, 0);
2162 mSystemAudioModeChangeListenerRecords.remove(record);
2163 break;
2164 }
2165 }
Jungshik Jangea67c182014-06-19 22:19:20 +09002166 }
2167 }
2168
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002169 private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
Jungshik Jangf4249322014-08-21 14:17:05 +09002170 private final IHdmiInputChangeListener mListener;
2171
2172 public InputChangeListenerRecord(IHdmiInputChangeListener listener) {
2173 mListener = listener;
2174 }
2175
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002176 @Override
2177 public void binderDied() {
2178 synchronized (mLock) {
Donghyun Chofbbeb3e2016-04-15 09:12:03 +09002179 if (mInputChangeListenerRecord == this) {
2180 mInputChangeListenerRecord = null;
2181 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002182 }
2183 }
2184 }
2185
2186 private void setInputChangeListener(IHdmiInputChangeListener listener) {
2187 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002188 mInputChangeListenerRecord = new InputChangeListenerRecord(listener);
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002189 try {
2190 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
2191 } catch (RemoteException e) {
2192 Slog.w(TAG, "Listener already died");
2193 return;
2194 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002195 }
2196 }
2197
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09002198 void invokeInputChangeListener(HdmiDeviceInfo info) {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002199 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002200 if (mInputChangeListenerRecord != null) {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002201 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09002202 mInputChangeListenerRecord.mListener.onChanged(info);
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002203 } catch (RemoteException e) {
2204 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
2205 }
2206 }
2207 }
2208 }
2209
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002210 private void setHdmiRecordListener(IHdmiRecordListener listener) {
Jungshik Jangb6591b82014-07-23 16:10:23 +09002211 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002212 mRecordListenerRecord = new HdmiRecordListenerRecord(listener);
Jungshik Jangb6591b82014-07-23 16:10:23 +09002213 try {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002214 listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
Jungshik Jangb6591b82014-07-23 16:10:23 +09002215 } catch (RemoteException e) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002216 Slog.w(TAG, "Listener already died.", e);
Jungshik Jangb6591b82014-07-23 16:10:23 +09002217 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09002218 }
2219 }
2220
2221 byte[] invokeRecordRequestListener(int recorderAddress) {
2222 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002223 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002224 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09002225 return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002226 } catch (RemoteException e) {
2227 Slog.w(TAG, "Failed to start record.", e);
Jungshik Jangb6591b82014-07-23 16:10:23 +09002228 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09002229 }
2230 return EmptyArray.BYTE;
2231 }
2232 }
2233
Jungshik Jang326aef02014-11-05 12:50:35 +09002234 void invokeOneTouchRecordResult(int recorderAddress, int result) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002235 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002236 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002237 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09002238 mRecordListenerRecord.mListener.onOneTouchRecordResult(recorderAddress, result);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002239 } catch (RemoteException e) {
2240 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
2241 }
2242 }
2243 }
2244 }
2245
Jungshik Jang326aef02014-11-05 12:50:35 +09002246 void invokeTimerRecordingResult(int recorderAddress, int result) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002247 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002248 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002249 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09002250 mRecordListenerRecord.mListener.onTimerRecordingResult(recorderAddress, result);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002251 } catch (RemoteException e) {
Jungshik Jange5a93372014-07-25 13:41:14 +09002252 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
2253 }
2254 }
2255 }
2256 }
2257
Jungshik Jang326aef02014-11-05 12:50:35 +09002258 void invokeClearTimerRecordingResult(int recorderAddress, int result) {
Jungshik Jange5a93372014-07-25 13:41:14 +09002259 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002260 if (mRecordListenerRecord != null) {
Jungshik Jange5a93372014-07-25 13:41:14 +09002261 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09002262 mRecordListenerRecord.mListener.onClearTimerRecordingResult(recorderAddress,
2263 result);
Jungshik Jange5a93372014-07-25 13:41:14 +09002264 } catch (RemoteException e) {
2265 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002266 }
2267 }
2268 }
2269 }
2270
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002271 private void invokeCallback(IHdmiControlCallback callback, int result) {
2272 try {
2273 callback.onComplete(result);
2274 } catch (RemoteException e) {
2275 Slog.e(TAG, "Invoking callback failed:" + e);
2276 }
2277 }
Yuncheol Heo63a2e062014-05-27 23:06:01 +09002278
Jungshik Jangf4249322014-08-21 14:17:05 +09002279 private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener,
Jungshik Jangea67c182014-06-19 22:19:20 +09002280 boolean enabled) {
2281 try {
2282 listener.onStatusChanged(enabled);
2283 } catch (RemoteException e) {
2284 Slog.e(TAG, "Invoking callback failed:" + e);
2285 }
2286 }
2287
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002288 private void announceHotplugEvent(int portId, boolean connected) {
2289 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
Jungshik Jang60cffce2014-06-12 18:03:04 +09002290 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002291 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
2292 invokeHotplugEventListenerLocked(record.mListener, event);
Jungshik Jang60cffce2014-06-12 18:03:04 +09002293 }
2294 }
2295 }
2296
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002297 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
Jungshik Jang60cffce2014-06-12 18:03:04 +09002298 HdmiHotplugEvent event) {
2299 try {
2300 listener.onReceived(event);
2301 } catch (RemoteException e) {
2302 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
2303 }
Jungshik Jange81e1082014-06-05 15:37:59 +09002304 }
2305
Jinsuk Kimf98b9e82015-10-05 14:24:48 +09002306 public HdmiCecLocalDeviceTv tv() {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09002307 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
Jungshik Jang79c58a42014-06-16 16:45:36 +09002308 }
2309
Yuncheol Heoe946ed82014-07-25 14:05:19 +09002310 boolean isTvDevice() {
Yuncheol Heob8d62e72014-09-22 19:53:41 +09002311 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
Yuncheol Heoe946ed82014-07-25 14:05:19 +09002312 }
2313
Amyb887fa02018-06-21 11:22:13 -07002314 boolean isAudioSystemDevice() {
2315 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
2316 }
2317
Amy34037422018-09-06 13:21:08 -07002318 boolean isPlaybackDevice() {
2319 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_PLAYBACK);
2320 }
2321
Amy066db152018-10-04 09:54:51 -07002322 boolean isSwitchDevice() {
2323 return SystemProperties.getBoolean(
Amy17ee20f2018-10-11 11:08:23 -07002324 PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false);
Amy066db152018-10-04 09:54:51 -07002325 }
2326
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002327 boolean isTvDeviceEnabled() {
2328 return isTvDevice() && tv() != null;
2329 }
2330
Amy34037422018-09-06 13:21:08 -07002331 protected HdmiCecLocalDevicePlayback playback() {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002332 return (HdmiCecLocalDevicePlayback)
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09002333 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
Jungshik Jang60cffce2014-06-12 18:03:04 +09002334 }
Jungshik Janga858d222014-06-23 17:17:47 +09002335
Shubangc480a712018-06-11 18:02:42 -07002336 public HdmiCecLocalDeviceAudioSystem audioSystem() {
2337 return (HdmiCecLocalDeviceAudioSystem) mCecController.getLocalDevice(
2338 HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
2339 }
2340
Jungshik Janga858d222014-06-23 17:17:47 +09002341 AudioManager getAudioManager() {
2342 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
2343 }
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09002344
2345 boolean isControlEnabled() {
2346 synchronized (mLock) {
2347 return mHdmiControlEnabled;
2348 }
2349 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002350
Jungshik Jangf67113f2014-08-22 16:27:19 +09002351 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09002352 int getPowerStatus() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09002353 assertRunOnServiceThread();
Yuncheol Heo38db6292014-07-01 14:15:14 +09002354 return mPowerStatus;
2355 }
2356
Jungshik Jangf67113f2014-08-22 16:27:19 +09002357 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09002358 boolean isPowerOnOrTransient() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09002359 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002360 return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
2361 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002362 }
2363
Jungshik Jangf67113f2014-08-22 16:27:19 +09002364 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09002365 boolean isPowerStandbyOrTransient() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09002366 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002367 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
2368 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002369 }
2370
Jungshik Jangf67113f2014-08-22 16:27:19 +09002371 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09002372 boolean isPowerStandby() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09002373 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002374 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002375 }
2376
2377 @ServiceThreadOnly
2378 void wakeUp() {
2379 assertRunOnServiceThread();
Yuncheol Heofc44e4e2014-08-04 19:41:09 +09002380 mWakeUpMessageReceived = true;
Dianne Hackborn280a64e2015-07-13 14:48:08 -07002381 mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.server.hdmi:WAKE");
Yuncheol Heo38db6292014-07-01 14:15:14 +09002382 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
2383 // the intent, the sequence will continue at onWakeUp().
2384 }
2385
2386 @ServiceThreadOnly
2387 void standby() {
2388 assertRunOnServiceThread();
Donghyun Cho02920a02016-10-11 17:17:34 +09002389 if (!canGoToStandby()) {
2390 return;
2391 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002392 mStandbyMessageReceived = true;
Jinsuk Kime26d8332015-01-09 08:55:41 +09002393 mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
Yuncheol Heo38db6292014-07-01 14:15:14 +09002394 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
2395 // the intent, the sequence will continue at onStandby().
2396 }
2397
Donghyun Choafd26a22016-12-23 15:53:28 +09002398 boolean isWakeUpMessageReceived() {
2399 return mWakeUpMessageReceived;
2400 }
2401
Amybf8a4662018-07-02 12:34:24 -07002402 @VisibleForTesting
2403 boolean isStandbyMessageReceived() {
2404 return mStandbyMessageReceived;
2405 }
2406
Yuncheol Heo38db6292014-07-01 14:15:14 +09002407 @ServiceThreadOnly
2408 private void onWakeUp() {
2409 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002410 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002411 if (mCecController != null) {
Jungshik Janga9f10622014-07-11 15:36:39 +09002412 if (mHdmiControlEnabled) {
Yuncheol Heofc44e4e2014-08-04 19:41:09 +09002413 int startReason = INITIATED_BY_SCREEN_ON;
2414 if (mWakeUpMessageReceived) {
2415 startReason = INITIATED_BY_WAKE_UP_MESSAGE;
2416 }
2417 initializeCec(startReason);
Jungshik Janga9f10622014-07-11 15:36:39 +09002418 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002419 } else {
2420 Slog.i(TAG, "Device does not support HDMI-CEC.");
2421 }
2422 // TODO: Initialize MHL local devices.
2423 }
2424
2425 @ServiceThreadOnly
Amybf8a4662018-07-02 12:34:24 -07002426 @VisibleForTesting
2427 protected void onStandby(final int standbyAction) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002428 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002429 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
Yuncheol Heo0608b932014-10-13 16:39:18 +09002430 invokeVendorCommandListenersOnControlStateChanged(false,
2431 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);
Amybf8a4662018-07-02 12:34:24 -07002432
2433 final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
2434
2435 if (!isStandbyMessageReceived() && !canGoToStandby()) {
Donghyun Cho02920a02016-10-11 17:17:34 +09002436 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
Amybf8a4662018-07-02 12:34:24 -07002437 for (HdmiCecLocalDevice device : devices) {
2438 device.onStandby(mStandbyMessageReceived, standbyAction);
2439 }
Donghyun Cho02920a02016-10-11 17:17:34 +09002440 return;
2441 }
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002442
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002443 disableDevices(new PendingActionClearedCallback() {
2444 @Override
2445 public void onCleared(HdmiCecLocalDevice device) {
2446 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
2447 devices.remove(device);
2448 if (devices.isEmpty()) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +09002449 onStandbyCompleted(standbyAction);
Yuncheol Heo4b542712014-07-30 20:31:06 +09002450 // We will not clear local devices here, since some OEM/SOC will keep passing
2451 // the received packets until the application processor enters to the sleep
2452 // actually.
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002453 }
2454 }
2455 });
2456 }
2457
Jinsuk Kime26d8332015-01-09 08:55:41 +09002458 private boolean canGoToStandby() {
2459 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2460 if (!device.canGoToStandby()) return false;
2461 }
2462 return true;
2463 }
2464
Terry Heo1ca0a432014-08-18 10:30:32 +09002465 @ServiceThreadOnly
2466 private void onLanguageChanged(String language) {
2467 assertRunOnServiceThread();
2468 mLanguage = language;
2469
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002470 if (isTvDeviceEnabled()) {
Terry Heo1ca0a432014-08-18 10:30:32 +09002471 tv().broadcastMenuLanguage(language);
Donghyun Chobc6e3722016-11-04 05:25:52 +09002472 mCecController.setLanguage(language);
Terry Heo1ca0a432014-08-18 10:30:32 +09002473 }
2474 }
2475
Jungshik Jangf67113f2014-08-22 16:27:19 +09002476 @ServiceThreadOnly
2477 String getLanguage() {
2478 assertRunOnServiceThread();
2479 return mLanguage;
2480 }
2481
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002482 private void disableDevices(PendingActionClearedCallback callback) {
Jungshik Jang350e68d2014-08-19 18:56:21 +09002483 if (mCecController != null) {
2484 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2485 device.disableDevice(mStandbyMessageReceived, callback);
2486 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002487 }
Jungshik Jang350e68d2014-08-19 18:56:21 +09002488
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09002489 mMhlController.clearAllLocalDevices();
Yuncheol Heo38db6292014-07-01 14:15:14 +09002490 }
2491
Yuncheol Heo38db6292014-07-01 14:15:14 +09002492 @ServiceThreadOnly
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002493 private void clearLocalDevices() {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002494 assertRunOnServiceThread();
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002495 if (mCecController == null) {
2496 return;
2497 }
2498 mCecController.clearLogicalAddress();
2499 mCecController.clearLocalDevices();
2500 }
2501
2502 @ServiceThreadOnly
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +09002503 private void onStandbyCompleted(int standbyAction) {
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002504 assertRunOnServiceThread();
2505 Slog.v(TAG, "onStandbyCompleted");
2506
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002507 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002508 return;
2509 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002510 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002511 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +09002512 device.onStandby(mStandbyMessageReceived, standbyAction);
Yuncheol Heo38db6292014-07-01 14:15:14 +09002513 }
2514 mStandbyMessageReceived = false;
Amyb887fa02018-06-21 11:22:13 -07002515 if (!isAudioSystemDevice()) {
2516 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false);
2517 mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
2518 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002519 }
Jinsuk Kim4d43d932014-07-03 16:43:58 +09002520
Jinsuk Kim119160a2014-07-07 18:48:10 +09002521 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
2522 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
2523 try {
2524 listener.asBinder().linkToDeath(record, 0);
2525 } catch (RemoteException e) {
2526 Slog.w(TAG, "Listener already died");
2527 return;
2528 }
2529 synchronized (mLock) {
2530 mVendorCommandListenerRecords.add(record);
2531 }
2532 }
2533
Yuncheol Heo0608b932014-10-13 16:39:18 +09002534 boolean invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress,
2535 byte[] params, boolean hasVendorId) {
Jinsuk Kim119160a2014-07-07 18:48:10 +09002536 synchronized (mLock) {
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09002537 if (mVendorCommandListenerRecords.isEmpty()) {
2538 return false;
2539 }
Jinsuk Kim119160a2014-07-07 18:48:10 +09002540 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2541 if (record.mDeviceType != deviceType) {
2542 continue;
2543 }
2544 try {
Yuncheol Heo0608b932014-10-13 16:39:18 +09002545 record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId);
Jinsuk Kim119160a2014-07-07 18:48:10 +09002546 } catch (RemoteException e) {
2547 Slog.e(TAG, "Failed to notify vendor command reception", e);
2548 }
2549 }
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09002550 return true;
Jinsuk Kim119160a2014-07-07 18:48:10 +09002551 }
2552 }
2553
Yuncheol Heo0608b932014-10-13 16:39:18 +09002554 boolean invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason) {
2555 synchronized (mLock) {
2556 if (mVendorCommandListenerRecords.isEmpty()) {
2557 return false;
2558 }
2559 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2560 try {
2561 record.mListener.onControlStateChanged(enabled, reason);
2562 } catch (RemoteException e) {
2563 Slog.e(TAG, "Failed to notify control-state-changed to vendor handler", e);
2564 }
2565 }
2566 return true;
2567 }
2568 }
2569
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002570 private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {
2571 HdmiMhlVendorCommandListenerRecord record =
2572 new HdmiMhlVendorCommandListenerRecord(listener);
Jungshik Jangf4249322014-08-21 14:17:05 +09002573 try {
2574 listener.asBinder().linkToDeath(record, 0);
2575 } catch (RemoteException e) {
2576 Slog.w(TAG, "Listener already died.");
2577 return;
2578 }
2579
2580 synchronized (mLock) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002581 mMhlVendorCommandListenerRecords.add(record);
Jungshik Jangf4249322014-08-21 14:17:05 +09002582 }
2583 }
2584
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002585 void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002586 synchronized (mLock) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002587 for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002588 try {
2589 record.mListener.onReceived(portId, offest, length, data);
2590 } catch (RemoteException e) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002591 Slog.e(TAG, "Failed to notify MHL vendor command", e);
Jungshik Jangf4249322014-08-21 14:17:05 +09002592 }
2593 }
2594 }
2595 }
2596
Donghyun Chob3515642017-03-02 13:47:40 +09002597 void setStandbyMode(boolean isStandbyModeOn) {
2598 assertRunOnServiceThread();
2599 if (isPowerOnOrTransient() && isStandbyModeOn) {
2600 mPowerManager.goToSleep(SystemClock.uptimeMillis(),
2601 PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
2602 if (playback() != null) {
2603 playback().sendStandby(0 /* unused */);
2604 }
2605 } else if (isPowerStandbyOrTransient() && !isStandbyModeOn) {
2606 mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.server.hdmi:WAKE");
2607 if (playback() != null) {
2608 oneTouchPlay(new IHdmiControlCallback.Stub() {
2609 @Override
2610 public void onComplete(int result) {
2611 if (result != HdmiControlManager.RESULT_SUCCESS) {
2612 Slog.w(TAG, "Failed to complete 'one touch play'. result=" + result);
2613 }
2614 }
2615 });
2616 }
2617 }
2618 }
2619
Jinsuk Kim4d43d932014-07-03 16:43:58 +09002620 boolean isProhibitMode() {
2621 synchronized (mLock) {
2622 return mProhibitMode;
2623 }
2624 }
2625
2626 void setProhibitMode(boolean enabled) {
2627 synchronized (mLock) {
2628 mProhibitMode = enabled;
2629 }
2630 }
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002631
2632 @ServiceThreadOnly
Donghyun Chobc6e3722016-11-04 05:25:52 +09002633 void setCecOption(int key, boolean value) {
Jinsuk Kim50084862014-08-07 13:11:40 +09002634 assertRunOnServiceThread();
2635 mCecController.setOption(key, value);
2636 }
2637
2638 @ServiceThreadOnly
2639 void setControlEnabled(boolean enabled) {
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002640 assertRunOnServiceThread();
2641
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002642 synchronized (mLock) {
2643 mHdmiControlEnabled = enabled;
2644 }
2645
2646 if (enabled) {
Yuncheol Heof1702482014-11-27 19:52:01 +09002647 enableHdmiControlService();
2648 return;
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002649 }
Yuncheol Heof1702482014-11-27 19:52:01 +09002650 // Call the vendor handler before the service is disabled.
2651 invokeVendorCommandListenersOnControlStateChanged(false,
2652 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING);
2653 // Post the remained tasks in the service thread again to give the vendor-issued-tasks
2654 // a chance to run.
2655 runOnServiceThread(new Runnable() {
2656 @Override
2657 public void run() {
2658 disableHdmiControlService();
2659 }
2660 });
2661 return;
2662 }
2663
2664 @ServiceThreadOnly
2665 private void enableHdmiControlService() {
Amy718e41e2018-08-17 17:23:37 -07002666 mCecController.setOption(OptionKey.ENABLE_CEC, true);
Donghyun Chobc6e3722016-11-04 05:25:52 +09002667 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
Yuncheol Heof1702482014-11-27 19:52:01 +09002668 mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED);
2669
2670 initializeCec(INITIATED_BY_ENABLE_CEC);
2671 }
2672
2673 @ServiceThreadOnly
2674 private void disableHdmiControlService() {
2675 disableDevices(new PendingActionClearedCallback() {
2676 @Override
2677 public void onCleared(HdmiCecLocalDevice device) {
2678 assertRunOnServiceThread();
2679 mCecController.flush(new Runnable() {
2680 @Override
2681 public void run() {
Donghyun Chobc6e3722016-11-04 05:25:52 +09002682 mCecController.setOption(OptionKey.ENABLE_CEC, false);
Amy718e41e2018-08-17 17:23:37 -07002683 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false);
Yuncheol Heof1702482014-11-27 19:52:01 +09002684 mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED);
2685 clearLocalDevices();
2686 }
2687 });
2688 }
2689 });
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002690 }
Jungshik Jang867b4e02014-08-12 13:41:30 +09002691
2692 @ServiceThreadOnly
2693 void setActivePortId(int portId) {
2694 assertRunOnServiceThread();
2695 mActivePortId = portId;
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002696
2697 // Resets last input for MHL, which stays valid only after the MHL device was selected,
2698 // and no further switching is done.
2699 setLastInputForMhl(Constants.INVALID_PORT_ID);
Jungshik Jang867b4e02014-08-12 13:41:30 +09002700 }
Yuncheol Heo08a1be82014-08-12 20:58:41 +09002701
Amy123ec402018-09-25 10:56:31 -07002702 ActiveSource getActiveSource() {
2703 synchronized (mLock) {
2704 return mActiveSource;
2705 }
2706 }
2707
2708 void setActiveSource(int logicalAddress, int physicalAddress) {
2709 synchronized (mLock) {
2710 mActiveSource.logicalAddress = logicalAddress;
2711 mActiveSource.physicalAddress = physicalAddress;
2712 }
2713 }
2714
2715 // This method should only be called when the device can be the active source
2716 // and all the device types call into this method.
2717 // For example, when receiving broadcast messages, all the device types will call this
2718 // method but only one of them will be the Active Source.
2719 protected void setAndBroadcastActiveSource(
Amyb9d7f432018-11-30 15:08:30 -08002720 int physicalAddress, int deviceType, int source) {
Amy123ec402018-09-25 10:56:31 -07002721 // If the device has both playback and audio system logical addresses,
2722 // playback will claim active source. Otherwise audio system will.
2723 if (deviceType == HdmiDeviceInfo.DEVICE_PLAYBACK) {
2724 HdmiCecLocalDevicePlayback playback = playback();
2725 playback.setIsActiveSource(true);
2726 playback.wakeUpIfActiveSource();
Amyb9d7f432018-11-30 15:08:30 -08002727 playback.maySendActiveSource(source);
Amy123ec402018-09-25 10:56:31 -07002728 setActiveSource(playback.mAddress, physicalAddress);
2729 }
2730
2731 if (deviceType == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
2732 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
2733 if (playback() != null) {
2734 audioSystem.setIsActiveSource(false);
2735 } else {
2736 audioSystem.setIsActiveSource(true);
2737 audioSystem.wakeUpIfActiveSource();
Amyb9d7f432018-11-30 15:08:30 -08002738 audioSystem.maySendActiveSource(source);
Amy123ec402018-09-25 10:56:31 -07002739 setActiveSource(audioSystem.mAddress, physicalAddress);
2740 }
2741 }
2742 }
2743
2744 // This method should only be called when the device can be the active source
2745 // and only one of the device types calls into this method.
2746 // For example, when receiving One Touch Play, only playback device handles it
2747 // and this method updates Active Source in all the device types sharing the same
2748 // Physical Address.
2749 protected void setAndBroadcastActiveSourceFromOneDeviceType(
2750 int sourceAddress, int physicalAddress) {
2751 // If the device has both playback and audio system logical addresses,
2752 // playback will claim active source. Otherwise audio system will.
2753 HdmiCecLocalDevicePlayback playback = playback();
2754 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
2755 if (playback != null) {
2756 playback.setIsActiveSource(true);
2757 playback.wakeUpIfActiveSource();
2758 playback.maySendActiveSource(sourceAddress);
2759 if (audioSystem != null) {
2760 audioSystem.setIsActiveSource(false);
2761 }
2762 setActiveSource(playback.mAddress, physicalAddress);
2763 } else {
2764 if (audioSystem != null) {
2765 audioSystem.setIsActiveSource(true);
2766 audioSystem.wakeUpIfActiveSource();
2767 audioSystem.maySendActiveSource(sourceAddress);
2768 setActiveSource(audioSystem.mAddress, physicalAddress);
2769 }
2770 }
2771 }
2772
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002773 @ServiceThreadOnly
2774 void setLastInputForMhl(int portId) {
2775 assertRunOnServiceThread();
2776 mLastInputMhl = portId;
2777 }
2778
2779 @ServiceThreadOnly
2780 int getLastInputForMhl() {
2781 assertRunOnServiceThread();
2782 return mLastInputMhl;
2783 }
2784
2785 /**
2786 * Performs input change, routing control for MHL device.
2787 *
2788 * @param portId MHL port, or the last port to go back to if {@code contentOn} is false
2789 * @param contentOn {@code true} if RAP data is content on; otherwise false
2790 */
2791 @ServiceThreadOnly
2792 void changeInputForMhl(int portId, boolean contentOn) {
2793 assertRunOnServiceThread();
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002794 if (tv() == null) return;
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002795 final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID;
Jinsuk Kimcb8661c2015-01-19 12:39:06 +09002796 if (portId != Constants.INVALID_PORT_ID) {
2797 tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() {
2798 @Override
2799 public void onComplete(int result) throws RemoteException {
2800 // Keep the last input to switch back later when RAP[ContentOff] is received.
2801 // This effectively sets the port to invalid one if the switching is for
2802 // RAP[ContentOff].
2803 setLastInputForMhl(lastInput);
2804 }
2805 });
2806 }
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002807 // MHL device is always directly connected to the port. Update the active port ID to avoid
2808 // unnecessary post-routing control task.
2809 tv().setActivePortId(portId);
2810
2811 // The port is either the MHL-enabled port where the mobile device is connected, or
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09002812 // the last port to go back to when turnoff command is received. Note that the last port
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002813 // may not be the MHL-enabled one. In this case the device info to be passed to
2814 // input change listener should be the one describing the corresponding HDMI port.
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09002815 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jinsuk Kimcb8661c2015-01-19 12:39:06 +09002816 HdmiDeviceInfo info = (device != null) ? device.getInfo()
2817 : mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002818 invokeInputChangeListener(info);
2819 }
2820
2821 void setMhlInputChangeEnabled(boolean enabled) {
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09002822 mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
Yuncheol Heo08a1be82014-08-12 20:58:41 +09002823
2824 synchronized (mLock) {
2825 mMhlInputChangeEnabled = enabled;
2826 }
2827 }
2828
2829 boolean isMhlInputChangeEnabled() {
2830 synchronized (mLock) {
2831 return mMhlInputChangeEnabled;
2832 }
2833 }
Jungshik Jang339227d2014-08-25 15:37:20 +09002834
2835 @ServiceThreadOnly
2836 void displayOsd(int messageId) {
2837 assertRunOnServiceThread();
2838 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2839 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2840 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2841 HdmiControlService.PERMISSION);
2842 }
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09002843
2844 @ServiceThreadOnly
2845 void displayOsd(int messageId, int extra) {
2846 assertRunOnServiceThread();
2847 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2848 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
Yuncheol Heo2b0da5c2014-10-22 14:32:27 +09002849 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra);
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09002850 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2851 HdmiControlService.PERMISSION);
2852 }
Jungshik Jang0792d372014-04-23 17:57:26 +09002853}